@oh-my-pi/pi-catalog 15.10.11
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/CHANGELOG.md +38 -0
- package/dist/types/build.d.ts +3 -0
- package/dist/types/compat/anthropic.d.ts +11 -0
- package/dist/types/compat/apply.d.ts +7 -0
- package/dist/types/compat/openai.d.ts +21 -0
- package/dist/types/discovery/antigravity.d.ts +61 -0
- package/dist/types/discovery/codex.d.ts +38 -0
- package/dist/types/discovery/cursor-gen/agent_pb.d.ts +13022 -0
- package/dist/types/discovery/cursor.d.ts +23 -0
- package/dist/types/discovery/gemini.d.ts +25 -0
- package/dist/types/discovery/index.d.ts +4 -0
- package/dist/types/discovery/openai-compatible.d.ts +72 -0
- package/dist/types/effort.d.ts +9 -0
- package/dist/types/fireworks-model-id.d.ts +10 -0
- package/dist/types/hosts.d.ts +128 -0
- package/dist/types/identity/bundled.d.ts +6 -0
- package/dist/types/identity/classify.d.ts +45 -0
- package/dist/types/identity/equivalence.d.ts +46 -0
- package/dist/types/identity/family.d.ts +45 -0
- package/dist/types/identity/id.d.ts +12 -0
- package/dist/types/identity/index.d.ts +9 -0
- package/dist/types/identity/markers.d.ts +4 -0
- package/dist/types/identity/priority.d.ts +1 -0
- package/dist/types/identity/reference.d.ts +22 -0
- package/dist/types/identity/selection.d.ts +20 -0
- package/dist/types/index.d.ts +15 -0
- package/dist/types/model-cache.d.ts +17 -0
- package/dist/types/model-manager.d.ts +64 -0
- package/dist/types/model-thinking.d.ts +67 -0
- package/dist/types/models.d.ts +12 -0
- package/dist/types/provider-models/bundled-references.d.ts +11 -0
- package/dist/types/provider-models/descriptor-types.d.ts +74 -0
- package/dist/types/provider-models/descriptors.d.ts +384 -0
- package/dist/types/provider-models/discovery-constants.d.ts +11 -0
- package/dist/types/provider-models/google.d.ts +27 -0
- package/dist/types/provider-models/index.d.ts +6 -0
- package/dist/types/provider-models/ollama.d.ts +9 -0
- package/dist/types/provider-models/openai-compat.d.ts +385 -0
- package/dist/types/provider-models/special.d.ts +16 -0
- package/dist/types/types.d.ts +405 -0
- package/dist/types/utils.d.ts +5 -0
- package/dist/types/wire/codex.d.ts +26 -0
- package/dist/types/wire/gemini-headers.d.ts +18 -0
- package/dist/types/wire/github-copilot.d.ts +18 -0
- package/package.json +100 -0
- package/src/build.ts +40 -0
- package/src/compat/anthropic.ts +67 -0
- package/src/compat/apply.ts +15 -0
- package/src/compat/openai.ts +365 -0
- package/src/discovery/antigravity.ts +261 -0
- package/src/discovery/codex.ts +371 -0
- package/src/discovery/cursor-gen/agent_pb.ts +15274 -0
- package/src/discovery/cursor.ts +307 -0
- package/src/discovery/gemini.ts +249 -0
- package/src/discovery/index.ts +4 -0
- package/src/discovery/openai-compatible.ts +224 -0
- package/src/effort.ts +16 -0
- package/src/fireworks-model-id.ts +30 -0
- package/src/hosts.ts +114 -0
- package/src/identity/bundled.ts +38 -0
- package/src/identity/classify.ts +141 -0
- package/src/identity/equivalence.ts +870 -0
- package/src/identity/family.ts +88 -0
- package/src/identity/id.ts +81 -0
- package/src/identity/index.ts +9 -0
- package/src/identity/markers.ts +49 -0
- package/src/identity/priority.ts +56 -0
- package/src/identity/reference.ts +134 -0
- package/src/identity/selection.ts +65 -0
- package/src/index.ts +15 -0
- package/src/model-cache.ts +132 -0
- package/src/model-manager.ts +472 -0
- package/src/model-thinking.ts +407 -0
- package/src/models.json +75308 -0
- package/src/models.json.d.ts +9 -0
- package/src/models.ts +64 -0
- package/src/provider-models/bundled-references.ts +54 -0
- package/src/provider-models/descriptor-types.ts +79 -0
- package/src/provider-models/descriptors.ts +456 -0
- package/src/provider-models/discovery-constants.ts +11 -0
- package/src/provider-models/google.ts +105 -0
- package/src/provider-models/index.ts +6 -0
- package/src/provider-models/ollama.ts +154 -0
- package/src/provider-models/openai-compat.ts +3106 -0
- package/src/provider-models/special.ts +67 -0
- package/src/types.ts +470 -0
- package/src/utils.ts +27 -0
- package/src/wire/codex.ts +43 -0
- package/src/wire/gemini-headers.ts +41 -0
- package/src/wire/github-copilot.ts +72 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thinking metadata: build-time derivation and runtime field-read helpers.
|
|
3
|
+
*
|
|
4
|
+
* Derivation (`resolveModelThinking`) runs exactly once per model — from
|
|
5
|
+
* `buildModel` for dynamic specs and from the catalog generator for bundled
|
|
6
|
+
* entries. Everything below the "runtime helpers" divider reads baked fields
|
|
7
|
+
* only: no id parsing, no host matching, no compat detection per request.
|
|
8
|
+
*/
|
|
9
|
+
import { Effort, THINKING_EFFORTS } from "./effort";
|
|
10
|
+
import { modelMatchesHost } from "./hosts";
|
|
11
|
+
import {
|
|
12
|
+
type AnthropicModel,
|
|
13
|
+
type GeminiModel,
|
|
14
|
+
isFableOrMythos,
|
|
15
|
+
type OpenAIModel,
|
|
16
|
+
type ParsedModel,
|
|
17
|
+
parseKnownModel,
|
|
18
|
+
semverEqual,
|
|
19
|
+
semverGte,
|
|
20
|
+
} from "./identity/classify";
|
|
21
|
+
import { supportsAdaptiveThinkingDisplay } from "./identity/family";
|
|
22
|
+
import type {
|
|
23
|
+
Api,
|
|
24
|
+
CompatOf,
|
|
25
|
+
Model,
|
|
26
|
+
ModelSpec,
|
|
27
|
+
ResolvedOpenAICompat,
|
|
28
|
+
ResolvedOpenAIResponsesCompat,
|
|
29
|
+
ThinkingConfig,
|
|
30
|
+
} from "./types";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Runtime helpers read baked metadata only, so they accept both pre-build
|
|
34
|
+
* specs and built models.
|
|
35
|
+
*/
|
|
36
|
+
type ApiModel<TApi extends Api = Api> = ModelSpec<TApi> | Model<TApi>;
|
|
37
|
+
|
|
38
|
+
const DEFAULT_REASONING_EFFORTS: readonly Effort[] = [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High];
|
|
39
|
+
const DEFAULT_REASONING_EFFORTS_WITH_XHIGH: readonly Effort[] = [
|
|
40
|
+
Effort.Minimal,
|
|
41
|
+
Effort.Low,
|
|
42
|
+
Effort.Medium,
|
|
43
|
+
Effort.High,
|
|
44
|
+
Effort.XHigh,
|
|
45
|
+
];
|
|
46
|
+
const GEMINI_3_PRO_EFFORTS: readonly Effort[] = [Effort.Low, Effort.High];
|
|
47
|
+
const GEMINI_3_FLASH_EFFORTS: readonly Effort[] = [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High];
|
|
48
|
+
const GPT_5_2_PLUS_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh];
|
|
49
|
+
const GPT_5_1_CODEX_MINI_EFFORTS: readonly Effort[] = [Effort.Medium, Effort.High];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Effort → wire-value map for the 5-tier adaptive scale (Opus 4.7+ and
|
|
53
|
+
* Fable/Mythos 5 on the Messages API). User-facing efforts shift up one notch
|
|
54
|
+
* so the top tier reaches the genuine "max" and "high" lands on Anthropic's
|
|
55
|
+
* recommended "xhigh" coding/agentic default.
|
|
56
|
+
*/
|
|
57
|
+
export const ANTHROPIC_ADAPTIVE_EFFORT_MAP_5_TIER: Readonly<Partial<Record<Effort, string>>> = {
|
|
58
|
+
[Effort.Minimal]: "low",
|
|
59
|
+
[Effort.Low]: "medium",
|
|
60
|
+
[Effort.Medium]: "high",
|
|
61
|
+
[Effort.High]: "xhigh",
|
|
62
|
+
[Effort.XHigh]: "max",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Effort → wire-value map for the legacy 4-tier adaptive scale (Opus 4.6,
|
|
67
|
+
* Sonnet 4.6+, and every adaptive model on Bedrock Converse). `low..high` pass
|
|
68
|
+
* through verbatim; there is no real "xhigh", so it aliases the top "max" tier.
|
|
69
|
+
*/
|
|
70
|
+
export const ANTHROPIC_ADAPTIVE_EFFORT_MAP_4_TIER: Readonly<Partial<Record<Effort, string>>> = {
|
|
71
|
+
[Effort.Minimal]: "low",
|
|
72
|
+
[Effort.XHigh]: "max",
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Build-time derivation (buildModel + catalog generator only)
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Resolve the canonical thinking metadata for a spec. Called exactly once per
|
|
81
|
+
* model by `buildModel`, after compat resolution.
|
|
82
|
+
*
|
|
83
|
+
* - Non-reasoning models never carry thinking.
|
|
84
|
+
* - Models that reason natively but reject the wire effort param
|
|
85
|
+
* (`compat.supportsReasoningEffort: false` on openai-responses*) carry no
|
|
86
|
+
* thinking either: `reasoning: true, thinking: undefined` IS the encoding
|
|
87
|
+
* for "thinks, but exposes no control surface".
|
|
88
|
+
* - Explicit spec thinking (generator-baked or user-authored) owns the
|
|
89
|
+
* capability surface (`mode`, `efforts`, `defaultLevel`); the wire facts
|
|
90
|
+
* (`effortMap`, `supportsDisplay`) are backfilled from identity when not
|
|
91
|
+
* explicitly set, so configs never need to know Anthropic's tier tables.
|
|
92
|
+
* - Sparse specs go through full inference.
|
|
93
|
+
*/
|
|
94
|
+
export function resolveModelThinking<TApi extends Api>(
|
|
95
|
+
spec: ModelSpec<TApi>,
|
|
96
|
+
compat: CompatOf<TApi>,
|
|
97
|
+
): ThinkingConfig | undefined {
|
|
98
|
+
if (!spec.reasoning) return undefined;
|
|
99
|
+
if (omitsWireReasoningEffort(spec.api, compat)) return undefined;
|
|
100
|
+
if (spec.thinking && spec.thinking.efforts.length > 0) {
|
|
101
|
+
return fillThinkingWireDefaults(spec, spec.thinking);
|
|
102
|
+
}
|
|
103
|
+
// Empty/malformed explicit metadata is treated as absent — infer instead.
|
|
104
|
+
return deriveThinking(spec, compat);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Backfill identity-derived wire facts onto explicit thinking metadata.
|
|
109
|
+
* Explicit `effortMap` / `supportsDisplay` (including `false`) always win;
|
|
110
|
+
* untouched configs are returned as-is with zero allocation.
|
|
111
|
+
*/
|
|
112
|
+
function fillThinkingWireDefaults<TApi extends Api>(spec: ModelSpec<TApi>, thinking: ThinkingConfig): ThinkingConfig {
|
|
113
|
+
const needsEffortMap = thinking.mode === "anthropic-adaptive" && thinking.effortMap === undefined;
|
|
114
|
+
const needsDisplay =
|
|
115
|
+
thinking.supportsDisplay === undefined &&
|
|
116
|
+
(spec.api === "anthropic-messages" || spec.api === "bedrock-converse-stream") &&
|
|
117
|
+
supportsAdaptiveThinkingDisplay(spec.id);
|
|
118
|
+
if (!needsEffortMap && !needsDisplay) {
|
|
119
|
+
return thinking;
|
|
120
|
+
}
|
|
121
|
+
const filled: ThinkingConfig = { ...thinking };
|
|
122
|
+
if (needsEffortMap) {
|
|
123
|
+
filled.effortMap = anthropicModelHasRealXHighEffort(spec, parseKnownModel(spec.id))
|
|
124
|
+
? ANTHROPIC_ADAPTIVE_EFFORT_MAP_5_TIER
|
|
125
|
+
: ANTHROPIC_ADAPTIVE_EFFORT_MAP_4_TIER;
|
|
126
|
+
}
|
|
127
|
+
if (needsDisplay) {
|
|
128
|
+
filled.supportsDisplay = true;
|
|
129
|
+
}
|
|
130
|
+
return filled;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Derive thinking from identity + resolved compat, ignoring any baked value. Generator-side entry. */
|
|
134
|
+
export function deriveThinking<TApi extends Api>(spec: ModelSpec<TApi>, compat: CompatOf<TApi>): ThinkingConfig {
|
|
135
|
+
const parsed = parseKnownModel(spec.id);
|
|
136
|
+
const efforts = inferSupportedEfforts(parsed, spec, compat);
|
|
137
|
+
if (efforts.length === 0) {
|
|
138
|
+
throw new Error(`Model ${spec.provider}/${spec.id} resolved to an empty thinking range`);
|
|
139
|
+
}
|
|
140
|
+
const config: ThinkingConfig = {
|
|
141
|
+
mode: inferThinkingControlMode(spec, parsed),
|
|
142
|
+
efforts,
|
|
143
|
+
};
|
|
144
|
+
if (config.mode === "anthropic-adaptive") {
|
|
145
|
+
config.effortMap = anthropicModelHasRealXHighEffort(spec, parsed)
|
|
146
|
+
? ANTHROPIC_ADAPTIVE_EFFORT_MAP_5_TIER
|
|
147
|
+
: ANTHROPIC_ADAPTIVE_EFFORT_MAP_4_TIER;
|
|
148
|
+
}
|
|
149
|
+
if (
|
|
150
|
+
(spec.api === "anthropic-messages" || spec.api === "bedrock-converse-stream") &&
|
|
151
|
+
supportsAdaptiveThinkingDisplay(spec.id)
|
|
152
|
+
) {
|
|
153
|
+
config.supportsDisplay = true;
|
|
154
|
+
}
|
|
155
|
+
return config;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* True when the model reasons natively but rejects the wire `reasoning.effort`
|
|
160
|
+
* param. Scoped to openai-responses* because that's the only API surface where
|
|
161
|
+
* `compat.supportsReasoningEffort: false` means "omit the field entirely"
|
|
162
|
+
* (xAI Grok off the GROK_EFFORT_CAPABLE_PREFIXES allowlist: grok-build,
|
|
163
|
+
* grok-4.20-0309-reasoning). openai-completions keeps its thinking config even
|
|
164
|
+
* without effort support — binary thinking formats (zai/qwen) drive reasoning
|
|
165
|
+
* through other request fields.
|
|
166
|
+
*/
|
|
167
|
+
function omitsWireReasoningEffort(api: Api, compat: CompatOf<Api>): boolean {
|
|
168
|
+
if (api !== "openai-responses" && api !== "openai-codex-responses") {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
return (compat as ResolvedOpenAIResponsesCompat | undefined)?.supportsReasoningEffort === false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function inferSupportedEfforts<TApi extends Api>(
|
|
175
|
+
parsedModel: ParsedModel,
|
|
176
|
+
spec: ModelSpec<TApi>,
|
|
177
|
+
compat: CompatOf<TApi>,
|
|
178
|
+
): readonly Effort[] {
|
|
179
|
+
switch (parsedModel.family) {
|
|
180
|
+
case "openai":
|
|
181
|
+
return inferOpenAISupportedEfforts(parsedModel);
|
|
182
|
+
case "gemini":
|
|
183
|
+
return inferGeminiSupportedEfforts(parsedModel);
|
|
184
|
+
case "anthropic":
|
|
185
|
+
return inferAnthropicSupportedEfforts(parsedModel, spec, compat);
|
|
186
|
+
case "unknown":
|
|
187
|
+
return inferFallbackEfforts(spec, compat);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function inferOpenAISupportedEfforts(model: OpenAIModel): readonly Effort[] {
|
|
192
|
+
if (model.variant === "codex-mini" && semverEqual(model.version, "5.1")) {
|
|
193
|
+
return GPT_5_1_CODEX_MINI_EFFORTS;
|
|
194
|
+
}
|
|
195
|
+
if (semverGte(model.version, "5.2")) {
|
|
196
|
+
return GPT_5_2_PLUS_EFFORTS;
|
|
197
|
+
}
|
|
198
|
+
return DEFAULT_REASONING_EFFORTS;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function inferGeminiSupportedEfforts(model: GeminiModel): readonly Effort[] {
|
|
202
|
+
if (!semverGte(model.version, "3.0")) {
|
|
203
|
+
return DEFAULT_REASONING_EFFORTS;
|
|
204
|
+
}
|
|
205
|
+
return model.kind === "pro" ? GEMINI_3_PRO_EFFORTS : GEMINI_3_FLASH_EFFORTS;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function inferAnthropicSupportedEfforts<TApi extends Api>(
|
|
209
|
+
parsedModel: AnthropicModel,
|
|
210
|
+
spec: ModelSpec<TApi>,
|
|
211
|
+
compat: CompatOf<TApi>,
|
|
212
|
+
): readonly Effort[] {
|
|
213
|
+
if (
|
|
214
|
+
(spec.api === "anthropic-messages" || spec.api === "bedrock-converse-stream") &&
|
|
215
|
+
semverGte(parsedModel.version, "4.6")
|
|
216
|
+
) {
|
|
217
|
+
return parsedModel.kind === "opus" || isFableOrMythos(parsedModel.kind)
|
|
218
|
+
? DEFAULT_REASONING_EFFORTS_WITH_XHIGH
|
|
219
|
+
: DEFAULT_REASONING_EFFORTS;
|
|
220
|
+
}
|
|
221
|
+
if (isOpenRouterAnthropicAdaptiveReasoningModel(parsedModel, spec)) {
|
|
222
|
+
return DEFAULT_REASONING_EFFORTS_WITH_XHIGH;
|
|
223
|
+
}
|
|
224
|
+
return inferFallbackEfforts(spec, compat);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function inferFallbackEfforts<TApi extends Api>(spec: ModelSpec<TApi>, compat: CompatOf<TApi>): readonly Effort[] {
|
|
228
|
+
if (spec.api === "anthropic-messages") {
|
|
229
|
+
return DEFAULT_REASONING_EFFORTS_WITH_XHIGH;
|
|
230
|
+
}
|
|
231
|
+
if (spec.name.includes("deepseek-v4")) {
|
|
232
|
+
return DEFAULT_REASONING_EFFORTS_WITH_XHIGH;
|
|
233
|
+
}
|
|
234
|
+
if (spec.api === "bedrock-converse-stream") {
|
|
235
|
+
return DEFAULT_REASONING_EFFORTS;
|
|
236
|
+
}
|
|
237
|
+
if (spec.api === "openai-completions") {
|
|
238
|
+
const resolved = compat as ResolvedOpenAICompat;
|
|
239
|
+
if (resolved.thinkingFormat === "openai" && resolved.supportsReasoningEffort) {
|
|
240
|
+
return DEFAULT_REASONING_EFFORTS_WITH_XHIGH;
|
|
241
|
+
}
|
|
242
|
+
return DEFAULT_REASONING_EFFORTS;
|
|
243
|
+
}
|
|
244
|
+
// OpenAI Responses APIs encode discrete effort levels, including xhigh.
|
|
245
|
+
if (spec.api === "openai-responses" || spec.api === "openai-codex-responses") {
|
|
246
|
+
return DEFAULT_REASONING_EFFORTS_WITH_XHIGH;
|
|
247
|
+
}
|
|
248
|
+
return DEFAULT_REASONING_EFFORTS;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function inferThinkingControlMode<TApi extends Api>(
|
|
252
|
+
spec: ModelSpec<TApi>,
|
|
253
|
+
parsedModel: ParsedModel,
|
|
254
|
+
): ThinkingConfig["mode"] {
|
|
255
|
+
switch (spec.api) {
|
|
256
|
+
case "google-generative-ai":
|
|
257
|
+
case "google-gemini-cli":
|
|
258
|
+
case "google-vertex":
|
|
259
|
+
return parsedModel.family === "gemini" &&
|
|
260
|
+
semverGte(parsedModel.version, "3.0") &&
|
|
261
|
+
parsedModel.version.major === 3
|
|
262
|
+
? "google-level"
|
|
263
|
+
: "budget";
|
|
264
|
+
|
|
265
|
+
case "anthropic-messages":
|
|
266
|
+
if (parsedModel.family === "anthropic") {
|
|
267
|
+
if (semverGte(parsedModel.version, "4.6")) {
|
|
268
|
+
return "anthropic-adaptive";
|
|
269
|
+
}
|
|
270
|
+
if (semverGte(parsedModel.version, "4.5")) {
|
|
271
|
+
return "anthropic-budget-effort";
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return "budget";
|
|
275
|
+
|
|
276
|
+
case "bedrock-converse-stream":
|
|
277
|
+
if (parsedModel.family === "anthropic") {
|
|
278
|
+
if (
|
|
279
|
+
semverGte(parsedModel.version, "4.6") &&
|
|
280
|
+
(parsedModel.kind === "opus" || isFableOrMythos(parsedModel.kind))
|
|
281
|
+
) {
|
|
282
|
+
return "anthropic-adaptive";
|
|
283
|
+
}
|
|
284
|
+
if (semverGte(parsedModel.version, "4.5")) {
|
|
285
|
+
return "anthropic-budget-effort";
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return "budget";
|
|
289
|
+
|
|
290
|
+
default:
|
|
291
|
+
return "effort";
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function isOpenRouterAnthropicAdaptiveReasoningModel<TApi extends Api>(
|
|
296
|
+
parsedModel: AnthropicModel,
|
|
297
|
+
spec: ModelSpec<TApi>,
|
|
298
|
+
): boolean {
|
|
299
|
+
if (spec.api !== "openai-completions") return false;
|
|
300
|
+
if (!modelMatchesHost(spec, "openrouter")) return false;
|
|
301
|
+
return isFableOrMythos(parsedModel.kind) || (parsedModel.kind === "opus" && semverGte(parsedModel.version, "4.6"));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Opus 4.7+ and Fable/Mythos on the Messages API expose the full five-tier
|
|
306
|
+
* adaptive scale (low/medium/high/xhigh/max). Bedrock Converse stays on the
|
|
307
|
+
* four-tier scale regardless of model version.
|
|
308
|
+
*/
|
|
309
|
+
function anthropicModelHasRealXHighEffort<TApi extends Api>(spec: ModelSpec<TApi>, parsedModel: ParsedModel): boolean {
|
|
310
|
+
if (spec.api !== "anthropic-messages") return false;
|
|
311
|
+
if (parsedModel.family !== "anthropic") return false;
|
|
312
|
+
if (isFableOrMythos(parsedModel.kind)) return true;
|
|
313
|
+
return parsedModel.kind === "opus" && semverGte(parsedModel.version, "4.7");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ---------------------------------------------------------------------------
|
|
317
|
+
// Runtime helpers (field reads only — safe per request)
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Returns the supported thinking efforts declared on the model metadata.
|
|
322
|
+
* Empty for non-reasoning models and for reasoning models without a
|
|
323
|
+
* controllable effort surface (`thinking: undefined`).
|
|
324
|
+
*/
|
|
325
|
+
export function getSupportedEfforts<TApi extends Api>(model: ApiModel<TApi>): readonly Effort[] {
|
|
326
|
+
if (!model.reasoning) {
|
|
327
|
+
return [];
|
|
328
|
+
}
|
|
329
|
+
return model.thinking?.efforts ?? [];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Clamps a requested thinking level against explicit model metadata.
|
|
334
|
+
*
|
|
335
|
+
* Non-reasoning models always resolve to `undefined`.
|
|
336
|
+
*/
|
|
337
|
+
export function clampThinkingLevelForModel<TApi extends Api>(
|
|
338
|
+
model: ApiModel<TApi> | undefined,
|
|
339
|
+
requested: Effort | undefined,
|
|
340
|
+
): Effort | undefined {
|
|
341
|
+
if (!model) {
|
|
342
|
+
return requested;
|
|
343
|
+
}
|
|
344
|
+
if (!model.reasoning || requested === undefined) {
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const levels = getSupportedEfforts(model);
|
|
349
|
+
if (levels.includes(requested)) {
|
|
350
|
+
return requested;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const requestedIndex = THINKING_EFFORTS.indexOf(requested);
|
|
354
|
+
if (requestedIndex === -1) {
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
let clamped: Effort | undefined;
|
|
359
|
+
for (const effort of levels) {
|
|
360
|
+
if (THINKING_EFFORTS.indexOf(effort) > requestedIndex) {
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
clamped = effort;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return clamped ?? levels[0];
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function requireSupportedEffort<TApi extends Api>(model: ApiModel<TApi>, effort: Effort): Effort {
|
|
370
|
+
if (!model.reasoning) {
|
|
371
|
+
throw new Error(`Model ${model.provider}/${model.id} does not support thinking`);
|
|
372
|
+
}
|
|
373
|
+
const levels = getSupportedEfforts(model);
|
|
374
|
+
if (!levels.includes(effort)) {
|
|
375
|
+
throw new Error(
|
|
376
|
+
`Thinking effort ${effort} is not supported by ${model.provider}/${model.id}. Supported efforts: ${levels.join(", ")}`,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return effort;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** Maps a normalized thinking effort to Google's `thinkingLevel` enum values. */
|
|
383
|
+
export function mapEffortToGoogleThinkingLevel(effort: Effort): "MINIMAL" | "LOW" | "MEDIUM" | "HIGH" {
|
|
384
|
+
switch (effort) {
|
|
385
|
+
case Effort.Minimal:
|
|
386
|
+
return "MINIMAL";
|
|
387
|
+
case Effort.Low:
|
|
388
|
+
return "LOW";
|
|
389
|
+
case Effort.Medium:
|
|
390
|
+
return "MEDIUM";
|
|
391
|
+
case Effort.High:
|
|
392
|
+
case Effort.XHigh:
|
|
393
|
+
return "HIGH";
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Maps a normalized thinking effort to Anthropic adaptive effort values via
|
|
399
|
+
* the model's baked `thinking.effortMap` (identity for unmapped efforts).
|
|
400
|
+
*/
|
|
401
|
+
export function mapEffortToAnthropicAdaptiveEffort<TApi extends Api>(
|
|
402
|
+
model: ApiModel<TApi>,
|
|
403
|
+
effort: Effort,
|
|
404
|
+
): "low" | "medium" | "high" | "xhigh" | "max" {
|
|
405
|
+
const supported = requireSupportedEffort(model, effort);
|
|
406
|
+
return (model.thinking?.effortMap?.[supported] ?? supported) as "low" | "medium" | "high" | "xhigh" | "max";
|
|
407
|
+
}
|