import { type ModelSlug, type ProviderKind, type ProviderModelOptions, type ThreadId, } from "@t3tools/contracts"; import { getModelCapabilities, isClaudeUltrathinkPrompt, normalizeClaudeModelOptions, normalizeCodexModelOptions, trimOrNull, getDefaultEffort, hasEffortLevel, } from "@t3tools/shared/model"; import type { ReactNode } from "react"; import { TraitsMenuContent, TraitsPicker } from "./TraitsPicker"; export type ComposerProviderStateInput = { provider: ProviderKind; model: ModelSlug; prompt: string; modelOptions: ProviderModelOptions | null | undefined; }; export type ComposerProviderState = { provider: ProviderKind; promptEffort: string | null; modelOptionsForDispatch: ProviderModelOptions[ProviderKind] | undefined; composerFrameClassName?: string; composerSurfaceClassName?: string; modelPickerIconClassName?: string; }; type ProviderRegistryEntry = { getState: (input: ComposerProviderStateInput) => ComposerProviderState; renderTraitsMenuContent: (input: { threadId: ThreadId; model: ModelSlug; modelOptions: ProviderModelOptions[ProviderKind] | undefined; prompt: string; onPromptChange: (prompt: string) => void; }) => ReactNode; renderTraitsPicker: (input: { threadId: ThreadId; model: ModelSlug; modelOptions: ProviderModelOptions[ProviderKind] | undefined; prompt: string; onPromptChange: (prompt: string) => void; }) => ReactNode; }; function getProviderStateFromCapabilities( input: ComposerProviderStateInput, ): ComposerProviderState { const { provider, model, prompt, modelOptions } = input; const caps = getModelCapabilities(provider, model); const providerOptions = modelOptions?.[provider]; // Resolve effort const rawEffort = providerOptions ? "effort" in providerOptions ? providerOptions.effort : "reasoningEffort" in providerOptions ? providerOptions.reasoningEffort : null : null; const draftEffort = trimOrNull(rawEffort); const defaultEffort = getDefaultEffort(caps); const isPromptInjected = draftEffort ? caps.promptInjectedEffortLevels.includes(draftEffort) : false; const promptEffort = draftEffort && !isPromptInjected && hasEffortLevel(caps, draftEffort) ? draftEffort : defaultEffort && hasEffortLevel(caps, defaultEffort) ? defaultEffort : null; // Normalize options for dispatch const normalizedOptions = provider === "codex" ? normalizeCodexModelOptions(model, providerOptions) : normalizeClaudeModelOptions(model, providerOptions); // Ultrathink styling (driven by capabilities data, not provider identity) const ultrathinkActive = caps.promptInjectedEffortLevels.length > 0 && isClaudeUltrathinkPrompt(prompt); return { provider, promptEffort, modelOptionsForDispatch: normalizedOptions, ...(ultrathinkActive ? { composerFrameClassName: "ultrathink-frame" } : {}), ...(ultrathinkActive ? { composerSurfaceClassName: "shadow-[0_0_0_1px_rgba(255,255,255,0.04)_inset]" } : {}), ...(ultrathinkActive ? { modelPickerIconClassName: "ultrathink-chroma" } : {}), }; } const composerProviderRegistry: Record = { codex: { getState: (input) => getProviderStateFromCapabilities(input), renderTraitsMenuContent: ({ threadId, model, modelOptions, prompt, onPromptChange }) => ( ), renderTraitsPicker: ({ threadId, model, modelOptions, prompt, onPromptChange }) => ( ), }, claudeAgent: { getState: (input) => getProviderStateFromCapabilities(input), renderTraitsMenuContent: ({ threadId, model, modelOptions, prompt, onPromptChange }) => ( ), renderTraitsPicker: ({ threadId, model, modelOptions, prompt, onPromptChange }) => ( ), }, }; export function getComposerProviderState(input: ComposerProviderStateInput): ComposerProviderState { return composerProviderRegistry[input.provider].getState(input); } export function renderProviderTraitsMenuContent(input: { provider: ProviderKind; threadId: ThreadId; model: ModelSlug; modelOptions: ProviderModelOptions[ProviderKind] | undefined; prompt: string; onPromptChange: (prompt: string) => void; }): ReactNode { return composerProviderRegistry[input.provider].renderTraitsMenuContent({ threadId: input.threadId, model: input.model, modelOptions: input.modelOptions, prompt: input.prompt, onPromptChange: input.onPromptChange, }); } export function renderProviderTraitsPicker(input: { provider: ProviderKind; threadId: ThreadId; model: ModelSlug; modelOptions: ProviderModelOptions[ProviderKind] | undefined; prompt: string; onPromptChange: (prompt: string) => void; }): ReactNode { return composerProviderRegistry[input.provider].renderTraitsPicker({ threadId: input.threadId, model: input.model, modelOptions: input.modelOptions, prompt: input.prompt, onPromptChange: input.onPromptChange, }); }