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<ServerProviderModel>; promptInjectionState?: ComposerPromptInjectionState; modelOptions: ReadonlyArray<ProviderOptionSelection> | null | undefined; planModeEnabled: boolean;}; export type ComposerPromptInjectionState = "none" | "ultrathink"; export type ComposerProviderState = { provider: ProviderDriverKind; promptEffort: string | null; modelOptionsForDispatch: ReadonlyArray<ProviderOptionSelection> | undefined; composerFrameClassName?: string; composerSurfaceClassName?: string; modelPickerIconClassName?: string;}; type TraitsRenderInput = { provider: ProviderDriverKind; instanceId?: ProviderInstanceId; threadRef?: ScopedThreadRef; draftId?: DraftId; model: string; models: ReadonlyArray<ServerProviderModel>; modelOptions: ReadonlyArray<ProviderOptionSelection> | undefined; prompt: string; onPromptChange: (prompt: string) => void; planModeEnabled: boolean; size?: ComposerControlSize; hidden?: boolean; triggerVariant?: VariantProps<typeof buttonVariants>["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<ProviderOptionSelection> | null | undefined,): ReadonlyArray<ProviderOptionSelection> | 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<ServerProviderModel>, model: string, provider: ProviderDriverKind, modelOptions: ReadonlyArray<ProviderOptionSelection> | null | undefined, planModeEnabled: boolean,): { caps: ModelCapabilities; selections: ReadonlyArray<ProviderOptionSelection> | 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 ( <Component provider={provider} {...(instanceId ? { instanceId } : {})} models={models} {...(threadRef ? { threadRef } : {})} {...(draftId ? { draftId } : {})} model={model} modelOptions={resolvedModelOptions} prompt={prompt} onPromptChange={onPromptChange} planModeEnabled={planModeEnabled} {...(size !== undefined ? { size } : {})} {...(hidden !== undefined ? { hidden } : {})} {...(triggerVariant !== undefined ? { triggerVariant } : {})} {...(triggerClassName !== undefined ? { triggerClassName } : {})} {...(isComposerOwned ? { isComposerOwned } : {})} /> );} export function renderProviderTraitsMenuContent(input: TraitsRenderInput): ReactNode { return renderTraitsControl(TraitsMenuContent, input);} export function renderProviderTraitsPicker(input: TraitsRenderInput): ReactNode { return renderTraitsControl(TraitsPicker, input);}
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<ServerProviderModel>;
promptInjectionState?: ComposerPromptInjectionState;
modelOptions: ReadonlyArray<ProviderOptionSelection> | null | undefined;
planModeEnabled: boolean;
};
export type ComposerPromptInjectionState = "none" | "ultrathink";
export type ComposerProviderState = {
provider: ProviderDriverKind;
promptEffort: string | null;
modelOptionsForDispatch: ReadonlyArray<ProviderOptionSelection> | undefined;
composerFrameClassName?: string;
composerSurfaceClassName?: string;
modelPickerIconClassName?: string;
};
type TraitsRenderInput = {
provider: ProviderDriverKind;
instanceId?: ProviderInstanceId;
threadRef?: ScopedThreadRef;
draftId?: DraftId;
model: string;
models: ReadonlyArray<ServerProviderModel>;
modelOptions: ReadonlyArray<ProviderOptionSelection> | undefined;
prompt: string;
onPromptChange: (prompt: string) => void;
planModeEnabled: boolean;
size?: ComposerControlSize;
hidden?: boolean;
triggerVariant?: VariantProps<typeof buttonVariants>["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<ProviderOptionSelection> | null | undefined,
): ReadonlyArray<ProviderOptionSelection> | 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<ServerProviderModel>,
model: string,
provider: ProviderDriverKind,
modelOptions: ReadonlyArray<ProviderOptionSelection> | null | undefined,
planModeEnabled: boolean,
): {
caps: ModelCapabilities;
selections: ReadonlyArray<ProviderOptionSelection> | 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 (
<Component
provider={provider}
{...(instanceId ? { instanceId } : {})}
models={models}
{...(threadRef ? { threadRef } : {})}
{...(draftId ? { draftId } : {})}
model={model}
modelOptions={resolvedModelOptions}
prompt={prompt}
onPromptChange={onPromptChange}
planModeEnabled={planModeEnabled}
{...(size !== undefined ? { size } : {})}
{...(hidden !== undefined ? { hidden } : {})}
{...(triggerVariant !== undefined ? { triggerVariant } : {})}
{...(triggerClassName !== undefined ? { triggerClassName } : {})}
{...(isComposerOwned ? { isComposerOwned } : {})}
/>
);
}
export function renderProviderTraitsMenuContent(input: TraitsRenderInput): ReactNode {
return renderTraitsControl(TraitsMenuContent, input);
}
export function renderProviderTraitsPicker(input: TraitsRenderInput): ReactNode {
return renderTraitsControl(TraitsPicker, input);
}