import { DEFAULT_MODEL_BY_PROVIDER, MODEL_SLUG_ALIASES_BY_PROVIDER, type ClaudeCodeEffort, type ClaudeModelOptions, type CodexModelOptions, type ModelCapabilities, type ModelSelection, type ProviderKind, } from "@t3tools/contracts"; export interface SelectableModelOption { slug: string; name: string; } // ── 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; } /** * Resolve a raw effort option against capabilities. * * Returns the effective effort value — the explicit value if supported and not * prompt-injected, otherwise the model's default. Returns `undefined` only * when the model has no effort levels at all. * * Prompt-injected efforts (e.g. "ultrathink") are excluded because they are * applied via prompt text, not the effort API parameter. */ export function resolveEffort( caps: ModelCapabilities, raw: string | null | undefined, ): string | undefined { const defaultValue = getDefaultEffort(caps); const trimmed = typeof raw === "string" ? raw.trim() : null; if ( trimmed && !caps.promptInjectedEffortLevels.includes(trimmed) && hasEffortLevel(caps, trimmed) ) { return trimmed; } return defaultValue ?? undefined; } // ── Context window helpers ─────────────────────────────────────────── /** Check whether a capabilities object includes a given context window value. */ export function hasContextWindowOption(caps: ModelCapabilities, value: string): boolean { return caps.contextWindowOptions.some((o) => o.value === value); } /** Return the default context window value, or `null` if none is defined. */ export function getDefaultContextWindow(caps: ModelCapabilities): string | null { return caps.contextWindowOptions.find((o) => o.isDefault)?.value ?? null; } /** * Resolve a raw `contextWindow` option against capabilities. * * Returns the effective context window value — the explicit value if supported, * otherwise the model's default. Returns `undefined` only when the model has * no context window options at all. * * Unlike effort levels (where the API has matching defaults), the context * window requires an explicit API suffix (e.g. `[1m]`), so we always preserve * the resolved value to avoid ambiguity between "user chose the default" and * "not specified". */ export function resolveContextWindow( caps: ModelCapabilities, raw: string | null | undefined, ): string | undefined { const defaultValue = getDefaultContextWindow(caps); if (!raw) return defaultValue ?? undefined; return hasContextWindowOption(caps, raw) ? raw : (defaultValue ?? undefined); } export function normalizeCodexModelOptionsWithCapabilities( caps: ModelCapabilities, modelOptions: CodexModelOptions | null | undefined, ): CodexModelOptions | undefined { const reasoningEffort = resolveEffort(caps, modelOptions?.reasoningEffort); const fastMode = caps.supportsFastMode ? modelOptions?.fastMode : undefined; const nextOptions: CodexModelOptions = { ...(reasoningEffort ? { reasoningEffort: reasoningEffort as CodexModelOptions["reasoningEffort"] } : {}), ...(fastMode !== undefined ? { fastMode } : {}), }; return Object.keys(nextOptions).length > 0 ? nextOptions : undefined; } export function normalizeClaudeModelOptionsWithCapabilities( caps: ModelCapabilities, modelOptions: ClaudeModelOptions | null | undefined, ): ClaudeModelOptions | undefined { const effort = resolveEffort(caps, modelOptions?.effort); const thinking = caps.supportsThinkingToggle ? modelOptions?.thinking : undefined; const fastMode = caps.supportsFastMode ? modelOptions?.fastMode : undefined; const contextWindow = resolveContextWindow(caps, modelOptions?.contextWindow); const nextOptions: ClaudeModelOptions = { ...(thinking !== undefined ? { thinking } : {}), ...(effort ? { effort: effort as ClaudeModelOptions["effort"] } : {}), ...(fastMode !== undefined ? { fastMode } : {}), ...(contextWindow !== undefined ? { contextWindow } : {}), }; return Object.keys(nextOptions).length > 0 ? nextOptions : undefined; } 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", ): string | 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; } export function resolveSelectableModel( provider: ProviderKind, value: string | null | undefined, options: ReadonlyArray, ): string | 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): string { const normalized = normalizeModelSlug(model, provider); if (!normalized) { return DEFAULT_MODEL_BY_PROVIDER[provider]; } return normalized; } export function resolveModelSlugForProvider( provider: ProviderKind, model: string | null | undefined, ): string { 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; } /** * Resolve the actual API model identifier from a model selection. * * Provider-aware: each provider can map `contextWindow` (or other options) * to whatever the API requires — a model-id suffix, a separate parameter, etc. * The canonical slug stored in the selection stays unchanged so the * capabilities system keeps working. * * Expects `contextWindow` to already be resolved (via `resolveContextWindow`) * to the effective value, not stripped to `undefined` for defaults. */ export function resolveApiModelId(modelSelection: ModelSelection): string { switch (modelSelection.provider) { case "claudeAgent": { switch (modelSelection.options?.contextWindow) { case "1m": return `${modelSelection.model}[1m]`; default: return modelSelection.model; } } default: { return modelSelection.model; } } } 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}`; }