@oh-my-pi/pi-catalog 16.2.8 → 16.2.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 +33 -0
- package/dist/types/discovery/openai-compatible.d.ts +3 -1
- package/dist/types/identity/classify.d.ts +7 -0
- package/dist/types/identity/family.d.ts +8 -7
- package/dist/types/provider-models/descriptors.d.ts +4 -0
- package/dist/types/provider-models/openai-compat.d.ts +3 -2
- package/package.json +3 -3
- package/src/compat/openai.ts +20 -11
- package/src/discovery/openai-compatible.ts +51 -18
- package/src/identity/classify.ts +14 -0
- package/src/identity/family.ts +12 -14
- package/src/model-thinking.ts +10 -15
- package/src/models.json +564 -737
- package/src/provider-models/descriptors.ts +2 -0
- package/src/provider-models/openai-compat.ts +80 -35
- package/src/variant-collapse.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [16.2.11] - 2026-07-01
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed a potential memory leak caused by dangling timeout timers during model discovery in OpenAI-compatible, vLLM, LiteLLM, and LM Studio catalogs.
|
|
10
|
+
- Widened stream watchdogs for local OpenAI-compatible backends (including llama.cpp, LM Studio, vLLM, and Ollama) to prevent premature timeouts during cold model loads.
|
|
11
|
+
|
|
12
|
+
## [16.2.10] - 2026-06-30
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Added Claude Sonnet 3.7, Claude Opus 3, and Claude Sonnet 3 model entries to the Anthropic catalog
|
|
17
|
+
- Added Anthropic Claude Sonnet 5 model entry to the Kilo provider catalog
|
|
18
|
+
- Added first-party catalog discovery support for the Anthropic provider
|
|
19
|
+
- Added Gemini 3.1 Flash Lite Image model entry to the Kilo provider catalog
|
|
20
|
+
- Added Anthropic Claude Sonnet 5 model variants with low, medium, high, xhigh, and max thinking efforts to the Devin provider catalog
|
|
21
|
+
- Added Claude Sonnet 5 model entry to the Anthropic curated catalog.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Updated the base API URL for the Claude Sonnet 5 model in the Anthropic catalog
|
|
26
|
+
- Updated pricing metrics for DeepSeek R1 and DeepSeek V3 model entries to reflect new rates
|
|
27
|
+
|
|
28
|
+
## [16.2.9] - 2026-06-30
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- Added full capability support for Claude Sonnet 5, aligning it with Claude Opus 4.8 and Fable 5. This includes adaptive thinking display, mid-conversation system messages, sampling parameter and thinking omission API restrictions, and 5-tier adaptive reasoning effort mapping (including xhigh and max levels) across direct APIs, OpenRouter, and Bedrock Converse.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Updated input and output costs for models in the catalog.
|
|
37
|
+
|
|
5
38
|
## [16.2.7] - 2026-06-30
|
|
6
39
|
|
|
7
40
|
### Fixed
|
|
@@ -48,8 +48,10 @@ export interface FetchOpenAICompatibleModelsOptions<TApi extends Api> {
|
|
|
48
48
|
apiKey?: string;
|
|
49
49
|
/** Additional request headers. */
|
|
50
50
|
headers?: Record<string, string>;
|
|
51
|
-
/** Optional AbortSignal for request cancellation. */
|
|
51
|
+
/** Optional AbortSignal for request cancellation; caller owns its lifecycle. */
|
|
52
52
|
signal?: AbortSignal;
|
|
53
|
+
/** Optional cancellable request timeout used when `signal` is omitted. */
|
|
54
|
+
timeoutMs?: number;
|
|
53
55
|
/** Optional fetch implementation override for testing/custom runtimes. */
|
|
54
56
|
fetch?: FetchImpl;
|
|
55
57
|
/**
|
|
@@ -56,6 +56,13 @@ export declare const parseOpenAIModel: (modelId: string) => OpenAIModel | null;
|
|
|
56
56
|
*/
|
|
57
57
|
export declare const parseGlmModel: (modelId: string) => GlmModel | null;
|
|
58
58
|
export declare function isFableOrMythos(kind: AnthropicKind): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Returns true if the parsed Anthropic model is part of the adaptive-thinking
|
|
61
|
+
* Claude generation at or above a specific capability threshold.
|
|
62
|
+
* - Opus has a configurable minimum version floor (e.g. "4.6", "4.7", "4.8").
|
|
63
|
+
* - Sonnet, Fable, and Mythos all require version 5 or higher.
|
|
64
|
+
*/
|
|
65
|
+
export declare function isAnthropicAdaptiveGenAtLeast(parsed: AnthropicModel, opusMin: "4.6" | "4.7" | "4.8"): boolean;
|
|
59
66
|
export declare function parseSemVer(version: string): SemVer | null;
|
|
60
67
|
export declare function semverGte(left: SemVer | string, right: SemVer | string): boolean;
|
|
61
68
|
export declare function semverEqual(left: SemVer | string, right: SemVer | string): boolean;
|
|
@@ -85,15 +85,16 @@ export declare const isGlmVisionModelId: (modelId: string) => boolean;
|
|
|
85
85
|
*/
|
|
86
86
|
export declare const modelFamilyToken: (modelId: string) => string;
|
|
87
87
|
/**
|
|
88
|
-
* Adaptive thinking `display` is supported starting with Claude Opus 4.7
|
|
89
|
-
* the Claude Fable/Mythos 5 generation. Older adaptive-thinking
|
|
90
|
-
* (Opus 4.6, Sonnet 4.6
|
|
91
|
-
* dashed version forms both match while bare dated ids
|
|
88
|
+
* Adaptive thinking `display` is supported starting with Claude Opus 4.7+,
|
|
89
|
+
* Sonnet 5+, and the Claude Fable/Mythos 5 generation. Older adaptive-thinking
|
|
90
|
+
* models (Opus 4.6, Sonnet 4.6) reject the field. Classifier-based, so dotted
|
|
91
|
+
* and dashed version forms both match while bare dated ids
|
|
92
92
|
* (`claude-opus-4-20250514` = Opus 4.0) stay excluded.
|
|
93
93
|
*/
|
|
94
94
|
export declare const supportsAdaptiveThinkingDisplay: (modelId: string) => boolean;
|
|
95
95
|
/**
|
|
96
|
-
* Returns true for Anthropic models with Opus 4.7
|
|
96
|
+
* Returns true for Anthropic models with Opus 4.7+, Sonnet 5+, and Fable/Mythos 5+
|
|
97
|
+
* API restrictions:
|
|
97
98
|
* - Sampling parameters (temperature/top_p/top_k) return 400 error
|
|
98
99
|
* - Thinking content is omitted by default (needs display: "summarized")
|
|
99
100
|
*/
|
|
@@ -101,8 +102,8 @@ export declare const hasOpus47ApiRestrictions: (modelId: string) => boolean;
|
|
|
101
102
|
/**
|
|
102
103
|
* Mid-conversation `role: "system"` messages (system instructions appended at
|
|
103
104
|
* non-first positions in the `messages` array) are supported starting with
|
|
104
|
-
* Claude Opus 4.8 and the Claude Fable/Mythos 5 generation.
|
|
105
|
-
* models reject the role.
|
|
105
|
+
* Claude Opus 4.8+, Sonnet 5+, and the Claude Fable/Mythos 5 generation.
|
|
106
|
+
* Earlier Claude models reject the role.
|
|
106
107
|
* @see https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages
|
|
107
108
|
*/
|
|
108
109
|
export declare const supportsMidConversationSystemMessages: (modelId: string) => boolean;
|
|
@@ -29,7 +29,11 @@ export declare const CATALOG_PROVIDERS: readonly [{
|
|
|
29
29
|
}, {
|
|
30
30
|
readonly id: "anthropic";
|
|
31
31
|
readonly defaultModel: "claude-opus-4-8";
|
|
32
|
+
readonly envVars: readonly ["ANTHROPIC_API_KEY"];
|
|
32
33
|
readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"anthropic-messages", unknown>;
|
|
34
|
+
readonly catalogDiscovery: {
|
|
35
|
+
readonly label: "Anthropic";
|
|
36
|
+
};
|
|
33
37
|
}, {
|
|
34
38
|
readonly id: "azure";
|
|
35
39
|
readonly defaultModel: "gpt-5.5";
|
|
@@ -29,8 +29,8 @@ export interface ModelsDevModel {
|
|
|
29
29
|
* first-party `/v1/models` endpoint but that models.dev has not catalogued yet.
|
|
30
30
|
* Seeded into model generation so the bundled catalog is never gated on
|
|
31
31
|
* models.dev's update cadence; deduped behind upstream catalog / models.dev
|
|
32
|
-
* entries once those appear. Token limits and pricing are pinned
|
|
33
|
-
*
|
|
32
|
+
* entries once those appear. Token limits and pricing are pinned either directly or
|
|
33
|
+
* in `applyAnthropicCatalogPolicy`, and `thinking` is re-baked
|
|
34
34
|
* by the generator's policy pass (scripts/generated-policies.ts).
|
|
35
35
|
*/
|
|
36
36
|
export declare const ANTHROPIC_CURATED_FALLBACK_MODELS: readonly ModelSpec<"anthropic-messages">[];
|
|
@@ -371,6 +371,7 @@ export interface FetchLiteLLMRichModelsOptions<TApi extends Api> {
|
|
|
371
371
|
headers?: Record<string, string>;
|
|
372
372
|
fetch?: FetchImpl;
|
|
373
373
|
signal?: AbortSignal;
|
|
374
|
+
timeoutMs?: number;
|
|
374
375
|
referenceResolver?: (modelId: string) => ModelSpec<TApi> | undefined;
|
|
375
376
|
}
|
|
376
377
|
export declare const OPENAI_COMPAT_DISCOVERY_DEFAULT_CONTEXT_WINDOW = 128000;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-catalog",
|
|
4
|
-
"version": "16.2.
|
|
4
|
+
"version": "16.2.11",
|
|
5
5
|
"description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -34,12 +34,12 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@bufbuild/protobuf": "^2.12.0",
|
|
37
|
-
"@oh-my-pi/pi-utils": "16.2.
|
|
37
|
+
"@oh-my-pi/pi-utils": "16.2.11",
|
|
38
38
|
"arktype": "^2.2.0",
|
|
39
39
|
"zod": "^4"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@oh-my-pi/pi-ai": "16.2.
|
|
42
|
+
"@oh-my-pi/pi-ai": "16.2.11",
|
|
43
43
|
"@types/bun": "^1.3.14"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
package/src/compat/openai.ts
CHANGED
|
@@ -57,6 +57,8 @@ function matchesKimiK27CodeFamily(spec: ModelSpec<"openai-completions">): boolea
|
|
|
57
57
|
const XIAOMI_MIMO_STREAM_IDLE_TIMEOUT_MS = 300_000;
|
|
58
58
|
/** Alibaba Coding Plan (coding-intl.dashscope) qwen models idle before the first event (issue #1770). */
|
|
59
59
|
const ALIBABA_CODING_PLAN_STREAM_IDLE_TIMEOUT_MS = 600_000;
|
|
60
|
+
/** Local OpenAI-compatible backends can spend minutes cold-loading a model before the first SSE event. */
|
|
61
|
+
const LOCAL_OPENAI_COMPAT_STREAM_IDLE_TIMEOUT_MS = 300_000;
|
|
60
62
|
const MINIMAX_PROVIDER_OR_ID_PATTERN = /minimax/i;
|
|
61
63
|
const DSML_HEALING_PROVIDERS = new Set([
|
|
62
64
|
"ollama",
|
|
@@ -294,6 +296,9 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
294
296
|
isMoonshotNative ||
|
|
295
297
|
isOpenCodeHost;
|
|
296
298
|
const isOpenCodeProvider = provider === "opencode-go" || provider === "opencode-zen";
|
|
299
|
+
const isLocalOpenAICompatBackend =
|
|
300
|
+
!PROXY_OPENAI_COMPAT_PROVIDERS.has(provider) &&
|
|
301
|
+
(LOCAL_OPENAI_COMPAT_PROVIDERS.has(provider) || hasLocalLoopbackBaseUrl(baseUrl));
|
|
297
302
|
|
|
298
303
|
const useMaxTokens =
|
|
299
304
|
isMistral ||
|
|
@@ -348,9 +353,10 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
348
353
|
isCopilotHost ||
|
|
349
354
|
isZenmuxHost);
|
|
350
355
|
|
|
351
|
-
// Stream-watchdog floor: GLM coding-plan SKUs, Kimi K2.6,
|
|
352
|
-
// DeepSeek reasoning models
|
|
353
|
-
//
|
|
356
|
+
// Stream-watchdog floor: GLM coding-plan SKUs, Kimi K2.6, direct
|
|
357
|
+
// DeepSeek reasoning models, and local OpenAI-compatible backends can idle
|
|
358
|
+
// for minutes while reasoning or cold-loading weights; widen the idle
|
|
359
|
+
// timeout so warm-ups stop aborting and retrying.
|
|
354
360
|
const streamIdleTimeoutMs =
|
|
355
361
|
GLM_CODING_PLAN_MODEL_PATTERN.test(spec.id) && (isZai || isZhipu)
|
|
356
362
|
? GLM_CODING_PLAN_STREAM_IDLE_TIMEOUT_MS
|
|
@@ -362,7 +368,9 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
362
368
|
? KIMI_K26_REASONING_STREAM_IDLE_TIMEOUT_MS
|
|
363
369
|
: spec.reasoning && isDirectDeepseekApi
|
|
364
370
|
? DEEPSEEK_REASONING_STREAM_IDLE_TIMEOUT_MS
|
|
365
|
-
:
|
|
371
|
+
: isLocalOpenAICompatBackend
|
|
372
|
+
? LOCAL_OPENAI_COMPAT_STREAM_IDLE_TIMEOUT_MS
|
|
373
|
+
: undefined;
|
|
366
374
|
|
|
367
375
|
// Fireworks "Fast" variants (`<id>-fast`) are served from the router
|
|
368
376
|
// namespace (`accounts/fireworks/routers/<id>-fast`), like Fire Pass, rather
|
|
@@ -482,9 +490,7 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
482
490
|
// when a thinking block actually exists on the turn
|
|
483
491
|
// (`nonEmptyThinkingBlocks.length > 0`), so the flag is a no-op on
|
|
484
492
|
// pure-text histories.
|
|
485
|
-
replayReasoningContent:
|
|
486
|
-
!PROXY_OPENAI_COMPAT_PROVIDERS.has(provider) &&
|
|
487
|
-
(LOCAL_OPENAI_COMPAT_PROVIDERS.has(provider) || hasLocalLoopbackBaseUrl(baseUrl)),
|
|
493
|
+
replayReasoningContent: isLocalOpenAICompatBackend,
|
|
488
494
|
// `preserve_thinking: true` makes the Qwen3.6+ chat template render
|
|
489
495
|
// `<think>...</think>` for older assistant turns too, instead of
|
|
490
496
|
// stripping it the moment a new user message moves them past
|
|
@@ -496,9 +502,7 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
496
502
|
// with `replayReasoningContent` above). Non-Qwen templates ignore the
|
|
497
503
|
// parameter, so the flag stays a no-op outside the Qwen path.
|
|
498
504
|
qwenPreserveThinking:
|
|
499
|
-
(thinkingFormat === "qwen" || thinkingFormat === "qwen-chat-template") &&
|
|
500
|
-
!PROXY_OPENAI_COMPAT_PROVIDERS.has(provider) &&
|
|
501
|
-
(LOCAL_OPENAI_COMPAT_PROVIDERS.has(provider) || hasLocalLoopbackBaseUrl(baseUrl)),
|
|
505
|
+
(thinkingFormat === "qwen" || thinkingFormat === "qwen-chat-template") && isLocalOpenAICompatBackend,
|
|
502
506
|
requiresAssistantContentForToolCalls: isKimiModel || isDirectDeepseekReasoning,
|
|
503
507
|
cacheControlFormat: isOpenRouter && spec.id.startsWith("anthropic/") ? "anthropic" : undefined,
|
|
504
508
|
openRouterRouting: undefined,
|
|
@@ -584,6 +588,9 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol
|
|
|
584
588
|
const isAnthropicModel = id ? isClaudeModelId(id) || isAnthropicNamespacedModelId(id) : false;
|
|
585
589
|
const isDeepseekFamily = id ? isDeepseekModelIdOrName(id) || isDeepseekModelIdOrName(spec.name) : false;
|
|
586
590
|
const reasoningCapable = Boolean(spec.reasoning);
|
|
591
|
+
const isLocalOpenAICompatBackend =
|
|
592
|
+
!PROXY_OPENAI_COMPAT_PROVIDERS.has(spec.provider) &&
|
|
593
|
+
(LOCAL_OPENAI_COMPAT_PROVIDERS.has(spec.provider) || hasLocalLoopbackBaseUrl(baseUrl));
|
|
587
594
|
|
|
588
595
|
const compat: ResolvedOpenAIResponsesCompat = {
|
|
589
596
|
supportsDeveloperRole: isAzure || isOpenAIUrl || hostMatchesUrl(baseUrl, "githubCopilot"),
|
|
@@ -645,7 +652,9 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol
|
|
|
645
652
|
emptyLengthFinishIsContextError: spec.provider === "ollama",
|
|
646
653
|
usesOpenAIToolCallIdLimit: spec.provider === "openai",
|
|
647
654
|
promptCacheSessionHeader: spec.provider === "xai-oauth" ? "x-grok-conv-id" : undefined,
|
|
648
|
-
streamIdleTimeoutMs:
|
|
655
|
+
streamIdleTimeoutMs: isLocalOpenAICompatBackend
|
|
656
|
+
? LOCAL_OPENAI_COMPAT_STREAM_IDLE_TIMEOUT_MS
|
|
657
|
+
: spec.compat?.streamIdleTimeoutMs,
|
|
649
658
|
};
|
|
650
659
|
applyCompatOverrides(compat, spec.compat);
|
|
651
660
|
if (spec.compat?.reasoningDisableMode === undefined) {
|
|
@@ -3,6 +3,27 @@ import type { Api, FetchImpl, ModelSpec, Provider } from "../types";
|
|
|
3
3
|
|
|
4
4
|
const MODELS_PATH = "/models";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Uses a cancellable timer rather than the native abort-timeout helper so
|
|
8
|
+
* successful fast discovery requests do not leave armed timeout signals for
|
|
9
|
+
* concurrent GC to trip over later.
|
|
10
|
+
*/
|
|
11
|
+
async function withOpenAICompatibleDiscoveryTimeout<T>(
|
|
12
|
+
timeoutMs: number,
|
|
13
|
+
run: (signal: AbortSignal) => Promise<T>,
|
|
14
|
+
): Promise<T> {
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
const timer = setTimeout(
|
|
17
|
+
() => controller.abort(new DOMException("The operation timed out.", "TimeoutError")),
|
|
18
|
+
timeoutMs,
|
|
19
|
+
);
|
|
20
|
+
try {
|
|
21
|
+
return await run(controller.signal);
|
|
22
|
+
} finally {
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
6
27
|
/**
|
|
7
28
|
* Minimal OpenAI-style model entry shape consumed by discovery.
|
|
8
29
|
*
|
|
@@ -72,8 +93,10 @@ export interface FetchOpenAICompatibleModelsOptions<TApi extends Api> {
|
|
|
72
93
|
apiKey?: string;
|
|
73
94
|
/** Additional request headers. */
|
|
74
95
|
headers?: Record<string, string>;
|
|
75
|
-
/** Optional AbortSignal for request cancellation. */
|
|
96
|
+
/** Optional AbortSignal for request cancellation; caller owns its lifecycle. */
|
|
76
97
|
signal?: AbortSignal;
|
|
98
|
+
/** Optional cancellable request timeout used when `signal` is omitted. */
|
|
99
|
+
timeoutMs?: number;
|
|
77
100
|
/** Optional fetch implementation override for testing/custom runtimes. */
|
|
78
101
|
fetch?: FetchImpl;
|
|
79
102
|
/**
|
|
@@ -115,25 +138,35 @@ export async function fetchOpenAICompatibleModels<TApi extends Api>(
|
|
|
115
138
|
}
|
|
116
139
|
|
|
117
140
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
141
|
+
const fetchPayload = async (signal?: AbortSignal): Promise<unknown | null> => {
|
|
142
|
+
let response: Response;
|
|
143
|
+
try {
|
|
144
|
+
response = await fetchImpl(`${baseUrl}${MODELS_PATH}`, {
|
|
145
|
+
method: "GET",
|
|
146
|
+
headers: requestHeaders,
|
|
147
|
+
signal,
|
|
148
|
+
});
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
128
152
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
132
156
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
157
|
+
try {
|
|
158
|
+
return await response.json();
|
|
159
|
+
} catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
const payload =
|
|
164
|
+
options.signal !== undefined
|
|
165
|
+
? await fetchPayload(options.signal)
|
|
166
|
+
: options.timeoutMs !== undefined
|
|
167
|
+
? await withOpenAICompatibleDiscoveryTimeout(options.timeoutMs, fetchPayload)
|
|
168
|
+
: await fetchPayload();
|
|
169
|
+
if (payload === null) {
|
|
137
170
|
return null;
|
|
138
171
|
}
|
|
139
172
|
|
package/src/identity/classify.ts
CHANGED
|
@@ -158,6 +158,20 @@ export function isFableOrMythos(kind: AnthropicKind): boolean {
|
|
|
158
158
|
return kind === "fable" || kind === "mythos";
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Returns true if the parsed Anthropic model is part of the adaptive-thinking
|
|
163
|
+
* Claude generation at or above a specific capability threshold.
|
|
164
|
+
* - Opus has a configurable minimum version floor (e.g. "4.6", "4.7", "4.8").
|
|
165
|
+
* - Sonnet, Fable, and Mythos all require version 5 or higher.
|
|
166
|
+
*/
|
|
167
|
+
export function isAnthropicAdaptiveGenAtLeast(parsed: AnthropicModel, opusMin: "4.6" | "4.7" | "4.8"): boolean {
|
|
168
|
+
if (parsed.kind === "opus") {
|
|
169
|
+
return semverGte(parsed.version, opusMin);
|
|
170
|
+
}
|
|
171
|
+
// Sonnet 5+, Fable 5+, Mythos 5+, and any future gen-5+ models
|
|
172
|
+
return semverGte(parsed.version, "5");
|
|
173
|
+
}
|
|
174
|
+
|
|
161
175
|
function createSemVer(major: number, minor: number, patch = 0): SemVer {
|
|
162
176
|
return { major, minor, patch };
|
|
163
177
|
}
|
package/src/identity/family.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
bareModelId,
|
|
12
|
+
isAnthropicAdaptiveGenAtLeast,
|
|
12
13
|
isFableOrMythos,
|
|
13
14
|
parseAnthropicModel,
|
|
14
15
|
parseGlmModel,
|
|
@@ -201,41 +202,38 @@ export const modelFamilyToken = memo((modelId: string): string => {
|
|
|
201
202
|
});
|
|
202
203
|
|
|
203
204
|
/**
|
|
204
|
-
* Adaptive thinking `display` is supported starting with Claude Opus 4.7
|
|
205
|
-
* the Claude Fable/Mythos 5 generation. Older adaptive-thinking
|
|
206
|
-
* (Opus 4.6, Sonnet 4.6
|
|
207
|
-
* dashed version forms both match while bare dated ids
|
|
205
|
+
* Adaptive thinking `display` is supported starting with Claude Opus 4.7+,
|
|
206
|
+
* Sonnet 5+, and the Claude Fable/Mythos 5 generation. Older adaptive-thinking
|
|
207
|
+
* models (Opus 4.6, Sonnet 4.6) reject the field. Classifier-based, so dotted
|
|
208
|
+
* and dashed version forms both match while bare dated ids
|
|
208
209
|
* (`claude-opus-4-20250514` = Opus 4.0) stay excluded.
|
|
209
210
|
*/
|
|
210
211
|
export const supportsAdaptiveThinkingDisplay = memo((modelId: string): boolean => {
|
|
211
212
|
const parsed = parseAnthropicModel(bareModelId(modelId));
|
|
212
|
-
|
|
213
|
-
if (isFableOrMythos(parsed.kind)) return semverGte(parsed.version, "5");
|
|
214
|
-
return parsed.kind === "opus" && semverGte(parsed.version, "4.7");
|
|
213
|
+
return parsed !== null && isAnthropicAdaptiveGenAtLeast(parsed, "4.7");
|
|
215
214
|
});
|
|
216
215
|
|
|
217
216
|
/**
|
|
218
|
-
* Returns true for Anthropic models with Opus 4.7
|
|
217
|
+
* Returns true for Anthropic models with Opus 4.7+, Sonnet 5+, and Fable/Mythos 5+
|
|
218
|
+
* API restrictions:
|
|
219
219
|
* - Sampling parameters (temperature/top_p/top_k) return 400 error
|
|
220
220
|
* - Thinking content is omitted by default (needs display: "summarized")
|
|
221
221
|
*/
|
|
222
222
|
export const hasOpus47ApiRestrictions = memo((modelId: string): boolean => {
|
|
223
223
|
const parsed = parseAnthropicModel(bareModelId(modelId));
|
|
224
|
-
|
|
225
|
-
return (parsed.kind === "opus" && semverGte(parsed.version, "4.7")) || isFableOrMythos(parsed.kind);
|
|
224
|
+
return parsed !== null && isAnthropicAdaptiveGenAtLeast(parsed, "4.7");
|
|
226
225
|
});
|
|
227
226
|
|
|
228
227
|
/**
|
|
229
228
|
* Mid-conversation `role: "system"` messages (system instructions appended at
|
|
230
229
|
* non-first positions in the `messages` array) are supported starting with
|
|
231
|
-
* Claude Opus 4.8 and the Claude Fable/Mythos 5 generation.
|
|
232
|
-
* models reject the role.
|
|
230
|
+
* Claude Opus 4.8+, Sonnet 5+, and the Claude Fable/Mythos 5 generation.
|
|
231
|
+
* Earlier Claude models reject the role.
|
|
233
232
|
* @see https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages
|
|
234
233
|
*/
|
|
235
234
|
export const supportsMidConversationSystemMessages = memo((modelId: string): boolean => {
|
|
236
235
|
const parsed = parseAnthropicModel(bareModelId(modelId));
|
|
237
|
-
|
|
238
|
-
return (parsed.kind === "opus" && semverGte(parsed.version, "4.8")) || isFableOrMythos(parsed.kind);
|
|
236
|
+
return parsed !== null && isAnthropicAdaptiveGenAtLeast(parsed, "4.8");
|
|
239
237
|
});
|
|
240
238
|
|
|
241
239
|
export const isAnthropicFableOrMythosModel = memo((modelId: string): boolean => {
|
package/src/model-thinking.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type AnthropicModel,
|
|
13
13
|
bareModelId,
|
|
14
14
|
type GeminiModel,
|
|
15
|
-
|
|
15
|
+
isAnthropicAdaptiveGenAtLeast,
|
|
16
16
|
type OpenAIModel,
|
|
17
17
|
type ParsedModel,
|
|
18
18
|
parseAnthropicModel,
|
|
@@ -441,12 +441,11 @@ function isDeepseekReasoningModel<TApi extends Api>(spec: ModelSpec<TApi>): bool
|
|
|
441
441
|
function getOpenRouterAnthropicReasoningEffortMap(modelId: string): EffortMap | undefined {
|
|
442
442
|
const parsed = parseAnthropicModel(bareModelId(modelId));
|
|
443
443
|
if (!parsed) return undefined;
|
|
444
|
-
// Adaptive efforts on OpenRouter's completions front: Fable/Mythos
|
|
445
|
-
// Opus 4.6+ only — Sonnet
|
|
446
|
-
|
|
447
|
-
if (!isFableOrMythos(parsed.kind) && !isOpusAdaptive) return undefined;
|
|
444
|
+
// Adaptive efforts on OpenRouter's completions front: Fable/Mythos, Sonnet 5+,
|
|
445
|
+
// and Opus 4.6+ only — older Sonnet versions stay on the plain effort vocabulary there.
|
|
446
|
+
if (!isAnthropicAdaptiveGenAtLeast(parsed, "4.6")) return undefined;
|
|
448
447
|
|
|
449
|
-
const hasRealXHigh =
|
|
448
|
+
const hasRealXHigh = isAnthropicAdaptiveGenAtLeast(parsed, "4.7");
|
|
450
449
|
return hasRealXHigh ? ANTHROPIC_ADAPTIVE_EFFORT_MAP_5_TIER : ANTHROPIC_ADAPTIVE_EFFORT_MAP_4_TIER;
|
|
451
450
|
}
|
|
452
451
|
|
|
@@ -521,7 +520,7 @@ function inferAnthropicSupportedEfforts<TApi extends Api>(
|
|
|
521
520
|
(spec.api === "anthropic-messages" || spec.api === "bedrock-converse-stream") &&
|
|
522
521
|
semverGte(parsedModel.version, "4.6")
|
|
523
522
|
) {
|
|
524
|
-
return parsedModel
|
|
523
|
+
return isAnthropicAdaptiveGenAtLeast(parsedModel, "4.6")
|
|
525
524
|
? DEFAULT_REASONING_EFFORTS_WITH_XHIGH
|
|
526
525
|
: DEFAULT_REASONING_EFFORTS;
|
|
527
526
|
}
|
|
@@ -601,10 +600,7 @@ function inferThinkingControlMode<TApi extends Api>(
|
|
|
601
600
|
|
|
602
601
|
case "bedrock-converse-stream":
|
|
603
602
|
if (parsedModel.family === "anthropic") {
|
|
604
|
-
if (
|
|
605
|
-
semverGte(parsedModel.version, "4.6") &&
|
|
606
|
-
(parsedModel.kind === "opus" || isFableOrMythos(parsedModel.kind))
|
|
607
|
-
) {
|
|
603
|
+
if (isAnthropicAdaptiveGenAtLeast(parsedModel, "4.6")) {
|
|
608
604
|
return "anthropic-adaptive";
|
|
609
605
|
}
|
|
610
606
|
// Opus 4.5 on Bedrock metadata mirrors the direct-Anthropic
|
|
@@ -627,19 +623,18 @@ function isOpenRouterAnthropicAdaptiveReasoningModel<TApi extends Api>(
|
|
|
627
623
|
): boolean {
|
|
628
624
|
if (!isOpenAICompatReasoningApi(spec.api)) return false;
|
|
629
625
|
if (!modelMatchesHost(spec, "openrouter")) return false;
|
|
630
|
-
return
|
|
626
|
+
return isAnthropicAdaptiveGenAtLeast(parsedModel, "4.6");
|
|
631
627
|
}
|
|
632
628
|
|
|
633
629
|
/**
|
|
634
|
-
* Opus 4.7
|
|
630
|
+
* Opus 4.7+, Sonnet 5+, and Fable/Mythos 5+ on the Messages API expose the full five-tier
|
|
635
631
|
* adaptive scale (low/medium/high/xhigh/max). Bedrock Converse stays on the
|
|
636
632
|
* four-tier scale regardless of model version.
|
|
637
633
|
*/
|
|
638
634
|
function anthropicModelHasRealXHighEffort<TApi extends Api>(spec: ModelSpec<TApi>, parsedModel: ParsedModel): boolean {
|
|
639
635
|
if (spec.api !== "anthropic-messages") return false;
|
|
640
636
|
if (parsedModel.family !== "anthropic") return false;
|
|
641
|
-
|
|
642
|
-
return parsedModel.kind === "opus" && semverGte(parsedModel.version, "4.7");
|
|
637
|
+
return isAnthropicAdaptiveGenAtLeast(parsedModel, "4.7");
|
|
643
638
|
}
|
|
644
639
|
|
|
645
640
|
// ---------------------------------------------------------------------------
|