import { type ClaudeModelOptions, type CodexModelOptions, type ProviderKind, type ProviderModelOptions, type ScopedThreadRef, type ServerProviderModel, } from "@t3tools/contracts"; import { applyClaudePromptEffortPrefix, isClaudeUltrathinkPrompt, trimOrNull, getDefaultEffort, getDefaultContextWindow, hasContextWindowOption, resolveEffort, } from "@t3tools/shared/model"; import { memo, useCallback, useState } from "react"; import type { VariantProps } from "class-variance-authority"; import { ChevronDownIcon } from "lucide-react"; import { Button, buttonVariants } from "../ui/button"; import { Menu, MenuGroup, MenuPopup, MenuRadioGroup, MenuRadioItem, MenuSeparator as MenuDivider, MenuTrigger, } from "../ui/menu"; import { useComposerDraftStore, DraftId } from "../../composerDraftStore"; import { getProviderModelCapabilities } from "../../providerModels"; import { cn } from "~/lib/utils"; type ProviderOptions = ProviderModelOptions[ProviderKind]; type TraitsPersistence = | { threadRef?: ScopedThreadRef; draftId?: DraftId; onModelOptionsChange?: never; } | { threadRef?: undefined; onModelOptionsChange: (nextOptions: ProviderOptions | undefined) => void; }; const ULTRATHINK_PROMPT_PREFIX = "Ultrathink:\n"; function getRawEffort( provider: ProviderKind, modelOptions: ProviderOptions | null | undefined, ): string | null { if (provider === "codex") { return trimOrNull((modelOptions as CodexModelOptions | undefined)?.reasoningEffort); } return trimOrNull((modelOptions as ClaudeModelOptions | undefined)?.effort); } function getRawContextWindow( provider: ProviderKind, modelOptions: ProviderOptions | null | undefined, ): string | null { if (provider === "claudeAgent") { return trimOrNull((modelOptions as ClaudeModelOptions | undefined)?.contextWindow); } return null; } function buildNextOptions( provider: ProviderKind, modelOptions: ProviderOptions | null | undefined, patch: Record, ): ProviderOptions { if (provider === "codex") { return { ...(modelOptions as CodexModelOptions | undefined), ...patch } as CodexModelOptions; } return { ...(modelOptions as ClaudeModelOptions | undefined), ...patch } as ClaudeModelOptions; } function getSelectedTraits( provider: ProviderKind, models: ReadonlyArray, model: string | null | undefined, prompt: string, modelOptions: ProviderOptions | null | undefined, allowPromptInjectedEffort: boolean, ) { const caps = getProviderModelCapabilities(models, model, provider); const effortLevels = allowPromptInjectedEffort ? caps.reasoningEffortLevels : caps.reasoningEffortLevels.filter( (option) => !caps.promptInjectedEffortLevels.includes(option.value), ); // Resolve effort from options (provider-specific key) const rawEffort = getRawEffort(provider, modelOptions); const effort = resolveEffort(caps, rawEffort) ?? null; // Thinking toggle (only for models that support it) const thinkingEnabled = caps.supportsThinkingToggle ? ((modelOptions as ClaudeModelOptions | undefined)?.thinking ?? true) : null; // Fast mode const fastModeEnabled = caps.supportsFastMode && (modelOptions as { fastMode?: boolean } | undefined)?.fastMode === true; // Context window const contextWindowOptions = caps.contextWindowOptions; const rawContextWindow = getRawContextWindow(provider, modelOptions); const defaultContextWindow = getDefaultContextWindow(caps); const contextWindow = rawContextWindow && hasContextWindowOption(caps, rawContextWindow) ? rawContextWindow : defaultContextWindow; // Prompt-controlled effort (e.g. ultrathink in prompt text) const ultrathinkPromptControlled = allowPromptInjectedEffort && caps.promptInjectedEffortLevels.length > 0 && isClaudeUltrathinkPrompt(prompt); // Check if "ultrathink" appears in the body text (not just our prefix) const ultrathinkInBodyText = ultrathinkPromptControlled && isClaudeUltrathinkPrompt(prompt.replace(/^Ultrathink:\s*/i, "")); return { caps, effort, effortLevels, thinkingEnabled, fastModeEnabled, contextWindowOptions, contextWindow, defaultContextWindow, ultrathinkPromptControlled, ultrathinkInBodyText, }; } export interface TraitsMenuContentProps { provider: ProviderKind; models: ReadonlyArray; model: string | null | undefined; prompt: string; onPromptChange: (prompt: string) => void; modelOptions?: ProviderOptions | null | undefined; allowPromptInjectedEffort?: boolean; triggerVariant?: VariantProps["variant"]; triggerClassName?: string; } export const TraitsMenuContent = memo(function TraitsMenuContentImpl({ provider, models, model, prompt, onPromptChange, modelOptions, allowPromptInjectedEffort = true, ...persistence }: TraitsMenuContentProps & TraitsPersistence) { const setProviderModelOptions = useComposerDraftStore((store) => store.setProviderModelOptions); const updateModelOptions = useCallback( (nextOptions: ProviderOptions | undefined) => { if ("onModelOptionsChange" in persistence) { persistence.onModelOptionsChange(nextOptions); return; } const threadTarget = persistence.threadRef ?? persistence.draftId; if (!threadTarget) { return; } setProviderModelOptions(threadTarget, provider, nextOptions, { persistSticky: true, }); }, [persistence, provider, setProviderModelOptions], ); const { caps, effort, effortLevels, thinkingEnabled, fastModeEnabled, contextWindowOptions, contextWindow, defaultContextWindow, ultrathinkPromptControlled, ultrathinkInBodyText, } = getSelectedTraits(provider, models, model, prompt, modelOptions, allowPromptInjectedEffort); const defaultEffort = getDefaultEffort(caps); const handleEffortChange = useCallback( (value: string) => { if (!value) return; const nextOption = effortLevels.find((option) => option.value === value); if (!nextOption) return; if (caps.promptInjectedEffortLevels.includes(nextOption.value)) { const nextPrompt = prompt.trim().length === 0 ? ULTRATHINK_PROMPT_PREFIX : applyClaudePromptEffortPrefix(prompt, "ultrathink"); onPromptChange(nextPrompt); return; } if (ultrathinkInBodyText) return; if (ultrathinkPromptControlled) { const stripped = prompt.replace(/^Ultrathink:\s*/i, ""); onPromptChange(stripped); } const effortKey = provider === "codex" ? "reasoningEffort" : "effort"; updateModelOptions( buildNextOptions(provider, modelOptions, { [effortKey]: nextOption.value }), ); }, [ ultrathinkPromptControlled, ultrathinkInBodyText, modelOptions, onPromptChange, updateModelOptions, effortLevels, prompt, caps.promptInjectedEffortLevels, provider, ], ); if (effort === null && thinkingEnabled === null && contextWindowOptions.length <= 1) { return null; } return ( <> {effort ? ( <>
Effort
{ultrathinkInBodyText ? (
Your prompt contains "ultrathink" in the text. Remove it to change effort.
) : null} {effortLevels.map((option) => ( {option.label} {option.value === defaultEffort ? " (default)" : ""} ))}
) : thinkingEnabled !== null ? (
Thinking
{ updateModelOptions( buildNextOptions(provider, modelOptions, { thinking: value === "on" }), ); }} > On (default) Off
) : null} {caps.supportsFastMode ? ( <>
Fast Mode
{ updateModelOptions( buildNextOptions(provider, modelOptions, { fastMode: value === "on" }), ); }} > off on
) : null} {contextWindowOptions.length > 1 ? ( <>
Context Window
{ updateModelOptions( buildNextOptions(provider, modelOptions, { contextWindow: value, }), ); }} > {contextWindowOptions.map((option) => ( {option.label} {option.value === defaultContextWindow ? " (default)" : ""} ))}
) : null} ); }); export const TraitsPicker = memo(function TraitsPicker({ provider, models, model, prompt, onPromptChange, modelOptions, allowPromptInjectedEffort = true, triggerVariant, triggerClassName, ...persistence }: TraitsMenuContentProps & TraitsPersistence) { const [isMenuOpen, setIsMenuOpen] = useState(false); const { caps, effort, effortLevels, thinkingEnabled, fastModeEnabled, contextWindowOptions, contextWindow, defaultContextWindow, ultrathinkPromptControlled, } = getSelectedTraits(provider, models, model, prompt, modelOptions, allowPromptInjectedEffort); const effortLabel = effort ? (effortLevels.find((l) => l.value === effort)?.label ?? effort) : null; const contextWindowLabel = contextWindowOptions.length > 1 && contextWindow !== defaultContextWindow ? (contextWindowOptions.find((o) => o.value === contextWindow)?.label ?? null) : null; const triggerLabel = [ ultrathinkPromptControlled ? "Ultrathink" : effortLabel ? effortLabel : thinkingEnabled === null ? null : `Thinking ${thinkingEnabled ? "On" : "Off"}`, ...(caps.supportsFastMode && fastModeEnabled ? ["Fast"] : []), ...(contextWindowLabel ? [contextWindowLabel] : []), ] .filter(Boolean) .join(" ยท "); const isCodexStyle = provider === "codex"; return ( { setIsMenuOpen(open); }} > } > {isCodexStyle ? ( {triggerLabel} ) : ( <> {triggerLabel} ); });