@dreb/ai 2.43.0 → 2.43.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -794,7 +794,7 @@ interface OpenAICompletionsCompat {
794
794
  requiresToolResultName?: boolean; // Whether tool results require the `name` field (default: false)
795
795
  requiresAssistantAfterToolResult?: boolean; // Whether tool results must be followed by an assistant message (default: false)
796
796
  requiresThinkingAsText?: boolean; // Whether thinking blocks must be converted to text (default: false)
797
- thinkingFormat?: 'openai' | 'zai' | 'qwen'; // Format for reasoning param: 'openai' uses reasoning_effort, 'zai' uses thinking: { type: "enabled" }, 'qwen' uses enable_thinking: boolean (default: openai)
797
+ thinkingFormat?: 'openai' | 'openrouter' | 'zai' | 'qwen' | 'qwen-chat-template' | 'kimi'; // Format for reasoning param: 'openai' uses reasoning_effort, 'openrouter' uses reasoning: { effort }, 'zai' uses top-level enable_thinking: boolean, 'qwen' uses top-level enable_thinking: boolean, 'qwen-chat-template' uses chat_template_kwargs.enable_thinking, and 'kimi' uses nested thinking: { type, effort? } plus prompt_cache_key (default: openai)
798
798
  openRouterRouting?: OpenRouterRouting; // OpenRouter routing preferences (default: {})
799
799
  vercelGatewayRouting?: VercelGatewayRouting; // Vercel AI Gateway routing preferences (default: {})
800
800
  }
@@ -830,16 +830,27 @@ await streamAnthropic(claude, context, options);
830
830
 
831
831
  ## Cross-Provider Handoffs
832
832
 
833
- The library supports seamless handoffs between different LLM providers within the same conversation. This allows you to switch models mid-conversation while preserving context, including thinking blocks, tool calls, and tool results.
833
+ The library can prepare one conversation for another model without changing the stored `Context`. User messages, visible assistant text, tool calls, and tool results remain available, while model-bound tool signatures and IDs are normalized for the destination. Provider-specific reasoning state is handled conservatively.
834
834
 
835
- ### How It Works
835
+ ### Reasoning State Compatibility
836
836
 
837
- When messages from one provider are sent to a different provider, the library automatically transforms them for compatibility:
837
+ - **Exact model:** signed, encrypted, and redacted reasoning state is replayed unchanged.
838
+ - **Compatible model switch:** structured reasoning is preserved only when source and target share a provider, both use `openai-completions`, the destination accepts structured reasoning, and the source uses a recognized plain field: `reasoning_content`, `reasoning`, or `reasoning_text`.
839
+ - **Other readable reasoning:** it is retained as labelled plaintext inside `<reformatted-pre-switch-reasoning>` markers, with incompatible protocol metadata stripped.
840
+ - **Opaque state:** redacted or encrypted-only reasoning is omitted for incompatible targets.
838
841
 
839
- - **User and tool result messages** are passed through unchanged
840
- - **Assistant messages from the same provider/API** are preserved as-is
841
- - **Assistant messages from different providers** have their thinking blocks converted to text with `<thinking>` tags
842
- - **Tool calls and regular text** are preserved unchanged
842
+ Compatibility depends on provider, API, and signature behavior. This includes custom models: two models at the same endpoint are not compatible merely because their IDs or URLs match; they must share the configured provider identity as well.
843
+
844
+ The transformation applies only to the outbound request. It does not mutate `Context.messages`, so a later switch back to the original model can replay its original reasoning state unless that history has been compacted or pruned by the caller.
845
+
846
+ ### Examples
847
+
848
+ | Source and target | Outbound reasoning state |
849
+ |---|---|
850
+ | The same model | Original signed, encrypted, or redacted state is replayed unchanged. |
851
+ | Two models under the same custom provider, both using `openai-completions`, where the destination accepts the source's `reasoning_content`, `reasoning`, or `reasoning_text` field | Recognized plain structured reasoning is preserved. |
852
+ | Different providers or APIs with readable reasoning | Reasoning is sent as labelled plaintext in `<reformatted-pre-switch-reasoning>` markers. |
853
+ | An incompatible target with redacted or encrypted-only reasoning | Opaque reasoning state is omitted. |
843
854
 
844
855
  ### Example: Multi-Provider Conversation
845
856
 
@@ -858,32 +869,17 @@ const claudeResponse = await complete(claude, context, {
858
869
  });
859
870
  context.messages.push(claudeResponse);
860
871
 
861
- // Switch to GPT-5 - it will see Claude's thinking as <thinking> tagged text
872
+ // Switch to GPT-5. Readable Claude reasoning is reformatted for this outbound request.
862
873
  const gpt5 = getModel('openai', 'gpt-5-mini');
863
874
  context.messages.push({ role: 'user', content: 'Is that calculation correct?' });
864
875
  const gptResponse = await complete(gpt5, context);
865
876
  context.messages.push(gptResponse);
866
877
 
867
- // Switch to Gemini
868
- const gemini = getModel('google', 'gemini-2.5-flash');
878
+ // Switching back to Claude can use the original Claude state in context.
869
879
  context.messages.push({ role: 'user', content: 'What was the original question?' });
870
- const geminiResponse = await complete(gemini, context);
880
+ const finalClaudeResponse = await complete(claude, context);
871
881
  ```
872
882
 
873
- ### Provider Compatibility
874
-
875
- All providers can handle messages from other providers, including:
876
- - Text content
877
- - Tool calls and tool results (including images in tool results)
878
- - Thinking/reasoning blocks (transformed to tagged text for cross-provider compatibility)
879
- - Aborted messages with partial content
880
-
881
- This enables flexible workflows where you can:
882
- - Start with a fast model for initial responses
883
- - Switch to a more capable model for complex reasoning
884
- - Use specialized models for specific tasks
885
- - Maintain conversation continuity across provider outages
886
-
887
883
  ## Context Serialization
888
884
 
889
885
  The `Context` object can be easily serialized and deserialized using standard JSON methods, making it simple to persist conversations, implement chat history, or transfer contexts between services:
package/dist/models.d.ts CHANGED
@@ -47,6 +47,7 @@ export declare function calculateCost<TApi extends Api>(model: Model<TApi>, usag
47
47
  * Supported today:
48
48
  * - GPT-5.2 through GPT-5.6 model families
49
49
  * - Opus 4.6+ models (xhigh maps to adaptive effort "max" on Anthropic-compatible providers)
50
+ * - Kimi Code K3 (xhigh maps to its advertised "max" effort)
50
51
  */
51
52
  export declare function supportsXhigh<TApi extends Api>(model: Model<TApi>): boolean;
52
53
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,EAAE,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAanE,KAAK,QAAQ,CACZ,SAAS,SAAS,aAAa,EAC/B,QAAQ,SAAS,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC,IAC9C,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,SAAS;IAAE,GAAG,EAAE,MAAM,IAAI,CAAA;CAAE,GAAG,CAAC,IAAI,SAAS,GAAG,GAAG,IAAI,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC;AAEjH,wBAAgB,QAAQ,CAAC,SAAS,SAAS,aAAa,EAAE,QAAQ,SAAS,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC,EAC1G,QAAQ,EAAE,SAAS,EACnB,OAAO,EAAE,QAAQ,GACf,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAGtC;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAGhD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CA8BnF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CA4B7F;AAED,wBAAgB,YAAY,IAAI,aAAa,EAAE,CAE9C;AAED,wBAAgB,SAAS,CAAC,SAAS,SAAS,aAAa,EACxD,QAAQ,EAAE,SAAS,GACjB,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAGhE;AAED,wBAAgB,aAAa,CAAC,IAAI,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAO/F;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAmB3E;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAGtF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,SAAS,GAAG,EAC9C,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,EACjC,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,GAC/B,OAAO,CAGT","sourcesContent":["import { MODELS } from \"./models.generated.js\";\nimport type { Api, KnownProvider, Model, Usage } from \"./types.js\";\n\nconst modelRegistry: Map<string, Map<string, Model<Api>>> = new Map();\n\n// Initialize registry from MODELS on module load\nfor (const [provider, models] of Object.entries(MODELS)) {\n\tconst providerModels = new Map<string, Model<Api>>();\n\tfor (const [id, model] of Object.entries(models)) {\n\t\tproviderModels.set(id, model as Model<Api>);\n\t}\n\tmodelRegistry.set(provider, providerModels);\n}\n\ntype ModelApi<\n\tTProvider extends KnownProvider,\n\tTModelId extends keyof (typeof MODELS)[TProvider],\n> = (typeof MODELS)[TProvider][TModelId] extends { api: infer TApi } ? (TApi extends Api ? TApi : never) : never;\n\nexport function getModel<TProvider extends KnownProvider, TModelId extends keyof (typeof MODELS)[TProvider]>(\n\tprovider: TProvider,\n\tmodelId: TModelId,\n): Model<ModelApi<TProvider, TModelId>> {\n\tconst providerModels = modelRegistry.get(provider);\n\treturn providerModels?.get(modelId as string) as Model<ModelApi<TProvider, TModelId>>;\n}\n\n/**\n * Check if a model ID looks like an alias (no date suffix).\n * Aliases are preferred over dated versions when fuzzy matching.\n *\n * IDs ending with `-latest` are treated as aliases.\n * IDs ending with a date pattern (`-YYYYMMDD`) are treated as dated versions.\n */\nexport function isModelAlias(id: string): boolean {\n\tif (id.endsWith(\"-latest\")) return true;\n\treturn !/-\\d{8}$/.test(id);\n}\n\n/**\n * Find a model by fuzzy matching against the provider's registered models.\n *\n * Resolution order:\n * 1. Exact match by provider + model ID (via registry Map.get)\n * 2. Case-insensitive substring match against model ID and display name\n * 3. Among matches, prefer aliases (non-dated IDs) over dated versions\n * 4. Among ties, pick the lexicographically highest (latest) ID\n *\n * This is the same matching logic used by the CLI, subagent model resolution,\n * and interactive mode — centralised here so tests can exercise the real path.\n *\n * @example\n * findModel(\"anthropic\", \"sonnet\") // → latest claude-sonnet alias\n * findModel(\"anthropic\", \"haiku\") // → latest claude-haiku alias\n * findModel(\"openai\", \"gpt-5\") // → latest gpt-5 alias\n */\nexport function findModel(provider: string, pattern: string): Model<Api> | undefined {\n\tconst providerModels = modelRegistry.get(provider);\n\tif (!providerModels) return undefined;\n\n\t// Try exact match first\n\tconst exact = providerModels.get(pattern);\n\tif (exact) return exact;\n\n\t// Substring match (case-insensitive)\n\tconst normalizedPattern = pattern.toLowerCase();\n\tconst matches = Array.from(providerModels.values()).filter(\n\t\t(m) => m.id.toLowerCase().includes(normalizedPattern) || m.name?.toLowerCase().includes(normalizedPattern),\n\t);\n\n\tif (matches.length === 0) return undefined;\n\tif (matches.length === 1) return matches[0];\n\n\t// Multiple matches — separate into aliases and dated versions\n\tconst aliases = matches.filter((m) => isModelAlias(m.id));\n\tconst datedVersions = matches.filter((m) => !isModelAlias(m.id));\n\n\tif (aliases.length > 0) {\n\t\t// Prefer alias — if multiple, pick the lexicographically highest\n\t\taliases.sort((a, b) => b.id.localeCompare(a.id));\n\t\treturn aliases[0];\n\t}\n\n\t// All dated — prefer the latest\n\tdatedVersions.sort((a, b) => b.id.localeCompare(a.id));\n\treturn datedVersions[0];\n}\n\n/**\n * Find a model by fuzzy matching against a flat array of models.\n * Same algorithm as findModel() but operates on an arbitrary model list\n * instead of the built-in registry.\n *\n * Used by model-resolver.ts and other code that manages its own model lists.\n */\nexport function findModelInList(pattern: string, models: Model<Api>[]): Model<Api> | undefined {\n\tif (models.length === 0) return undefined;\n\n\tconst normalizedPattern = pattern.toLowerCase();\n\n\t// Exact ID match (case-insensitive)\n\tconst exactById = models.find((m) => m.id.toLowerCase() === normalizedPattern);\n\tif (exactById) return exactById;\n\n\t// Substring match (case-insensitive)\n\tconst matches = models.filter(\n\t\t(m) => m.id.toLowerCase().includes(normalizedPattern) || m.name?.toLowerCase().includes(normalizedPattern),\n\t);\n\n\tif (matches.length === 0) return undefined;\n\tif (matches.length === 1) return matches[0];\n\n\t// Multiple matches — separate into aliases and dated versions\n\tconst aliases = matches.filter((m) => isModelAlias(m.id));\n\tconst datedVersions = matches.filter((m) => !isModelAlias(m.id));\n\n\tif (aliases.length > 0) {\n\t\taliases.sort((a, b) => b.id.localeCompare(a.id));\n\t\treturn aliases[0];\n\t}\n\n\tdatedVersions.sort((a, b) => b.id.localeCompare(a.id));\n\treturn datedVersions[0];\n}\n\nexport function getProviders(): KnownProvider[] {\n\treturn Array.from(modelRegistry.keys()) as KnownProvider[];\n}\n\nexport function getModels<TProvider extends KnownProvider>(\n\tprovider: TProvider,\n): Model<ModelApi<TProvider, keyof (typeof MODELS)[TProvider]>>[] {\n\tconst models = modelRegistry.get(provider);\n\treturn models ? (Array.from(models.values()) as Model<ModelApi<TProvider, keyof (typeof MODELS)[TProvider]>>[]) : [];\n}\n\nexport function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage): Usage[\"cost\"] {\n\tusage.cost.input = (model.cost.input / 1000000) * usage.input;\n\tusage.cost.output = (model.cost.output / 1000000) * usage.output;\n\tusage.cost.cacheRead = (model.cost.cacheRead / 1000000) * usage.cacheRead;\n\tusage.cost.cacheWrite = (model.cost.cacheWrite / 1000000) * usage.cacheWrite;\n\tusage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;\n\treturn usage.cost;\n}\n\n/**\n * Check if a model supports xhigh thinking level.\n *\n * Supported today:\n * - GPT-5.2 through GPT-5.6 model families\n * - Opus 4.6+ models (xhigh maps to adaptive effort \"max\" on Anthropic-compatible providers)\n */\nexport function supportsXhigh<TApi extends Api>(model: Model<TApi>): boolean {\n\tif (\n\t\tmodel.id.includes(\"gpt-5.2\") ||\n\t\tmodel.id.includes(\"gpt-5.3\") ||\n\t\tmodel.id.includes(\"gpt-5.4\") ||\n\t\tmodel.id.includes(\"gpt-5.5\") ||\n\t\tmodel.id.includes(\"gpt-5.6\")\n\t) {\n\t\treturn true;\n\t}\n\n\t// Opus 4.6+ supports xhigh (adaptive effort \"max\").\n\t// Match any opus-4-N or opus-4.N where N >= 6 (1-2 digit minor version, not date suffixes).\n\tconst opusMatch = model.id.match(/opus-4[.-](\\d{1,2})(?!\\d)/);\n\tif (opusMatch && Number.parseInt(opusMatch[1], 10) >= 6) {\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/**\n * Check if a model uses adaptive thinking (Opus 4.6+, Sonnet 4.6+), where the\n * `thinkingDisplay` option is honored. Mirrors the per-provider internal checks.\n */\nexport function supportsAdaptiveThinking<TApi extends Api>(model: Model<TApi>): boolean {\n\tconst m = model.id.match(/(opus|sonnet)-4[.-](\\d{1,2})(?!\\d)/);\n\treturn m != null && Number.parseInt(m[2], 10) >= 6;\n}\n\n/**\n * Check if two models are equal by comparing both their id and provider.\n * Returns false if either model is null or undefined.\n */\nexport function modelsAreEqual<TApi extends Api>(\n\ta: Model<TApi> | null | undefined,\n\tb: Model<TApi> | null | undefined,\n): boolean {\n\tif (!a || !b) return false;\n\treturn a.id === b.id && a.provider === b.provider;\n}\n"]}
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,EAAE,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAanE,KAAK,QAAQ,CACZ,SAAS,SAAS,aAAa,EAC/B,QAAQ,SAAS,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC,IAC9C,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,SAAS;IAAE,GAAG,EAAE,MAAM,IAAI,CAAA;CAAE,GAAG,CAAC,IAAI,SAAS,GAAG,GAAG,IAAI,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC;AAEjH,wBAAgB,QAAQ,CAAC,SAAS,SAAS,aAAa,EAAE,QAAQ,SAAS,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC,EAC1G,QAAQ,EAAE,SAAS,EACnB,OAAO,EAAE,QAAQ,GACf,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAGtC;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAGhD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CA8BnF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CA4B7F;AAED,wBAAgB,YAAY,IAAI,aAAa,EAAE,CAE9C;AAED,wBAAgB,SAAS,CAAC,SAAS,SAAS,aAAa,EACxD,QAAQ,EAAE,SAAS,GACjB,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAGhE;AAED,wBAAgB,aAAa,CAAC,IAAI,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAO/F;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAoB3E;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAGtF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,SAAS,GAAG,EAC9C,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,EACjC,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,GAC/B,OAAO,CAGT","sourcesContent":["import { MODELS } from \"./models.generated.js\";\nimport type { Api, KnownProvider, Model, Usage } from \"./types.js\";\n\nconst modelRegistry: Map<string, Map<string, Model<Api>>> = new Map();\n\n// Initialize registry from MODELS on module load\nfor (const [provider, models] of Object.entries(MODELS)) {\n\tconst providerModels = new Map<string, Model<Api>>();\n\tfor (const [id, model] of Object.entries(models)) {\n\t\tproviderModels.set(id, model as Model<Api>);\n\t}\n\tmodelRegistry.set(provider, providerModels);\n}\n\ntype ModelApi<\n\tTProvider extends KnownProvider,\n\tTModelId extends keyof (typeof MODELS)[TProvider],\n> = (typeof MODELS)[TProvider][TModelId] extends { api: infer TApi } ? (TApi extends Api ? TApi : never) : never;\n\nexport function getModel<TProvider extends KnownProvider, TModelId extends keyof (typeof MODELS)[TProvider]>(\n\tprovider: TProvider,\n\tmodelId: TModelId,\n): Model<ModelApi<TProvider, TModelId>> {\n\tconst providerModels = modelRegistry.get(provider);\n\treturn providerModels?.get(modelId as string) as Model<ModelApi<TProvider, TModelId>>;\n}\n\n/**\n * Check if a model ID looks like an alias (no date suffix).\n * Aliases are preferred over dated versions when fuzzy matching.\n *\n * IDs ending with `-latest` are treated as aliases.\n * IDs ending with a date pattern (`-YYYYMMDD`) are treated as dated versions.\n */\nexport function isModelAlias(id: string): boolean {\n\tif (id.endsWith(\"-latest\")) return true;\n\treturn !/-\\d{8}$/.test(id);\n}\n\n/**\n * Find a model by fuzzy matching against the provider's registered models.\n *\n * Resolution order:\n * 1. Exact match by provider + model ID (via registry Map.get)\n * 2. Case-insensitive substring match against model ID and display name\n * 3. Among matches, prefer aliases (non-dated IDs) over dated versions\n * 4. Among ties, pick the lexicographically highest (latest) ID\n *\n * This is the same matching logic used by the CLI, subagent model resolution,\n * and interactive mode — centralised here so tests can exercise the real path.\n *\n * @example\n * findModel(\"anthropic\", \"sonnet\") // → latest claude-sonnet alias\n * findModel(\"anthropic\", \"haiku\") // → latest claude-haiku alias\n * findModel(\"openai\", \"gpt-5\") // → latest gpt-5 alias\n */\nexport function findModel(provider: string, pattern: string): Model<Api> | undefined {\n\tconst providerModels = modelRegistry.get(provider);\n\tif (!providerModels) return undefined;\n\n\t// Try exact match first\n\tconst exact = providerModels.get(pattern);\n\tif (exact) return exact;\n\n\t// Substring match (case-insensitive)\n\tconst normalizedPattern = pattern.toLowerCase();\n\tconst matches = Array.from(providerModels.values()).filter(\n\t\t(m) => m.id.toLowerCase().includes(normalizedPattern) || m.name?.toLowerCase().includes(normalizedPattern),\n\t);\n\n\tif (matches.length === 0) return undefined;\n\tif (matches.length === 1) return matches[0];\n\n\t// Multiple matches — separate into aliases and dated versions\n\tconst aliases = matches.filter((m) => isModelAlias(m.id));\n\tconst datedVersions = matches.filter((m) => !isModelAlias(m.id));\n\n\tif (aliases.length > 0) {\n\t\t// Prefer alias — if multiple, pick the lexicographically highest\n\t\taliases.sort((a, b) => b.id.localeCompare(a.id));\n\t\treturn aliases[0];\n\t}\n\n\t// All dated — prefer the latest\n\tdatedVersions.sort((a, b) => b.id.localeCompare(a.id));\n\treturn datedVersions[0];\n}\n\n/**\n * Find a model by fuzzy matching against a flat array of models.\n * Same algorithm as findModel() but operates on an arbitrary model list\n * instead of the built-in registry.\n *\n * Used by model-resolver.ts and other code that manages its own model lists.\n */\nexport function findModelInList(pattern: string, models: Model<Api>[]): Model<Api> | undefined {\n\tif (models.length === 0) return undefined;\n\n\tconst normalizedPattern = pattern.toLowerCase();\n\n\t// Exact ID match (case-insensitive)\n\tconst exactById = models.find((m) => m.id.toLowerCase() === normalizedPattern);\n\tif (exactById) return exactById;\n\n\t// Substring match (case-insensitive)\n\tconst matches = models.filter(\n\t\t(m) => m.id.toLowerCase().includes(normalizedPattern) || m.name?.toLowerCase().includes(normalizedPattern),\n\t);\n\n\tif (matches.length === 0) return undefined;\n\tif (matches.length === 1) return matches[0];\n\n\t// Multiple matches — separate into aliases and dated versions\n\tconst aliases = matches.filter((m) => isModelAlias(m.id));\n\tconst datedVersions = matches.filter((m) => !isModelAlias(m.id));\n\n\tif (aliases.length > 0) {\n\t\taliases.sort((a, b) => b.id.localeCompare(a.id));\n\t\treturn aliases[0];\n\t}\n\n\tdatedVersions.sort((a, b) => b.id.localeCompare(a.id));\n\treturn datedVersions[0];\n}\n\nexport function getProviders(): KnownProvider[] {\n\treturn Array.from(modelRegistry.keys()) as KnownProvider[];\n}\n\nexport function getModels<TProvider extends KnownProvider>(\n\tprovider: TProvider,\n): Model<ModelApi<TProvider, keyof (typeof MODELS)[TProvider]>>[] {\n\tconst models = modelRegistry.get(provider);\n\treturn models ? (Array.from(models.values()) as Model<ModelApi<TProvider, keyof (typeof MODELS)[TProvider]>>[]) : [];\n}\n\nexport function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage): Usage[\"cost\"] {\n\tusage.cost.input = (model.cost.input / 1000000) * usage.input;\n\tusage.cost.output = (model.cost.output / 1000000) * usage.output;\n\tusage.cost.cacheRead = (model.cost.cacheRead / 1000000) * usage.cacheRead;\n\tusage.cost.cacheWrite = (model.cost.cacheWrite / 1000000) * usage.cacheWrite;\n\tusage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;\n\treturn usage.cost;\n}\n\n/**\n * Check if a model supports xhigh thinking level.\n *\n * Supported today:\n * - GPT-5.2 through GPT-5.6 model families\n * - Opus 4.6+ models (xhigh maps to adaptive effort \"max\" on Anthropic-compatible providers)\n * - Kimi Code K3 (xhigh maps to its advertised \"max\" effort)\n */\nexport function supportsXhigh<TApi extends Api>(model: Model<TApi>): boolean {\n\tif (model.provider === \"kimi-coding-oauth\" && model.id === \"k3\") return true;\n\tif (\n\t\tmodel.id.includes(\"gpt-5.2\") ||\n\t\tmodel.id.includes(\"gpt-5.3\") ||\n\t\tmodel.id.includes(\"gpt-5.4\") ||\n\t\tmodel.id.includes(\"gpt-5.5\") ||\n\t\tmodel.id.includes(\"gpt-5.6\")\n\t) {\n\t\treturn true;\n\t}\n\n\t// Opus 4.6+ supports xhigh (adaptive effort \"max\").\n\t// Match any opus-4-N or opus-4.N where N >= 6 (1-2 digit minor version, not date suffixes).\n\tconst opusMatch = model.id.match(/opus-4[.-](\\d{1,2})(?!\\d)/);\n\tif (opusMatch && Number.parseInt(opusMatch[1], 10) >= 6) {\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/**\n * Check if a model uses adaptive thinking (Opus 4.6+, Sonnet 4.6+), where the\n * `thinkingDisplay` option is honored. Mirrors the per-provider internal checks.\n */\nexport function supportsAdaptiveThinking<TApi extends Api>(model: Model<TApi>): boolean {\n\tconst m = model.id.match(/(opus|sonnet)-4[.-](\\d{1,2})(?!\\d)/);\n\treturn m != null && Number.parseInt(m[2], 10) >= 6;\n}\n\n/**\n * Check if two models are equal by comparing both their id and provider.\n * Returns false if either model is null or undefined.\n */\nexport function modelsAreEqual<TApi extends Api>(\n\ta: Model<TApi> | null | undefined,\n\tb: Model<TApi> | null | undefined,\n): boolean {\n\tif (!a || !b) return false;\n\treturn a.id === b.id && a.provider === b.provider;\n}\n"]}
@@ -5552,6 +5552,34 @@ export declare const MODELS: {
5552
5552
  };
5553
5553
  };
5554
5554
  readonly "kimi-coding-oauth": {
5555
+ readonly k3: {
5556
+ id: string;
5557
+ name: string;
5558
+ api: "openai-completions";
5559
+ provider: string;
5560
+ baseUrl: string;
5561
+ compat: {
5562
+ thinkingFormat: "kimi";
5563
+ supportsDeveloperRole: false;
5564
+ reasoningEffortMap: {
5565
+ minimal: string;
5566
+ low: string;
5567
+ medium: string;
5568
+ high: string;
5569
+ xhigh: string;
5570
+ };
5571
+ };
5572
+ reasoning: true;
5573
+ input: ("image" | "text")[];
5574
+ cost: {
5575
+ input: number;
5576
+ output: number;
5577
+ cacheRead: number;
5578
+ cacheWrite: number;
5579
+ };
5580
+ contextWindow: number;
5581
+ maxTokens: number;
5582
+ };
5555
5583
  readonly "kimi-for-coding": {
5556
5584
  id: string;
5557
5585
  name: string;
@@ -5561,6 +5589,41 @@ export declare const MODELS: {
5561
5589
  compat: {
5562
5590
  thinkingFormat: "kimi";
5563
5591
  supportsDeveloperRole: false;
5592
+ reasoningEffortMap: {
5593
+ minimal: string;
5594
+ low: string;
5595
+ medium: string;
5596
+ high: string;
5597
+ xhigh: string;
5598
+ };
5599
+ };
5600
+ reasoning: true;
5601
+ input: ("image" | "text")[];
5602
+ cost: {
5603
+ input: number;
5604
+ output: number;
5605
+ cacheRead: number;
5606
+ cacheWrite: number;
5607
+ };
5608
+ contextWindow: number;
5609
+ maxTokens: number;
5610
+ };
5611
+ readonly "kimi-for-coding-highspeed": {
5612
+ id: string;
5613
+ name: string;
5614
+ api: "openai-completions";
5615
+ provider: string;
5616
+ baseUrl: string;
5617
+ compat: {
5618
+ thinkingFormat: "kimi";
5619
+ supportsDeveloperRole: false;
5620
+ reasoningEffortMap: {
5621
+ minimal: string;
5622
+ low: string;
5623
+ medium: string;
5624
+ high: string;
5625
+ xhigh: string;
5626
+ };
5564
5627
  };
5565
5628
  reasoning: true;
5566
5629
  input: ("image" | "text")[];
@@ -7815,7 +7878,7 @@ export declare const MODELS: {
7815
7878
  readonly "grok-4.5": {
7816
7879
  id: string;
7817
7880
  name: string;
7818
- api: "openai-completions";
7881
+ api: "openai-responses";
7819
7882
  provider: string;
7820
7883
  baseUrl: string;
7821
7884
  reasoning: true;
@@ -8123,7 +8186,7 @@ export declare const MODELS: {
8123
8186
  readonly "grok-4.5": {
8124
8187
  id: string;
8125
8188
  name: string;
8126
- api: "openai-completions";
8189
+ api: "openai-responses";
8127
8190
  provider: string;
8128
8191
  baseUrl: string;
8129
8192
  reasoning: true;
@@ -9550,23 +9613,6 @@ export declare const MODELS: {
9550
9613
  contextWindow: number;
9551
9614
  maxTokens: number;
9552
9615
  };
9553
- readonly "meta-llama/llama-3.3-70b-instruct:free": {
9554
- id: string;
9555
- name: string;
9556
- api: "openai-completions";
9557
- provider: string;
9558
- baseUrl: string;
9559
- reasoning: false;
9560
- input: "text"[];
9561
- cost: {
9562
- input: number;
9563
- output: number;
9564
- cacheRead: number;
9565
- cacheWrite: number;
9566
- };
9567
- contextWindow: number;
9568
- maxTokens: number;
9569
- };
9570
9616
  readonly "meta-llama/llama-4-maverick": {
9571
9617
  id: string;
9572
9618
  name: string;
@@ -11760,23 +11806,6 @@ export declare const MODELS: {
11760
11806
  contextWindow: number;
11761
11807
  maxTokens: number;
11762
11808
  };
11763
- readonly "qwen/qwen3-coder:free": {
11764
- id: string;
11765
- name: string;
11766
- api: "openai-completions";
11767
- provider: string;
11768
- baseUrl: string;
11769
- reasoning: false;
11770
- input: "text"[];
11771
- cost: {
11772
- input: number;
11773
- output: number;
11774
- cacheRead: number;
11775
- cacheWrite: number;
11776
- };
11777
- contextWindow: number;
11778
- maxTokens: number;
11779
- };
11780
11809
  readonly "qwen/qwen3-max": {
11781
11810
  id: string;
11782
11811
  name: string;
@@ -11828,23 +11857,6 @@ export declare const MODELS: {
11828
11857
  contextWindow: number;
11829
11858
  maxTokens: number;
11830
11859
  };
11831
- readonly "qwen/qwen3-next-80b-a3b-instruct:free": {
11832
- id: string;
11833
- name: string;
11834
- api: "openai-completions";
11835
- provider: string;
11836
- baseUrl: string;
11837
- reasoning: false;
11838
- input: "text"[];
11839
- cost: {
11840
- input: number;
11841
- output: number;
11842
- cacheRead: number;
11843
- cacheWrite: number;
11844
- };
11845
- contextWindow: number;
11846
- maxTokens: number;
11847
- };
11848
11860
  readonly "qwen/qwen3-next-80b-a3b-thinking": {
11849
11861
  id: string;
11850
11862
  name: string;
@@ -12406,6 +12418,23 @@ export declare const MODELS: {
12406
12418
  contextWindow: number;
12407
12419
  maxTokens: number;
12408
12420
  };
12421
+ readonly "thinkingmachines/inkling": {
12422
+ id: string;
12423
+ name: string;
12424
+ api: "openai-completions";
12425
+ provider: string;
12426
+ baseUrl: string;
12427
+ reasoning: true;
12428
+ input: ("image" | "text")[];
12429
+ cost: {
12430
+ input: number;
12431
+ output: number;
12432
+ cacheRead: number;
12433
+ cacheWrite: number;
12434
+ };
12435
+ contextWindow: number;
12436
+ maxTokens: number;
12437
+ };
12409
12438
  readonly "upstage/solar-pro-3": {
12410
12439
  id: string;
12411
12440
  name: string;