import { type ModelCapabilities, type ProviderDriverKind, type ProviderInstanceId, type ProviderOptionSelection, type ScopedThreadRef, type ServerProviderModel, } from "@t3tools/contracts"; import { buildExplicitProviderOptionSelectionsFromDescriptors, getProviderOptionCurrentValue, getProviderOptionDescriptors, isClaudeUltrathinkPrompt, normalizeModelSlug, } from "@t3tools/shared/model"; import type { VariantProps } from "class-variance-authority"; import type { ReactNode } from "react"; import type { buttonVariants } from "../ui/button"; import type { DraftId } from "../../composerDraftStore"; import { getProviderModelCapabilities } from "../../providerModels"; import type { ComposerControlSize } from "./ComposerControl"; import { shouldRenderTraitsControls, TraitsMenuContent, TraitsPicker } from "./TraitsPicker"; export type ComposerProviderStateInput = { provider: ProviderDriverKind; model: string; models: ReadonlyArray; promptInjectionState?: ComposerPromptInjectionState; modelOptions: ReadonlyArray | null | undefined; planModeEnabled: boolean; }; export type ComposerPromptInjectionState = "none" | "ultrathink"; export type ComposerProviderState = { provider: ProviderDriverKind; promptEffort: string | null; modelOptionsForDispatch: ReadonlyArray | undefined; composerFrameClassName?: string; composerSurfaceClassName?: string; modelPickerIconClassName?: string; }; type TraitsRenderInput = { provider: ProviderDriverKind; instanceId?: ProviderInstanceId; threadRef?: ScopedThreadRef; draftId?: DraftId; model: string; models: ReadonlyArray; modelOptions: ReadonlyArray | undefined; prompt: string; onPromptChange: (prompt: string) => void; planModeEnabled: boolean; size?: ComposerControlSize; hidden?: boolean; triggerVariant?: VariantProps["variant"]; triggerClassName?: string; isComposerOwned?: boolean; }; export function getComposerPromptInjectionState(prompt: string): ComposerPromptInjectionState { return isClaudeUltrathinkPrompt(prompt) ? "ultrathink" : "none"; } /** * Cursor ACP can report `fastMode: true` as the provider default. T3 only * treats Fast as selected when the user chose it (draft/sticky/settings). * Otherwise inject an explicit `false` so new chats stay Normal and the * send path can overwrite a prior Fast session — descriptor defaults are * otherwise omitted by `buildExplicitProviderOptionSelectionsFromDescriptors`. */ export function withImplicitFastModeDefault( caps: ModelCapabilities, modelOptions: ReadonlyArray | null | undefined, ): ReadonlyArray | undefined { const hasExplicitFastMode = modelOptions?.some((selection) => selection.id === "fastMode"); if (hasExplicitFastMode) { return modelOptions ?? undefined; } const hasFastModeDescriptor = caps.optionDescriptors?.some( (descriptor) => descriptor.type === "boolean" && descriptor.id === "fastMode", ); if (!hasFastModeDescriptor) { return modelOptions ?? undefined; } return [...(modelOptions ?? []), { id: "fastMode", value: false }]; } function resolveComposerOptionSelections( models: ReadonlyArray, model: string, provider: ProviderDriverKind, modelOptions: ReadonlyArray | null | undefined, planModeEnabled: boolean, ): { caps: ModelCapabilities; selections: ReadonlyArray | undefined; } { const caps = getProviderModelCapabilities(models, model, provider, planModeEnabled); return { caps, selections: withImplicitFastModeDefault(caps, modelOptions) }; } export function getComposerProviderState(input: ComposerProviderStateInput): ComposerProviderState { const { provider, model, models, modelOptions, promptInjectionState = "none", planModeEnabled, } = input; if (provider === "opencode") { const normalizedModel = normalizeModelSlug(model, provider); const modelIsInCatalog = models.some((candidate) => candidate.slug === normalizedModel); if (!modelIsInCatalog) { const preservedOptions = modelOptions?.filter( (option) => planModeEnabled || option.id !== "agent" || option.value !== "plan", ); return { provider, promptEffort: null, modelOptionsForDispatch: preservedOptions && preservedOptions.length > 0 ? preservedOptions : undefined, }; } } const { caps, selections } = resolveComposerOptionSelections( models, model, provider, modelOptions, planModeEnabled, ); const descriptors = getProviderOptionDescriptors({ caps, selections }); const primarySelectDescriptor = descriptors.find( (descriptor): descriptor is Extract<(typeof descriptors)[number], { type: "select" }> => descriptor.type === "select", ); const primaryValue = getProviderOptionCurrentValue(primarySelectDescriptor ?? null); const promptEffort = typeof primaryValue === "string" ? primaryValue : null; const ultrathinkActive = (primarySelectDescriptor?.promptInjectedValues?.length ?? 0) > 0 && promptInjectionState === "ultrathink"; return { provider, promptEffort, modelOptionsForDispatch: buildExplicitProviderOptionSelectionsFromDescriptors( descriptors, selections, ), ...(ultrathinkActive ? { composerFrameClassName: "ultrathink-frame", composerSurfaceClassName: "shadow-[0_0_0_1px_rgba(255,255,255,0.07)_inset]", modelPickerIconClassName: "ultrathink-chroma", } : {}), }; } function renderTraitsControl( Component: typeof TraitsMenuContent | typeof TraitsPicker, input: TraitsRenderInput, ): ReactNode { const { provider, instanceId, threadRef, draftId, model, models, modelOptions, prompt, onPromptChange, planModeEnabled, size, hidden, triggerVariant, triggerClassName, isComposerOwned, } = input; const hasTarget = threadRef !== undefined || draftId !== undefined; const { selections: resolvedModelOptions } = resolveComposerOptionSelections( models, model, provider, modelOptions, planModeEnabled, ); if ( !hasTarget || !shouldRenderTraitsControls({ provider, models, model, modelOptions: resolvedModelOptions, prompt, planModeEnabled, }) ) { return null; } return (