241 lines · 7.9 KB · TypeScript
RawDownload
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<string, string>;  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<SelectableModelOption>,): 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<T extends string>(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}`;}