261 lines · 9.1 KB · TypeScript
RawDownload
import {  type CustomModelSetting,  type ModelCapabilities,  type ModelSelection,  ProviderDriverKind,  type ServerProviderModel,} from "@t3tools/contracts";import * as Option from "effect/Option";import {  getModelSelectionStringOptionValue,  getProviderOptionCurrentValue,  getProviderOptionDescriptors,  readCustomModelEntries,} from "@t3tools/shared/model";import { compareSemverVersions } from "@t3tools/shared/semver"; import {  type ClaudeCodeCompatibility,  type ClaudeCodeProfile,  decodeClaudeModelAdapter,  decodeClaudeProfileAdapter,} from "./ClaudeModelManifest.ts";import {  BUNDLED_MODEL_MANIFEST,  type ModelManifestData,  resolveProviderCatalog,} from "./ModelManifest.ts"; const CLAUDE = ProviderDriverKind.make("claudeAgent");const EMPTY_CAPABILITIES: ModelCapabilities = { optionDescriptors: [] }; export interface ClaudeCatalogModel {  readonly model: ServerProviderModel;  readonly runtime: ClaudeCodeProfile;  readonly compatibility: ClaudeCodeCompatibility;} export interface ClaudeModelCatalog {  readonly models: ReadonlyArray<ClaudeCatalogModel>;} function tryResolveClaudeModelCatalog(manifest: ModelManifestData): ClaudeModelCatalog | null {  const resolved = resolveProviderCatalog(manifest, CLAUDE);  if (!resolved) return null;   const models: Array<ClaudeCatalogModel> = [];  for (const entry of resolved.models) {    const profile = decodeClaudeProfileAdapter(entry.profileAdapter ?? {});    const adapter = decodeClaudeModelAdapter(entry.adapter ?? {});    if (Option.isNone(profile) || Option.isNone(adapter)) return null;    models.push({      model: entry.model,      runtime: profile.value.claudeCode ?? {},      compatibility: adapter.value.claudeCode ?? {},    });  }   return {    models,  };} export function resolveClaudeModelCatalog(manifest: ModelManifestData): ClaudeModelCatalog {  return (    tryResolveClaudeModelCatalog(manifest) ??    tryResolveClaudeModelCatalog(BUNDLED_MODEL_MANIFEST) ?? {      models: [],    }  );} export const BUNDLED_CLAUDE_MODEL_CATALOG = resolveClaudeModelCatalog(BUNDLED_MODEL_MANIFEST); /** * Scope the catalog to one instance's settings: custom model slugs stay opaque * (a built-in alias they shadow is dropped, canonical slugs and capabilities * are preserved), and custom entries that declare their own capabilities are * appended so the adapter resolves effort / fast mode / thinking against the * user's descriptors instead of the empty default. Custom entries carry no * runtime profile, so option values pass through to Claude Code verbatim. */export function scopeClaudeModelCatalog(  catalog: ClaudeModelCatalog,  customModels: ReadonlyArray<CustomModelSetting>,): ClaudeModelCatalog {  const customEntries = readCustomModelEntries(customModels);  if (customEntries.length === 0) return catalog;  const customAliases = new Set(customEntries.map((entry) => entry.slug.toLowerCase()));   const builtInModels = catalog.models.map((entry) => {    if (!entry.model.aliases?.some((alias) => customAliases.has(alias.toLowerCase()))) {      return entry;    }    return {      ...entry,      model: {        ...entry.model,        aliases: entry.model.aliases.filter((alias) => !customAliases.has(alias.toLowerCase())),      },    };  });  const builtInSlugs = new Set(builtInModels.map((entry) => entry.model.slug));  const customCatalogModels: Array<ClaudeCatalogModel> = [];  for (const entry of customEntries) {    if (!entry.capabilities || builtInSlugs.has(entry.slug)) continue;    customCatalogModels.push({      model: {        slug: entry.slug,        name: entry.name,        isCustom: true,        capabilities: entry.capabilities,      },      runtime: {},      compatibility: {},    });  }   return { models: [...builtInModels, ...customCatalogModels] };} function resolveClaudeCatalogModel(  catalog: ClaudeModelCatalog,  slugOrAlias: string | null | undefined,): ClaudeCatalogModel | undefined {  const value = slugOrAlias?.trim();  if (!value) return undefined;  return (    catalog.models.find((entry) => entry.model.slug === value) ??    catalog.models.find((entry) =>      entry.model.aliases?.some((alias) => alias.toLowerCase() === value.toLowerCase()),    )  );} export function resolveClaudeModelSlug(catalog: ClaudeModelCatalog, slugOrAlias: string): string {  return resolveClaudeCatalogModel(catalog, slugOrAlias)?.model.slug ?? slugOrAlias;} export function getClaudeCatalogModelCapabilities(  catalog: ClaudeModelCatalog,  slugOrAlias: string | null | undefined,): ModelCapabilities {  return resolveClaudeCatalogModel(catalog, slugOrAlias)?.model.capabilities ?? EMPTY_CAPABILITIES;} function isVersionSupported(  compatibility: ClaudeCodeCompatibility,  version: string | null | undefined,): boolean {  if (!compatibility.minVersion && !compatibility.maxVersionExclusive) return true;  if (!version) return false;  if (compatibility.minVersion && compareSemverVersions(version, compatibility.minVersion) < 0) {    return false;  }  return !(    compatibility.maxVersionExclusive &&    compareSemverVersions(version, compatibility.maxVersionExclusive) >= 0  );} export function resolveClaudeModelsForVersion(  catalog: ClaudeModelCatalog,  version: string | null | undefined,): ReadonlyArray<ClaudeCatalogModel["model"]> {  return catalog.models    .filter((entry) => isVersionSupported(entry.compatibility, version))    .map((entry) => entry.model);} export function formatClaudeVersionUpgradeMessage(  catalog: ClaudeModelCatalog,  version: string | null,): string | undefined {  const unavailable = catalog.models    .filter(      (entry) =>        entry.compatibility.minVersion &&        (!version || compareSemverVersions(version, entry.compatibility.minVersion) < 0),    )    .toSorted((left, right) =>      compareSemverVersions(left.compatibility.minVersion!, right.compatibility.minVersion!),    )[0];  if (!unavailable?.compatibility.minVersion) return undefined;  const versionLabel = version ? `v${version}` : "the installed version";  return `Claude Code ${versionLabel} is too old for ${unavailable.model.name}. Upgrade to v${unavailable.compatibility.minVersion} or newer to access it.`;} export function resolveClaudeCatalogEffort(  catalog: ClaudeModelCatalog,  model: string | null | undefined,  raw: string | null | undefined,): string | undefined {  const caps = getClaudeCatalogModelCapabilities(catalog, model);  const descriptors = getProviderOptionDescriptors({    caps,    ...(raw ? { selections: [{ id: "effort", value: raw }] } : {}),  });  const descriptor = descriptors.find((candidate) => candidate.id === "effort");  const value = getProviderOptionCurrentValue(descriptor);  return typeof value === "string" ? value : undefined;} export function normalizeClaudeCatalogEffort(  catalog: ClaudeModelCatalog,  effort: string | null | undefined,  model: string | null | undefined,): string | undefined {  if (!effort) return undefined;  const effortMap = resolveClaudeCatalogModel(catalog, model)?.runtime.effortMap;  if (!effortMap || !Object.prototype.hasOwnProperty.call(effortMap, effort)) return effort;  return effortMap[effort] ?? undefined;} export function isClaudeCatalogUltracodeEffort(effort: string | null | undefined): boolean {  return effort === "ultracode";} function resolveClaudeCatalogContextWindow(  catalog: ClaudeModelCatalog,  modelSelection: ModelSelection | undefined,): string | undefined {  const caps = getClaudeCatalogModelCapabilities(catalog, modelSelection?.model);  const raw = getModelSelectionStringOptionValue(modelSelection, "contextWindow");  const descriptors = getProviderOptionDescriptors({    caps,    ...(raw ? { selections: [{ id: "contextWindow", value: raw }] } : {}),  });  const descriptor = descriptors.find((candidate) => candidate.id === "contextWindow");  const value = getProviderOptionCurrentValue(descriptor);  return typeof value === "string" ? value : undefined;} export function resolveClaudeCatalogApiModelId(  catalog: ClaudeModelCatalog,  modelSelection: ModelSelection,): string {  const entry = resolveClaudeCatalogModel(catalog, modelSelection.model);  const slug = entry?.model.slug ?? modelSelection.model;  const descriptors = getProviderOptionDescriptors({    caps: entry?.model.capabilities ?? EMPTY_CAPABILITIES,    selections: modelSelection.options,  });  for (const [optionId, suffixes] of Object.entries(entry?.runtime.modelSuffixes ?? {})) {    const value = getProviderOptionCurrentValue(      descriptors.find((descriptor) => descriptor.id === optionId),    );    if (typeof value === "string" && suffixes[value]) return `${slug}${suffixes[value]}`;  }  return slug;} export function resolveClaudeCatalogContextWindowTokens(  catalog: ClaudeModelCatalog,  modelSelection: ModelSelection | undefined,): number | undefined {  const entry = resolveClaudeCatalogModel(catalog, modelSelection?.model);  if (!entry) return undefined;  if (entry.runtime.fixedContextWindowTokens) return entry.runtime.fixedContextWindowTokens;  const contextWindow = resolveClaudeCatalogContextWindow(catalog, modelSelection);  return contextWindow ? entry.runtime.contextWindowTokens?.[contextWindow] : undefined;}