@dreb/ai 2.70.1 → 2.72.0
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 +14 -3
- package/dist/models.d.ts +61 -5
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.d.ts +1789 -1315
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +2304 -1758
- package/dist/models.generated.js.map +1 -1
- package/dist/models.js +148 -17
- package/dist/models.js.map +1 -1
- package/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.js +14 -20
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/anthropic.d.ts +13 -9
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +15 -25
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/simple-options.d.ts +1 -0
- package/dist/providers/simple-options.d.ts.map +1 -1
- package/dist/providers/simple-options.js +7 -6
- package/dist/providers/simple-options.js.map +1 -1
- package/dist/utils/oauth/github-copilot-models.d.ts +36 -0
- package/dist/utils/oauth/github-copilot-models.d.ts.map +1 -0
- package/dist/utils/oauth/github-copilot-models.js +112 -0
- package/dist/utils/oauth/github-copilot-models.js.map +1 -0
- package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/dist/utils/oauth/github-copilot.js +32 -5
- package/dist/utils/oauth/github-copilot.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -444,7 +444,10 @@ const response = await completeSimple(model, {
|
|
|
444
444
|
messages: [{ role: 'user', content: 'Solve: 2x + 5 = 13' }]
|
|
445
445
|
}, {
|
|
446
446
|
reasoning: 'medium' // 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'
|
|
447
|
-
// Native normalized 'max' is model-aware (
|
|
447
|
+
// Native normalized 'max' is model-aware (GPT-5.6, GPT-6, and Claude Opus/Sonnet 4.6+ and Haiku 5.5 on Anthropic Messages/Bedrock).
|
|
448
|
+
// On Anthropic Messages (including GitHub Copilot) and Bedrock, Claude 'xhigh' and 'max' are sent as the native efforts of the same name.
|
|
449
|
+
// Use getAvailableThinkingLevels(model) for the supported levels and
|
|
450
|
+
// clampThinkingLevel(model, level) to apply dreb's capability fallback rules.
|
|
448
451
|
});
|
|
449
452
|
|
|
450
453
|
// Access thinking and text blocks
|
|
@@ -512,8 +515,8 @@ for await (const event of s) {
|
|
|
512
515
|
|
|
513
516
|
### Controlling Thinking Display (Anthropic adaptive models)
|
|
514
517
|
|
|
515
|
-
Adaptive-thinking Claude models (Opus and Sonnet 4.6
|
|
516
|
-
support a `thinkingDisplay` option on `streamSimple`/`completeSimple`:
|
|
518
|
+
Adaptive-thinking Claude models (Opus and Sonnet 4.6+, Haiku 5+, including the
|
|
519
|
+
Claude 5 families) support a `thinkingDisplay` option on `streamSimple`/`completeSimple`:
|
|
517
520
|
|
|
518
521
|
```typescript
|
|
519
522
|
const s = streamSimple(model, context, {
|
|
@@ -535,6 +538,12 @@ provider default applies.
|
|
|
535
538
|
|
|
536
539
|
Use `supportsAdaptiveThinking(model)` to check whether a model honors this option.
|
|
537
540
|
|
|
541
|
+
Related capability helpers: `supportsXhigh(model)`, `supportsMax(model)`, and
|
|
542
|
+
`supportsThinkingOff(model)` (false for Claude Opus 5.5+ on Anthropic Messages/Bedrock, where
|
|
543
|
+
thinking cannot be turned off). `clampThinkingLevel(model, level)` applies dreb's shared
|
|
544
|
+
fallback rules (`off` → `minimal` on always-thinking models, `xhigh` → `max` or `high`,
|
|
545
|
+
`max` → `xhigh` → `high`, non-reasoning → `off`).
|
|
546
|
+
|
|
538
547
|
## Stop Reasons
|
|
539
548
|
|
|
540
549
|
Every `AssistantMessage` includes a `stopReason` field that indicates how the generation ended:
|
|
@@ -1156,6 +1165,8 @@ const response = await complete(model, {
|
|
|
1156
1165
|
|
|
1157
1166
|
**GitHub Copilot**: If you get "The requested model is not supported" error, enable the model manually in VS Code: open Copilot Chat, click the model selector, select the model (warning icon), and click "Enable".
|
|
1158
1167
|
|
|
1168
|
+
The Copilot OAuth provider also discovers the account's models: `login` and `refreshToken` read Copilot's `/models` and store the result as `credentials.models`, and `modifyModels()` applies it — hiding built-in Copilot models the account doesn't offer (or has disabled) and switching a model whose catalog endpoint isn't served to an advertised one (`/responses`, then `/chat/completions`). Capabilities still come from the catalog. Without discovered models, `modifyModels()` only rewrites `baseUrl`.
|
|
1169
|
+
|
|
1159
1170
|
**Google Gemini CLI / Antigravity**: These use Google Cloud OAuth. The `apiKey` returned by `getOAuthApiKey()` is a JSON string containing both the token and project ID, which the library handles automatically.
|
|
1160
1171
|
|
|
1161
1172
|
**Kimi For Coding**: API-key models use Kimi's Anthropic-compatible coding API. OAuth models use the Kimi Code subscription endpoint and `kimi-for-coding` accepts OpenAI-style multimodal content arrays with base64 `image_url` data URLs. Moonshot Open Platform vision examples use a different base URL, and first-party Kimi CLI media handling may differ.
|
package/dist/models.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MODELS } from "./models.generated.js";
|
|
2
|
-
import type { Api, KnownProvider, Model, Usage } from "./types.js";
|
|
2
|
+
import type { Api, KnownProvider, Model, ThinkingLevel, Usage } from "./types.js";
|
|
3
3
|
type ModelApi<TProvider extends KnownProvider, TModelId extends keyof (typeof MODELS)[TProvider]> = (typeof MODELS)[TProvider][TModelId] extends {
|
|
4
4
|
api: infer TApi;
|
|
5
5
|
} ? (TApi extends Api ? TApi : never) : never;
|
|
@@ -52,6 +52,8 @@ export declare function isQwen38OrLater(modelId: string): boolean;
|
|
|
52
52
|
* Supported today:
|
|
53
53
|
* - GPT-5.6 model families (Sol, Terra, Luna, and the alias)
|
|
54
54
|
* - GPT-6 model families (Astra and future variants)
|
|
55
|
+
* - On Anthropic-native transports (Anthropic Messages, including GitHub Copilot, and
|
|
56
|
+
* Bedrock): Claude Opus/Sonnet 4.6+ (including the Claude 5 families) and Haiku 5.5+
|
|
55
57
|
*/
|
|
56
58
|
export declare function supportsMax<TApi extends Api>(model: Model<TApi>): boolean;
|
|
57
59
|
/**
|
|
@@ -60,17 +62,71 @@ export declare function supportsMax<TApi extends Api>(model: Model<TApi>): boole
|
|
|
60
62
|
* Supported today:
|
|
61
63
|
* - GPT-5.2 through GPT-5.6 model families
|
|
62
64
|
* - GPT-6 model families (Astra and future variants)
|
|
63
|
-
* - Claude Opus 4.
|
|
65
|
+
* - Claude on Anthropic-native transports: Opus 4.7+, Sonnet 5+, Haiku 5.5+
|
|
66
|
+
* (Opus 4.6 and Sonnet 4.6 have `max` but no native `xhigh`)
|
|
67
|
+
* - Claude on OpenAI-compatible transports (OpenRouter, ...): Opus 4.6+ and Sonnet 5+
|
|
68
|
+
* (the legacy rule; those transports map effort themselves)
|
|
64
69
|
* - Kimi Code K3 (xhigh maps to its advertised "max" effort)
|
|
65
70
|
* - Qwen 3.8+ model families (xhigh is their top native effort tier)
|
|
66
71
|
*/
|
|
67
72
|
export declare function supportsXhigh<TApi extends Api>(model: Model<TApi>): boolean;
|
|
68
73
|
/**
|
|
69
|
-
* Check if a model uses adaptive thinking (Opus/Sonnet 4.6
|
|
70
|
-
* families), where the `thinkingDisplay` option
|
|
71
|
-
* per-provider internal checks.
|
|
74
|
+
* Check if a model uses adaptive thinking (Opus/Sonnet 4.6+, Haiku 5+, including the
|
|
75
|
+
* Claude 5 families), where `output_config.effort` and the `thinkingDisplay` option
|
|
76
|
+
* are honored. Mirrors the per-provider internal checks.
|
|
72
77
|
*/
|
|
73
78
|
export declare function supportsAdaptiveThinking<TApi extends Api>(model: Model<TApi>): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Check whether thinking can be turned off for a model (dreb level `off`).
|
|
81
|
+
*
|
|
82
|
+
* False only for Claude Opus 5.5+ on Anthropic-native transports, where adaptive
|
|
83
|
+
* thinking is always on and `thinking: {type: "disabled"}` is rejected. Callers that
|
|
84
|
+
* offer selectable levels should hide `off` for such models; {@link clampThinkingLevel}
|
|
85
|
+
* maps a requested `off` to `minimal`.
|
|
86
|
+
*/
|
|
87
|
+
export declare function supportsThinkingOff<TApi extends Api>(model: Model<TApi>): boolean;
|
|
88
|
+
/** How a request turns Claude thinking off on an Anthropic-native transport. */
|
|
89
|
+
export type ClaudeThinkingOffMode = "disabled" | "between_tools" | "omit";
|
|
90
|
+
/**
|
|
91
|
+
* Select the request shape that turns thinking off for a Claude model:
|
|
92
|
+
* - `"omit"`: send neither a `thinking` field nor an effort (Opus 5.5+, where thinking
|
|
93
|
+
* cannot be disabled; the model default effort applies)
|
|
94
|
+
* - `"between_tools"`: `thinking: {type: "between_tools"}` (Sonnet 5.5+, where
|
|
95
|
+
* `disabled` is rejected)
|
|
96
|
+
* - `"disabled"`: `thinking: {type: "disabled"}` (every other model, and every model on
|
|
97
|
+
* non-native transports)
|
|
98
|
+
*/
|
|
99
|
+
export declare function getClaudeThinkingOffMode<TApi extends Api>(model: Model<TApi>): ClaudeThinkingOffMode;
|
|
100
|
+
/**
|
|
101
|
+
* Check whether a Claude model thinks by default when a request omits the `thinking`
|
|
102
|
+
* field (the Claude 5 generation). Transports that otherwise omit the field to turn
|
|
103
|
+
* thinking off (Bedrock) must send an explicit off config for these models.
|
|
104
|
+
*/
|
|
105
|
+
export declare function claudeThinksByDefault<TApi extends Api>(model: Model<TApi>): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Thinking levels a model supports, in ascending order: `["off"]` for non-reasoning
|
|
108
|
+
* models; otherwise the full scale minus `off` when thinking cannot be turned off
|
|
109
|
+
* (Claude Opus 5.5+), `xhigh` without {@link supportsXhigh}, and `max` without
|
|
110
|
+
* {@link supportsMax}.
|
|
111
|
+
*/
|
|
112
|
+
export declare function getAvailableThinkingLevels<TApi extends Api>(model: Model<TApi> | undefined): (ThinkingLevel | "off")[];
|
|
113
|
+
/**
|
|
114
|
+
* Clamp a requested thinking level to what a model supports. This is the single
|
|
115
|
+
* source of truth for thinking-level fallback across dreb: the nearest available
|
|
116
|
+
* level at or above the request, else the highest available level. So:
|
|
117
|
+
* - non-reasoning models → `off`
|
|
118
|
+
* - `off` on models where thinking cannot be turned off → `minimal`
|
|
119
|
+
* - unsupported `xhigh` → `max` when the model has `max` (Opus/Sonnet 4.6), else `high`
|
|
120
|
+
* - unsupported `max` → `xhigh` when supported, else `high`
|
|
121
|
+
* - an unknown level → the lowest available level
|
|
122
|
+
*/
|
|
123
|
+
export declare function clampThinkingLevel<TApi extends Api>(model: Model<TApi> | undefined, level: ThinkingLevel | "off"): ThinkingLevel | "off";
|
|
124
|
+
/**
|
|
125
|
+
* Anthropic adaptive-thinking effort (`output_config.effort`) for a dreb thinking level,
|
|
126
|
+
* after clamping it to the model. `minimal` → `low`; `xhigh` and `max` are sent as
|
|
127
|
+
* themselves; an unset level (or `off` on a non-reasoning model) → `high`.
|
|
128
|
+
*/
|
|
129
|
+
export declare function getClaudeEffort<TApi extends Api>(model: Model<TApi>, level: ThinkingLevel | undefined): Exclude<ThinkingLevel, "minimal">;
|
|
74
130
|
/**
|
|
75
131
|
* Check if two models are equal by comparing both their id and provider.
|
|
76
132
|
* Returns false if either model is null or undefined.
|
package/dist/models.d.ts.map
CHANGED
|
@@ -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;AAoCD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAIxD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAKzE;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAoB3E;AAED;;;;GAIG;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\ninterface ClaudeFamilyVersion {\n\tfamily: \"opus\" | \"sonnet\";\n\tmajor: number;\n\tminor?: number;\n}\n\n/** Parse Claude family versions without mistaking long date suffixes for versions. */\nfunction claudeFamilyVersion(modelId: string): ClaudeFamilyVersion | undefined {\n\tconst match = modelId.match(/(opus|sonnet)-(\\d{1,2})(?:[.-](\\d{1,2}))?(?!\\d)/);\n\tif (!match) return undefined;\n\treturn {\n\t\tfamily: match[1] as ClaudeFamilyVersion[\"family\"],\n\t\tmajor: Number.parseInt(match[2], 10),\n\t\tminor: match[3] === undefined ? undefined : Number.parseInt(match[3], 10),\n\t};\n}\n\n/**\n * Parse Qwen family versions without mistaking parameter counts for minor versions.\n * Only a dot separates major from minor (qwen3.8-27b, Qwen-3.8); a hyphen after the\n * major version introduces the parameter size (qwen3-32b → 3.0, not 3.32). A capture\n * directly followed by a `b`-suffixed size token is a parameter count, not a version\n * (qwen-32b, Qwen-7B-Chat, deepseek-r1-distill-qwen-32b), and Qwen majors never reach\n * two digits, so only a single digit is accepted.\n */\nfunction qwenFamilyVersion(modelId: string): { major: number; minor: number } | undefined {\n\tconst match = modelId.match(/\\bqwen[\\s_-]*v?(\\d)(?:\\.(\\d+))?(?![\\d.]*b\\b)/i);\n\tif (!match) return undefined;\n\treturn {\n\t\tmajor: Number.parseInt(match[1], 10),\n\t\tminor: match[2] === undefined ? 0 : Number.parseInt(match[2], 10),\n\t};\n}\n\n/**\n * Check whether a model id belongs to Qwen 3.8 or a later Qwen generation — the first\n * Qwen family with graded `reasoning_effort` tiers (low/medium/xhigh, default xhigh).\n */\nexport function isQwen38OrLater(modelId: string): boolean {\n\tconst qwen = qwenFamilyVersion(modelId);\n\tif (!qwen) return false;\n\treturn qwen.major > 3 || (qwen.major === 3 && qwen.minor >= 8);\n}\n\n/**\n * Check if a model supports the native `max` reasoning tier.\n *\n * Supported today:\n * - GPT-5.6 model families (Sol, Terra, Luna, and the alias)\n * - GPT-6 model families (Astra and future variants)\n */\nexport function supportsMax<TApi extends Api>(model: Model<TApi>): boolean {\n\tconst id = model.id.toLowerCase();\n\t// Anchored to the id start or a gateway \"/\" prefix so unrelated ids\n\t// (e.g. \"gpt-60\") do not match.\n\treturn /(?:^|\\/)gpt-5\\.6(?:$|[-.])/.test(id) || /(?:^|\\/)gpt-6(?:$|[-.])/.test(id);\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 * - GPT-6 model families (Astra and future variants)\n * - Claude Opus 4.6–4.x and Claude 5 model families\n * - Kimi Code K3 (xhigh maps to its advertised \"max\" effort)\n * - Qwen 3.8+ model families (xhigh is their top native effort tier)\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 (isQwen38OrLater(model.id)) 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\tmodel.id.includes(\"gpt-6\")\n\t) {\n\t\treturn true;\n\t}\n\n\tconst claude = claudeFamilyVersion(model.id);\n\tif (!claude) return false;\n\tif (claude.family === \"opus\") {\n\t\treturn claude.major >= 5 || (claude.major === 4 && (claude.minor ?? 0) >= 6);\n\t}\n\treturn claude.major >= 5;\n}\n\n/**\n * Check if a model uses adaptive thinking (Opus/Sonnet 4.6–4.x and Claude 5\n * families), where the `thinkingDisplay` option is honored. Mirrors the\n * per-provider internal checks.\n */\nexport function supportsAdaptiveThinking<TApi extends Api>(model: Model<TApi>): boolean {\n\tconst claude = claudeFamilyVersion(model.id);\n\treturn claude != null && (claude.major >= 5 || (claude.major === 4 && (claude.minor ?? 0) >= 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":"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","sourcesContent":["import { MODELS } from \"./models.generated.js\";\nimport type { Api, KnownProvider, Model, ThinkingLevel, 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\ninterface ClaudeFamilyVersion {\n\tfamily: \"opus\" | \"sonnet\" | \"haiku\";\n\tmajor: number;\n\tminor?: number;\n}\n\n/** Parse Claude family versions without mistaking long date suffixes for versions. */\nfunction claudeFamilyVersion(modelId: string): ClaudeFamilyVersion | undefined {\n\tconst match = modelId.match(/(opus|sonnet|haiku)-(\\d{1,2})(?:[.-](\\d{1,2}))?(?!\\d)/);\n\tif (!match) return undefined;\n\treturn {\n\t\tfamily: match[1] as ClaudeFamilyVersion[\"family\"],\n\t\tmajor: Number.parseInt(match[2], 10),\n\t\tminor: match[3] === undefined ? undefined : Number.parseInt(match[3], 10),\n\t};\n}\n\n/** True when a parsed Claude version is at least `major.minor`. */\nfunction claudeAtLeast(claude: ClaudeFamilyVersion, major: number, minor = 0): boolean {\n\treturn claude.major > major || (claude.major === major && (claude.minor ?? 0) >= minor);\n}\n\n/**\n * Anthropic-native transports, where dreb controls the Anthropic `thinking` /\n * `output_config.effort` request fields directly: the Anthropic Messages API\n * (Anthropic, GitHub Copilot's `/v1/messages`, opencode, custom Anthropic-compatible\n * endpoints) and Amazon Bedrock. Claude served through OpenAI-compatible APIs\n * (OpenRouter, Copilot Fable, ...) keeps the legacy capability rules because those\n * transports map effort themselves.\n */\nfunction isAnthropicNativeTransport<TApi extends Api>(model: Model<TApi>): boolean {\n\treturn model.api === \"anthropic-messages\" || model.api === \"bedrock-converse-stream\";\n}\n\n/** Claude model on an Anthropic-native transport, or undefined. */\nfunction nativeClaude<TApi extends Api>(model: Model<TApi>): ClaudeFamilyVersion | undefined {\n\treturn isAnthropicNativeTransport(model) ? claudeFamilyVersion(model.id) : undefined;\n}\n\n/**\n * Parse Qwen family versions without mistaking parameter counts for minor versions.\n * Only a dot separates major from minor (qwen3.8-27b, Qwen-3.8); a hyphen after the\n * major version introduces the parameter size (qwen3-32b → 3.0, not 3.32). A capture\n * directly followed by a `b`-suffixed size token is a parameter count, not a version\n * (qwen-32b, Qwen-7B-Chat, deepseek-r1-distill-qwen-32b), and Qwen majors never reach\n * two digits, so only a single digit is accepted.\n */\nfunction qwenFamilyVersion(modelId: string): { major: number; minor: number } | undefined {\n\tconst match = modelId.match(/\\bqwen[\\s_-]*v?(\\d)(?:\\.(\\d+))?(?![\\d.]*b\\b)/i);\n\tif (!match) return undefined;\n\treturn {\n\t\tmajor: Number.parseInt(match[1], 10),\n\t\tminor: match[2] === undefined ? 0 : Number.parseInt(match[2], 10),\n\t};\n}\n\n/**\n * Check whether a model id belongs to Qwen 3.8 or a later Qwen generation — the first\n * Qwen family with graded `reasoning_effort` tiers (low/medium/xhigh, default xhigh).\n */\nexport function isQwen38OrLater(modelId: string): boolean {\n\tconst qwen = qwenFamilyVersion(modelId);\n\tif (!qwen) return false;\n\treturn qwen.major > 3 || (qwen.major === 3 && qwen.minor >= 8);\n}\n\n/**\n * Check if a model supports the native `max` reasoning tier.\n *\n * Supported today:\n * - GPT-5.6 model families (Sol, Terra, Luna, and the alias)\n * - GPT-6 model families (Astra and future variants)\n * - On Anthropic-native transports (Anthropic Messages, including GitHub Copilot, and\n * Bedrock): Claude Opus/Sonnet 4.6+ (including the Claude 5 families) and Haiku 5.5+\n */\nexport function supportsMax<TApi extends Api>(model: Model<TApi>): boolean {\n\tconst id = model.id.toLowerCase();\n\t// Anchored to the id start or a gateway \"/\" prefix so unrelated ids\n\t// (e.g. \"gpt-60\") do not match.\n\tif (/(?:^|\\/)gpt-5\\.6(?:$|[-.])/.test(id) || /(?:^|\\/)gpt-6(?:$|[-.])/.test(id)) return true;\n\n\tconst claude = nativeClaude(model);\n\tif (!claude) return false;\n\tif (claude.family === \"haiku\") return claudeAtLeast(claude, 5, 5);\n\treturn claudeAtLeast(claude, 4, 6);\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 * - GPT-6 model families (Astra and future variants)\n * - Claude on Anthropic-native transports: Opus 4.7+, Sonnet 5+, Haiku 5.5+\n * (Opus 4.6 and Sonnet 4.6 have `max` but no native `xhigh`)\n * - Claude on OpenAI-compatible transports (OpenRouter, ...): Opus 4.6+ and Sonnet 5+\n * (the legacy rule; those transports map effort themselves)\n * - Kimi Code K3 (xhigh maps to its advertised \"max\" effort)\n * - Qwen 3.8+ model families (xhigh is their top native effort tier)\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 (isQwen38OrLater(model.id)) return true;\n\tif (/gpt-(?:5\\.[2-6]|6)/.test(model.id)) return true;\n\n\tconst claude = claudeFamilyVersion(model.id);\n\tif (!claude) return false;\n\tif (isAnthropicNativeTransport(model)) {\n\t\tif (claude.family === \"opus\") return claudeAtLeast(claude, 4, 7);\n\t\tif (claude.family === \"sonnet\") return claudeAtLeast(claude, 5);\n\t\treturn claudeAtLeast(claude, 5, 5);\n\t}\n\t// Legacy rule for proxied Claude transports (unchanged; Haiku never qualified).\n\tif (claude.family === \"opus\") return claudeAtLeast(claude, 4, 6);\n\tif (claude.family === \"sonnet\") return claudeAtLeast(claude, 5);\n\treturn false;\n}\n\n/**\n * Check if a model uses adaptive thinking (Opus/Sonnet 4.6+, Haiku 5+, including the\n * Claude 5 families), where `output_config.effort` and the `thinkingDisplay` option\n * are honored. Mirrors the per-provider internal checks.\n */\nexport function supportsAdaptiveThinking<TApi extends Api>(model: Model<TApi>): boolean {\n\tconst claude = claudeFamilyVersion(model.id);\n\tif (!claude) return false;\n\tif (claude.family === \"haiku\") return claudeAtLeast(claude, 5);\n\treturn claudeAtLeast(claude, 4, 6);\n}\n\n/**\n * Check whether thinking can be turned off for a model (dreb level `off`).\n *\n * False only for Claude Opus 5.5+ on Anthropic-native transports, where adaptive\n * thinking is always on and `thinking: {type: \"disabled\"}` is rejected. Callers that\n * offer selectable levels should hide `off` for such models; {@link clampThinkingLevel}\n * maps a requested `off` to `minimal`.\n */\nexport function supportsThinkingOff<TApi extends Api>(model: Model<TApi>): boolean {\n\treturn getClaudeThinkingOffMode(model) !== \"omit\";\n}\n\n/** How a request turns Claude thinking off on an Anthropic-native transport. */\nexport type ClaudeThinkingOffMode = \"disabled\" | \"between_tools\" | \"omit\";\n\n/**\n * Select the request shape that turns thinking off for a Claude model:\n * - `\"omit\"`: send neither a `thinking` field nor an effort (Opus 5.5+, where thinking\n * cannot be disabled; the model default effort applies)\n * - `\"between_tools\"`: `thinking: {type: \"between_tools\"}` (Sonnet 5.5+, where\n * `disabled` is rejected)\n * - `\"disabled\"`: `thinking: {type: \"disabled\"}` (every other model, and every model on\n * non-native transports)\n */\nexport function getClaudeThinkingOffMode<TApi extends Api>(model: Model<TApi>): ClaudeThinkingOffMode {\n\tconst claude = nativeClaude(model);\n\tif (!claude) return \"disabled\";\n\tif (claude.family === \"opus\" && claudeAtLeast(claude, 5, 5)) return \"omit\";\n\tif (claude.family === \"sonnet\" && claudeAtLeast(claude, 5, 5)) return \"between_tools\";\n\treturn \"disabled\";\n}\n\n/**\n * Check whether a Claude model thinks by default when a request omits the `thinking`\n * field (the Claude 5 generation). Transports that otherwise omit the field to turn\n * thinking off (Bedrock) must send an explicit off config for these models.\n */\nexport function claudeThinksByDefault<TApi extends Api>(model: Model<TApi>): boolean {\n\tconst claude = claudeFamilyVersion(model.id);\n\treturn claude !== undefined && claude.major >= 5;\n}\n\n/** Every dreb thinking level, in ascending order. */\nconst THINKING_LEVEL_ORDER: readonly (ThinkingLevel | \"off\")[] = [\n\t\"off\",\n\t\"minimal\",\n\t\"low\",\n\t\"medium\",\n\t\"high\",\n\t\"xhigh\",\n\t\"max\",\n];\n\n/**\n * Thinking levels a model supports, in ascending order: `[\"off\"]` for non-reasoning\n * models; otherwise the full scale minus `off` when thinking cannot be turned off\n * (Claude Opus 5.5+), `xhigh` without {@link supportsXhigh}, and `max` without\n * {@link supportsMax}.\n */\nexport function getAvailableThinkingLevels<TApi extends Api>(\n\tmodel: Model<TApi> | undefined,\n): (ThinkingLevel | \"off\")[] {\n\tif (!model?.reasoning) return [\"off\"];\n\treturn THINKING_LEVEL_ORDER.filter((level) => {\n\t\tif (level === \"off\") return supportsThinkingOff(model);\n\t\tif (level === \"xhigh\") return supportsXhigh(model);\n\t\tif (level === \"max\") return supportsMax(model);\n\t\treturn true;\n\t});\n}\n\n/**\n * Clamp a requested thinking level to what a model supports. This is the single\n * source of truth for thinking-level fallback across dreb: the nearest available\n * level at or above the request, else the highest available level. So:\n * - non-reasoning models → `off`\n * - `off` on models where thinking cannot be turned off → `minimal`\n * - unsupported `xhigh` → `max` when the model has `max` (Opus/Sonnet 4.6), else `high`\n * - unsupported `max` → `xhigh` when supported, else `high`\n * - an unknown level → the lowest available level\n */\nexport function clampThinkingLevel<TApi extends Api>(\n\tmodel: Model<TApi> | undefined,\n\tlevel: ThinkingLevel | \"off\",\n): ThinkingLevel | \"off\" {\n\tconst available = getAvailableThinkingLevels(model);\n\tconst requested = THINKING_LEVEL_ORDER.indexOf(level);\n\treturn (\n\t\tavailable.find((candidate) => THINKING_LEVEL_ORDER.indexOf(candidate) >= requested) ??\n\t\tavailable[available.length - 1]\n\t);\n}\n\n/**\n * Anthropic adaptive-thinking effort (`output_config.effort`) for a dreb thinking level,\n * after clamping it to the model. `minimal` → `low`; `xhigh` and `max` are sent as\n * themselves; an unset level (or `off` on a non-reasoning model) → `high`.\n */\nexport function getClaudeEffort<TApi extends Api>(\n\tmodel: Model<TApi>,\n\tlevel: ThinkingLevel | undefined,\n): Exclude<ThinkingLevel, \"minimal\"> {\n\tconst clamped = level === undefined ? \"high\" : clampThinkingLevel(model, level);\n\treturn clamped === \"minimal\" ? \"low\" : clamped === \"off\" ? \"high\" : clamped;\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"]}
|