import { DEFAULT_MODEL_BY_PROVIDER, MODEL_CAPABILITIES_INDEX, MODEL_OPTIONS_BY_PROVIDER, MODEL_SLUG_ALIASES_BY_PROVIDER, type ClaudeModelOptions, type ClaudeCodeEffort, type CodexModelOptions, type ModelCapabilities, type ModelSlug, type ProviderKind, CodexReasoningEffort, } from "@t3tools/contracts"; const MODEL_SLUG_SET_BY_PROVIDER: Record> = { claudeAgent: new Set(MODEL_OPTIONS_BY_PROVIDER.claudeAgent.map((option) => option.slug)), codex: new Set(MODEL_OPTIONS_BY_PROVIDER.codex.map((option) => option.slug)), }; export interface SelectableModelOption { slug: string; name: string; } export function getModelOptions(provider: ProviderKind = "codex") { return MODEL_OPTIONS_BY_PROVIDER[provider]; } export function getDefaultModel(provider: ProviderKind = "codex"): ModelSlug { return DEFAULT_MODEL_BY_PROVIDER[provider]; } // ── Effort helpers ──────────────────────────────────────────────────── /** Check whether a capabilities object includes a given effort value. */ export function hasEffortLevel(caps: ModelCapabilities, value: string): boolean { return caps.reasoningEffortLevels.some((l) => l.value === value); } /** Return the default effort value for a capabilities object, or null if none. */ export function getDefaultEffort(caps: ModelCapabilities): string | null { return caps.reasoningEffortLevels.find((l) => l.isDefault)?.value ?? null; } // ── Data-driven capability resolver ─────────────────────────────────── export function getModelCapabilities( provider: ProviderKind, model: string | null | undefined, ): ModelCapabilities { const slug = normalizeModelSlug(model, provider); if (slug && MODEL_CAPABILITIES_INDEX[provider]?.[slug]) { return MODEL_CAPABILITIES_INDEX[provider][slug]; } return { reasoningEffortLevels: [], supportsFastMode: false, supportsThinkingToggle: false, promptInjectedEffortLevels: [], }; } export function isClaudeUltrathinkPrompt(text: string | null | undefined): boolean { return typeof text === "string" && /\bultrathink\b/i.test(text); } export function normalizeModelSlug( model: string | null | undefined, provider: ProviderKind = "codex", ): ModelSlug | null { if (typeof model !== "string") { return null; } const trimmed = model.trim(); if (!trimmed) { return null; } const aliases = MODEL_SLUG_ALIASES_BY_PROVIDER[provider] as Record; const aliased = Object.prototype.hasOwnProperty.call(aliases, trimmed) ? aliases[trimmed] : undefined; return typeof aliased === "string" ? aliased : (trimmed as ModelSlug); } export function resolveSelectableModel( provider: ProviderKind, value: string | null | undefined, options: ReadonlyArray, ): ModelSlug | null { if (typeof value !== "string") { return null; } const trimmed = value.trim(); if (!trimmed) { return null; } const direct = options.find((option) => option.slug === trimmed); if (direct) { return direct.slug; } const byName = options.find((option) => option.name.toLowerCase() === trimmed.toLowerCase()); if (byName) { return byName.slug; } const normalized = normalizeModelSlug(trimmed, provider); if (!normalized) { return null; } const resolved = options.find((option) => option.slug === normalized); return resolved ? resolved.slug : null; } export function resolveModelSlug( model: string | null | undefined, provider: ProviderKind = "codex", ): ModelSlug { const normalized = normalizeModelSlug(model, provider); if (!normalized) { return DEFAULT_MODEL_BY_PROVIDER[provider]; } return MODEL_SLUG_SET_BY_PROVIDER[provider].has(normalized) ? normalized : DEFAULT_MODEL_BY_PROVIDER[provider]; } export function resolveModelSlugForProvider( provider: ProviderKind, model: string | null | undefined, ): ModelSlug { return resolveModelSlug(model, provider); } /** Trim a string, returning null for empty/missing values. */ export function trimOrNull(value: T | null | undefined): T | null { if (typeof value !== "string") return null; const trimmed = value.trim() as T; return trimmed || null; } export function normalizeCodexModelOptions( model: string | null | undefined, modelOptions: CodexModelOptions | null | undefined, ): CodexModelOptions | undefined { const caps = getModelCapabilities("codex", model); const defaultReasoningEffort = getDefaultEffort(caps) as CodexReasoningEffort; const reasoningEffort = trimOrNull(modelOptions?.reasoningEffort) ?? defaultReasoningEffort; const fastModeEnabled = modelOptions?.fastMode === true; const nextOptions: CodexModelOptions = { ...(reasoningEffort !== defaultReasoningEffort ? { reasoningEffort } : {}), ...(fastModeEnabled ? { fastMode: true } : {}), }; return Object.keys(nextOptions).length > 0 ? nextOptions : undefined; } export function normalizeClaudeModelOptions( model: string | null | undefined, modelOptions: ClaudeModelOptions | null | undefined, ): ClaudeModelOptions | undefined { const caps = getModelCapabilities("claudeAgent", model); const defaultReasoningEffort = getDefaultEffort(caps); const resolvedEffort = trimOrNull(modelOptions?.effort); const isPromptInjected = caps.promptInjectedEffortLevels.includes(resolvedEffort ?? ""); const effort = resolvedEffort && !isPromptInjected && hasEffortLevel(caps, resolvedEffort) && resolvedEffort !== defaultReasoningEffort ? resolvedEffort : undefined; const thinking = caps.supportsThinkingToggle && modelOptions?.thinking === false ? false : undefined; const fastMode = caps.supportsFastMode && modelOptions?.fastMode === true ? true : undefined; const nextOptions: ClaudeModelOptions = { ...(thinking === false ? { thinking: false } : {}), ...(effort ? { effort } : {}), ...(fastMode ? { fastMode: true } : {}), }; return Object.keys(nextOptions).length > 0 ? nextOptions : undefined; } export function applyClaudePromptEffortPrefix( text: string, effort: ClaudeCodeEffort | null | undefined, ): string { const trimmed = text.trim(); if (!trimmed) { return trimmed; } if (effort !== "ultrathink") { return trimmed; } if (trimmed.startsWith("Ultrathink:")) { return trimmed; } return `Ultrathink:\n${trimmed}`; }