import { type ProviderDriverKind, type ProviderInstanceId, type ProviderOptionDescriptor, type ProviderOptionSelection, type ScopedThreadRef, type ServerProviderModel, } from "@t3tools/contracts"; import { applyClaudePromptEffortPrefix, buildProviderOptionSelectionsFromDescriptors, getProviderOptionCurrentLabel, getProviderOptionCurrentValue, getProviderOptionDescriptors, isClaudeUltrathinkPrompt, normalizeModelSlug, } from "@t3tools/shared/model"; import { memo, useCallback } from "react"; import type { VariantProps } from "class-variance-authority"; import { ZapIcon } from "lucide-react"; import { 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"; import { Badge } from "../ui/badge"; import { ComposerControl, ComposerControlChevron, ComposerControlIcon, type ComposerControlSize, } from "./ComposerControl"; import { composerFloatingLayerProps } from "./composerEventScope"; import { useComposerMenuState } from "./useComposerMenuState"; type ProviderOptions = ReadonlyArray; const SAVED_OPTION_LABELS: Readonly> = { agent: "Agent", effort: "Effort", reasoningEffort: "Reasoning effort", variant: "Reasoning", }; function savedOptionLabel(id: string): string { return ( SAVED_OPTION_LABELS[id] ?? id.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (character) => character.toUpperCase()) ); } /** Read-only descriptors for saved values whose OpenCode model metadata is unavailable. */ export function buildUnavailableModelOptionDescriptors( selections: ProviderOptions | null | undefined, ): ReadonlyArray { return (selections ?? []).map((selection) => typeof selection.value === "boolean" ? { id: selection.id, label: savedOptionLabel(selection.id), type: "boolean" as const, currentValue: selection.value, } : { id: selection.id, label: savedOptionLabel(selection.id), type: "select" as const, options: [{ id: selection.value, label: selection.value }], currentValue: selection.value, }, ); } type TraitsPersistence = | { threadRef?: ScopedThreadRef; draftId?: DraftId; onModelOptionsChange?: never; } | { threadRef?: undefined; onModelOptionsChange: (nextOptions: ProviderOptions | undefined) => void; }; const ULTRATHINK_PROMPT_PREFIX = "Ultrathink:\n"; function DefaultBadge() { return ( Default ); } function replaceDescriptorCurrentValue( descriptors: ReadonlyArray, descriptorId: string, currentValue: string | boolean | undefined, ): ReadonlyArray { return descriptors.map((descriptor) => descriptor.id !== descriptorId ? descriptor : descriptor.type === "boolean" ? { ...descriptor, ...(typeof currentValue === "boolean" ? { currentValue } : {}), } : { ...descriptor, ...(typeof currentValue === "string" ? { currentValue } : {}), }, ); } function getDescriptorStringValue( descriptor: Extract | null, ): string | null { if (!descriptor) { return null; } const value = getProviderOptionCurrentValue(descriptor); return typeof value === "string" ? value : null; } function getSelectedTraits( provider: ProviderDriverKind, models: ReadonlyArray, model: string | null | undefined, prompt: string, modelOptions: ProviderOptions | null | undefined, allowPromptInjectedEffort: boolean, planModeEnabled: boolean, ) { const caps = getProviderModelCapabilities(models, model, provider, planModeEnabled); const modelIsUnavailable = provider === "opencode" && !models.some((candidate) => candidate.slug === normalizeModelSlug(model, provider)); const descriptors = modelIsUnavailable ? buildUnavailableModelOptionDescriptors( planModeEnabled ? modelOptions : modelOptions?.filter((option) => option.id !== "agent" || option.value !== "plan"), ) : getProviderOptionDescriptors({ caps, selections: modelOptions, }); const selectDescriptors = descriptors.filter( (descriptor): descriptor is Extract => descriptor.type === "select", ); const booleanDescriptors = descriptors.filter( (descriptor): descriptor is Extract => descriptor.type === "boolean", ); const primarySelectDescriptor = selectDescriptors[0] ?? null; const contextWindowDescriptor = selectDescriptors.find((descriptor) => descriptor.id === "contextWindow") ?? null; const agentDescriptor = selectDescriptors.find((descriptor) => descriptor.id === "agent") ?? null; const fastModeDescriptor = booleanDescriptors.find((descriptor) => descriptor.id === "fastMode") ?? null; const thinkingDescriptor = booleanDescriptors.find((descriptor) => descriptor.id === "thinking") ?? null; // Prompt-controlled effort (e.g. ultrathink in prompt text) const ultrathinkPromptControlled = allowPromptInjectedEffort && (primarySelectDescriptor?.promptInjectedValues?.length ?? 0) > 0 && isClaudeUltrathinkPrompt(prompt); // Check if "ultrathink" appears in the body text (not just our prefix) const ultrathinkInBodyText = ultrathinkPromptControlled && isClaudeUltrathinkPrompt(prompt.replace(/^Ultrathink:\s*/i, "")); const effort = (ultrathinkPromptControlled ? "ultrathink" : getDescriptorStringValue(primarySelectDescriptor)) ?? null; const thinkingEnabled = typeof thinkingDescriptor?.currentValue === "boolean" ? thinkingDescriptor.currentValue : null; const contextWindow = getDescriptorStringValue(contextWindowDescriptor); const selectedAgent = getDescriptorStringValue(agentDescriptor); const selectedAgentLabel = agentDescriptor ? getProviderOptionCurrentLabel(agentDescriptor) : null; return { caps, descriptors, selectDescriptors, booleanDescriptors, primarySelectDescriptor, contextWindowDescriptor, agentDescriptor, fastModeDescriptor, thinkingDescriptor, effort, thinkingEnabled, contextWindow, ultrathinkPromptControlled, ultrathinkInBodyText, selectedAgent, selectedAgentLabel, modelIsUnavailable, }; } function getTraitsSectionVisibility(input: { provider: ProviderDriverKind; models: ReadonlyArray; model: string | null | undefined; prompt: string; modelOptions: ProviderOptions | null | undefined; allowPromptInjectedEffort?: boolean; planModeEnabled: boolean; }) { const selected = getSelectedTraits( input.provider, input.models, input.model, input.prompt, input.modelOptions, input.allowPromptInjectedEffort ?? true, input.planModeEnabled, ); const showEffort = selected.primarySelectDescriptor !== null; const showThinking = selected.thinkingDescriptor !== null; const showFastMode = selected.fastModeDescriptor !== null; const showContextWindow = selected.contextWindowDescriptor !== null; const showAgent = selected.agentDescriptor !== null; return { ...selected, showEffort, showThinking, showFastMode, showContextWindow, showAgent, hasAnyControls: showEffort || showThinking || showFastMode || showContextWindow || showAgent || (selected.modelIsUnavailable && selected.descriptors.length > 0), }; } export function shouldRenderTraitsControls(input: { provider: ProviderDriverKind; models: ReadonlyArray; model: string | null | undefined; prompt: string; modelOptions: ProviderOptions | null | undefined; allowPromptInjectedEffort?: boolean; planModeEnabled: boolean; }): boolean { return getTraitsSectionVisibility(input).hasAnyControls; } export interface TraitsMenuContentProps { provider: ProviderDriverKind; instanceId?: ProviderInstanceId; models: ReadonlyArray; model: string | null | undefined; prompt: string; onPromptChange: (prompt: string) => void; modelOptions?: ProviderOptions | null | undefined; allowPromptInjectedEffort?: boolean; planModeEnabled: boolean; triggerVariant?: VariantProps["variant"]; triggerClassName?: string; isComposerOwned?: boolean; } export const TraitsMenuContent = memo(function TraitsMenuContentImpl({ provider, instanceId, models, model, prompt, onPromptChange, modelOptions, allowPromptInjectedEffort = true, planModeEnabled, ...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, { ...(instanceId ? { instanceId } : {}), model, persistSticky: true, }); }, [instanceId, model, persistence, provider, setProviderModelOptions], ); const { descriptors, selectDescriptors, booleanDescriptors, primarySelectDescriptor, ultrathinkPromptControlled, ultrathinkInBodyText, hasAnyControls, modelIsUnavailable, } = getTraitsSectionVisibility({ provider, models, model, prompt, modelOptions, allowPromptInjectedEffort, planModeEnabled, }); const updateDescriptors = (nextDescriptors: ReadonlyArray) => { updateModelOptions(buildProviderOptionSelectionsFromDescriptors(nextDescriptors)); }; const handleSelectChange = ( descriptor: Extract, value: string, ) => { if (!value) return; if (descriptor.promptInjectedValues?.includes(value)) { const nextPrompt = prompt.trim().length === 0 ? ULTRATHINK_PROMPT_PREFIX : applyClaudePromptEffortPrefix(prompt, "ultrathink"); onPromptChange(nextPrompt); return; } if (ultrathinkInBodyText && descriptor.id === primarySelectDescriptor?.id) return; if (ultrathinkPromptControlled && descriptor.id === primarySelectDescriptor?.id) { const stripped = prompt.replace(/^Ultrathink:\s*/i, ""); onPromptChange(stripped); } updateDescriptors(replaceDescriptorCurrentValue(descriptors, descriptor.id, value)); }; if (!hasAnyControls) { return null; } if (modelIsUnavailable) { return ( <> {descriptors.map((descriptor, index) => { const value = getProviderOptionCurrentLabel(descriptor); if (!value) return null; return (
{index > 0 ? : null}
{descriptor.label}
{value}
); })} ); } return ( <> {selectDescriptors.map((descriptor, index) => { const selectedValue = ultrathinkPromptControlled && descriptor.id === primarySelectDescriptor?.id ? "ultrathink" : (getDescriptorStringValue(descriptor) ?? ""); return (
{index > 0 ? : null}
{descriptor.label}
{ultrathinkInBodyText && descriptor.id === primarySelectDescriptor?.id ? (
Your prompt contains "ultrathink" in the text. Remove it to change this option.
) : null} handleSelectChange(descriptor, value)} > {descriptor.options.map((option) => ( {option.label} {option.isDefault ? ( <> {" "} ) : null} {option.description ? ( {option.description} ) : null} ))}
); })} {booleanDescriptors.map((descriptor, index) => { const selectedValue = descriptor.currentValue === true ? "on" : "off"; return (
{index > 0 || selectDescriptors.length > 0 ? : null}
{descriptor.label}
{ updateDescriptors( replaceDescriptorCurrentValue(descriptors, descriptor.id, value === "on"), ); }} > {(["on", "off"] as const).map((value) => ( {value === "on" ? "On" : "Off"} ))}
); })} ); }); /** * Build the traits trigger's text label plus whether the fast-mode bolt should * render. Claude and Cursor expose fast mode as a boolean, while Codex exposes * it through the Standard/Fast service tiers. In either form, fast mode is a * lightning bolt when on and nothing at all when off. The one exception is when * fast mode is the only trait, where a bare bolt (or bare chevron) would leave * the trigger unreadable. */ export function buildTraitsTriggerDisplay(input: { provider: ProviderDriverKind; descriptors: ReadonlyArray; primarySelectDescriptorId: string | null; ultrathinkPromptControlled: boolean; }): { label: string; showFastModeIcon: boolean } { let fastModeFallbackLabel: string | null = null; let fastModeEnabled = false; const labels: Array = []; for (const descriptor of input.descriptors) { if (descriptor.id === "fastMode" && descriptor.type === "boolean") { fastModeEnabled = descriptor.currentValue === true; fastModeFallbackLabel = fastModeEnabled ? "Fast" : "Normal"; continue; } if ( input.provider === "codex" && descriptor.id === "serviceTier" && descriptor.type === "select" ) { const currentValue = getProviderOptionCurrentValue(descriptor); const fastTier = descriptor.options.find(({ label }) => label === "Fast"); if (fastTier && (currentValue === "default" || currentValue === fastTier.id)) { fastModeEnabled = currentValue === fastTier.id; fastModeFallbackLabel = descriptor.options.find(({ id }) => id === currentValue)?.label ?? (fastModeEnabled ? "Fast" : "Normal"); continue; } } const label = input.ultrathinkPromptControlled && descriptor.id === input.primarySelectDescriptorId ? "Ultrathink" : descriptor.type === "boolean" ? `${descriptor.label} ${descriptor.currentValue === true ? "On" : "Off"}` : getProviderOptionCurrentLabel(descriptor); if (typeof label === "string" && label.length > 0) { labels.push(label); } } // Only fall back to text when fast mode is genuinely the sole trait. Keying // off an empty label list alone would also catch descriptors that resolved to // no label at all, printing a bogus "Normal" for a model without fast mode. if (labels.length === 0 && fastModeFallbackLabel !== null) { return { label: fastModeFallbackLabel, showFastModeIcon: false }; } return { label: labels.join(" ยท "), showFastModeIcon: fastModeEnabled }; } export const TraitsPicker = memo(function TraitsPicker({ provider, instanceId, models, model, prompt, onPromptChange, modelOptions, allowPromptInjectedEffort = true, planModeEnabled, triggerVariant, triggerClassName, isComposerOwned, size = "sm", hidden = false, ...persistence }: TraitsMenuContentProps & TraitsPersistence & { size?: ComposerControlSize; hidden?: boolean; }) { const [isMenuOpen, setIsMenuOpen] = useComposerMenuState(hidden); const { descriptors, primarySelectDescriptor, ultrathinkPromptControlled } = getTraitsSectionVisibility({ provider, models, model, prompt, modelOptions, allowPromptInjectedEffort, planModeEnabled, }); if ( !shouldRenderTraitsControls({ provider, models, model, prompt, modelOptions, allowPromptInjectedEffort, planModeEnabled, }) ) { return null; } const { label: triggerLabel, showFastModeIcon } = buildTraitsTriggerDisplay({ provider, descriptors, primarySelectDescriptorId: primarySelectDescriptor?.id ?? null, ultrathinkPromptControlled, }); const fastModeIcon = showFastModeIcon ? ( <> Fast mode on ) : null; const isCodexStyle = provider === "codex"; return ( { setIsMenuOpen(open); }} > } > {isCodexStyle ? ( // The label truncates itself; clipping the wrapper too would cut off // the chevron, whose negative end margin overhangs the wrapper edge. {fastModeIcon} {triggerLabel} ) : ( <> {fastModeIcon} {triggerLabel} )} ); });