@oh-my-pi/pi-catalog 17.1.3 → 17.1.4
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 +13 -0
- package/dist/types/identity/family.d.ts +9 -0
- package/dist/types/model-cache.d.ts +1 -1
- package/dist/types/model-manager.d.ts +10 -1
- package/dist/types/provider-models/openai-compat.d.ts +2 -2
- package/dist/types/types.d.ts +3 -1
- package/dist/types/wire/alibaba-token-plan.d.ts +17 -1
- package/package.json +3 -3
- package/src/build.ts +34 -9
- package/src/compat/anthropic.ts +18 -4
- package/src/compat/bedrock.ts +12 -0
- package/src/identity/family.ts +13 -0
- package/src/model-cache.ts +15 -7
- package/src/model-manager.ts +42 -13
- package/src/models.json +5371 -2112
- package/src/provider-models/openai-compat.ts +25 -3
- package/src/types.ts +4 -1
- package/src/wire/alibaba-token-plan.ts +31 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.1.4] - 2026-07-26
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added Claude Opus 5 model entries for Amazon Bedrock: `anthropic.claude-opus-5` plus its `us.`, `eu.`, `au.`, and `global.` regional/geo IDs.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed `alibaba-token-plan` locking out China (Beijing) 百炼 Token Plan subscribers: the provider hardcoded the international Singapore endpoint, so Beijing-issued `sk-sp-` keys got `401 invalid_api_key`. The wire credential now carries an optional region base URL, and model discovery targets the credential's region ([#6682](https://github.com/can1357/oh-my-pi/issues/6682)).
|
|
14
|
+
- Fixed forced `tool_choice` 400s (`tool_choice 'specified' is incompatible with thinking enabled`) on Kimi Code's Anthropic-compatible endpoint for the `kimi-for-coding`, `kimi-for-coding-highspeed`, and `k3` aliases: the Anthropic-surface compat matcher only recognised Moonshot's native `kimi-k2.7-code*` ids, so thinking-locked kimi-code models kept `supportsForcedToolChoice: true` and the forced selector was sent to a host that always thinks. These models now resolve `requiresThinkingEnabled`, keeping thinking on and downgrading forced choices to `auto`.
|
|
15
|
+
- Retried empty successful provider discovery responses after the short non-authoritative interval instead of caching them for the full catalog TTL ([#6620](https://github.com/can1357/oh-my-pi/issues/6620)).
|
|
16
|
+
- Fixed GitHub Copilot Claude models with no bundled catalog reference (e.g. a freshly served `claude-opus-5`) discovering with `reasoning: false`/`thinking: null` and no effort dial, and disappearing along with their synthesized `-1m` sibling on offline reads: reference-less Copilot models on the anthropic-messages proxy now derive the adaptive reasoning ladder from the model id, and the cache restores their compile-time `COPILOT_API_HEADERS` by value instead of dropping them as unrestorable ([#6664](https://github.com/can1357/oh-my-pi/issues/6664)).
|
|
17
|
+
|
|
5
18
|
## [17.1.3] - 2026-07-24
|
|
6
19
|
|
|
7
20
|
### Fixed
|
|
@@ -119,6 +119,15 @@ export declare const isGlmVisionModelId: (modelId: string) => boolean;
|
|
|
119
119
|
* collapsed onto the single vendor token; use {@link parseKnownModel} for finer breakdowns.
|
|
120
120
|
*/
|
|
121
121
|
export declare const modelFamilyToken: (modelId: string) => string;
|
|
122
|
+
/**
|
|
123
|
+
* True for Claude generations that support extended thinking: Sonnet/Opus 3.7+,
|
|
124
|
+
* every 4.x/5+ Opus/Sonnet, and the Fable/Mythos generation. Pre-thinking
|
|
125
|
+
* models (Claude 3.5 and older) are excluded so no thinking effort dial is
|
|
126
|
+
* fabricated for a model that rejects thinking parameters. Classifier-based, so
|
|
127
|
+
* dotted and dashed version forms both match; ids the classifier does not parse
|
|
128
|
+
* (e.g. Haiku, bare dated ids) return false.
|
|
129
|
+
*/
|
|
130
|
+
export declare const anthropicModelSupportsThinking: (modelId: string) => boolean;
|
|
122
131
|
/**
|
|
123
132
|
* Adaptive thinking `display` is supported starting with Claude Opus 4.7+,
|
|
124
133
|
* Sonnet 5+, and the Claude Fable/Mythos 5 generation. Older adaptive-thinking
|
|
@@ -19,5 +19,5 @@ interface CacheEntry<TApi extends Api = Api> {
|
|
|
19
19
|
staticFingerprint: string;
|
|
20
20
|
}
|
|
21
21
|
export declare function readModelCache<TApi extends Api>(providerId: string, ttlMs: number, now: () => number, dbPath?: string): CacheEntry<TApi> | null;
|
|
22
|
-
export declare function writeModelCache<TApi extends Api>(providerId: string, updatedAt: number, models: Model<TApi>[], authoritative: boolean, staticFingerprint: string, dbPath?: string, staticHeaderSources?: readonly Model<TApi>[]): void;
|
|
22
|
+
export declare function writeModelCache<TApi extends Api>(providerId: string, updatedAt: number, models: Model<TApi>[], authoritative: boolean, staticFingerprint: string, dbPath?: string, staticHeaderSources?: readonly Model<TApi>[], restorableHeaderFallback?: Record<string, string>): void;
|
|
23
23
|
export {};
|
|
@@ -30,6 +30,14 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
|
|
|
30
30
|
dynamicModelsAuthoritative?: boolean;
|
|
31
31
|
/** Cached model ids to ignore when the cache was written against a different static catalog fingerprint. */
|
|
32
32
|
dropCachedModelIdsOnStaticMismatch?: readonly string[];
|
|
33
|
+
/**
|
|
34
|
+
* Trusted, provider-wide request headers (compile-time constants, never
|
|
35
|
+
* credentials) that the cache may restore by value for any model whose live
|
|
36
|
+
* headers matched them at write time. Lets header-bearing dynamic models
|
|
37
|
+
* without a bundled static entry survive offline reads instead of being
|
|
38
|
+
* dropped as unrestorable (e.g. GitHub Copilot's User-Agent + API version).
|
|
39
|
+
*/
|
|
40
|
+
restorableHeaderFallback?: Record<string, string>;
|
|
33
41
|
/** Optional dynamic endpoint fetcher. */
|
|
34
42
|
fetchDynamicModels?: () => Promise<readonly ModelSpec<TApi>[] | null>;
|
|
35
43
|
/** Optional models.dev fallback hook. */
|
|
@@ -41,7 +49,8 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
|
|
|
41
49
|
* Resolution result.
|
|
42
50
|
*
|
|
43
51
|
* `stale` is false when the resolved catalog is authoritative for the selected provider:
|
|
44
|
-
* - dynamic endpoint
|
|
52
|
+
* - a dynamic endpoint fetch succeeded in this call (an empty catalog is still
|
|
53
|
+
* authoritative for the cycle, so downstream pruning of removed models runs),
|
|
45
54
|
* - a still-fresh authoritative cache was reused in `online-if-uncached` mode, or
|
|
46
55
|
* - the provider has no dynamic fetcher configured.
|
|
47
56
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ModelManagerOptions } from "../model-manager.js";
|
|
2
2
|
import { getBundledModels } from "../models.js";
|
|
3
3
|
import type { Api, FetchImpl, ModelSpec, Provider } from "../types.js";
|
|
4
|
+
import { ALIBABA_TOKEN_PLAN_BASE_URL } from "../wire/alibaba-token-plan.js";
|
|
4
5
|
export interface ModelsDevModel {
|
|
5
6
|
id?: string;
|
|
6
7
|
name?: string;
|
|
@@ -299,7 +300,7 @@ export interface AlibabaCodingPlanModelManagerConfig {
|
|
|
299
300
|
fetch?: FetchImpl;
|
|
300
301
|
}
|
|
301
302
|
export declare function alibabaCodingPlanModelManagerOptions(config?: AlibabaCodingPlanModelManagerConfig): ModelManagerOptions<"openai-completions">;
|
|
302
|
-
export
|
|
303
|
+
export { ALIBABA_TOKEN_PLAN_BASE_URL };
|
|
303
304
|
export declare const ALIBABA_TOKEN_PLAN_STATIC_MODELS: readonly ModelSpec<"openai-completions">[];
|
|
304
305
|
export interface AlibabaTokenPlanModelManagerConfig {
|
|
305
306
|
apiKey?: string;
|
|
@@ -506,4 +507,3 @@ export interface ModelsDevProviderDescriptor {
|
|
|
506
507
|
export declare function mapModelsDevToModels(data: Record<string, unknown>, descriptors: readonly ModelsDevProviderDescriptor[]): ModelSpec<Api>[];
|
|
507
508
|
/** All provider descriptors for models.dev data mapping in generate-models.ts. */
|
|
508
509
|
export declare const MODELS_DEV_PROVIDER_DESCRIPTORS: readonly ModelsDevProviderDescriptor[];
|
|
509
|
-
export {};
|
package/dist/types/types.d.ts
CHANGED
|
@@ -686,6 +686,8 @@ export interface Model<TApi extends Api = Api> {
|
|
|
686
686
|
supportsTools?: boolean;
|
|
687
687
|
/** Whether this model accepts the GA OpenAI Responses `{ type: "computer" }` native tool. */
|
|
688
688
|
supportsComputerUse?: boolean;
|
|
689
|
+
/** Verbatim explicit computer-use support from the spec; undefined when `buildModel` inferred the runtime value. */
|
|
690
|
+
supportsComputerUseConfig?: boolean;
|
|
689
691
|
/** GitLab Duo Workflow root namespace selected during catalog discovery. */
|
|
690
692
|
gitlabDuoWorkflowRootNamespaceId?: string;
|
|
691
693
|
/** Cursor `max_mode` request flag returned by `GetUsableModels` for premium models that require max mode. */
|
|
@@ -770,7 +772,7 @@ export interface Model<TApi extends Api = Api> {
|
|
|
770
772
|
* vocabulary of `buildModel`. Identical to `Model` except `compat` carries the
|
|
771
773
|
* sparse override shape and nothing is resolved yet.
|
|
772
774
|
*/
|
|
773
|
-
export interface ModelSpec<TApi extends Api = Api> extends Omit<Model<TApi>, "compat" | "compatConfig"> {
|
|
775
|
+
export interface ModelSpec<TApi extends Api = Api> extends Omit<Model<TApi>, "compat" | "compatConfig" | "supportsComputerUseConfig"> {
|
|
774
776
|
/** Sparse compatibility overrides; resolved into `Model.compat` by `buildModel`. */
|
|
775
777
|
compat?: CompatConfigOf<TApi>;
|
|
776
778
|
}
|
|
@@ -1,6 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* International (Singapore) Token Plan endpoint. Default region; keys issued by
|
|
3
|
+
* the international product authenticate only here.
|
|
4
|
+
*/
|
|
5
|
+
export declare const ALIBABA_TOKEN_PLAN_BASE_URL = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1";
|
|
6
|
+
/**
|
|
7
|
+
* China (Beijing) Token Plan endpoint (百炼 Token Plan). Keys are region-locked:
|
|
8
|
+
* a Beijing-issued key is rejected by the international endpoint with
|
|
9
|
+
* `invalid_api_key`, and vice versa (#6682).
|
|
10
|
+
*/
|
|
11
|
+
export declare const ALIBABA_TOKEN_PLAN_CN_BASE_URL = "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1";
|
|
1
12
|
export interface AlibabaTokenPlanCredential {
|
|
2
13
|
token: string;
|
|
3
14
|
cookie?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Region base URL the key authenticates against. Absent means the default
|
|
17
|
+
* international endpoint ({@link ALIBABA_TOKEN_PLAN_BASE_URL}).
|
|
18
|
+
*/
|
|
19
|
+
baseUrl?: string;
|
|
4
20
|
}
|
|
5
21
|
export declare function parseAlibabaTokenPlanCredential(value: string): AlibabaTokenPlanCredential | null;
|
|
6
|
-
export declare function serializeAlibabaTokenPlanCredential(token: string, cookie: string): string;
|
|
22
|
+
export declare function serializeAlibabaTokenPlanCredential(token: string, cookie: string, baseUrl?: string): string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-catalog",
|
|
4
|
-
"version": "17.1.
|
|
4
|
+
"version": "17.1.4",
|
|
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.1",
|
|
37
|
-
"@oh-my-pi/pi-utils": "17.1.
|
|
37
|
+
"@oh-my-pi/pi-utils": "17.1.4",
|
|
38
38
|
"arktype": "2.2.3",
|
|
39
39
|
"zod": "^4"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@oh-my-pi/pi-ai": "17.1.
|
|
42
|
+
"@oh-my-pi/pi-ai": "17.1.4",
|
|
43
43
|
"@types/bun": "^1.3.14"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
package/src/build.ts
CHANGED
|
@@ -20,28 +20,53 @@ import { cleanModelName } from "./utils";
|
|
|
20
20
|
|
|
21
21
|
const OPENAI_GA_COMPUTER_MODEL_RE = /^gpt-5\.(?:[4-9]|[1-9]\d)(?:[.-]|$)/i;
|
|
22
22
|
|
|
23
|
-
function
|
|
24
|
-
if (spec.
|
|
25
|
-
|
|
26
|
-
spec.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
function isDirectOpenAIResponsesEndpoint(spec: ModelSpec<Api>): boolean {
|
|
24
|
+
if (spec.api === "openai-responses") {
|
|
25
|
+
if (spec.provider !== "openai") return false;
|
|
26
|
+
if (!spec.baseUrl) return true;
|
|
27
|
+
try {
|
|
28
|
+
const url = new URL(spec.baseUrl);
|
|
29
|
+
return url.protocol === "https:" && url.hostname === "api.openai.com";
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (spec.api !== "azure-openai-responses" || (spec.provider !== "azure" && spec.provider !== "azure-openai")) {
|
|
30
35
|
return false;
|
|
31
36
|
}
|
|
32
|
-
if (spec.
|
|
37
|
+
if (!spec.baseUrl) return true;
|
|
38
|
+
try {
|
|
39
|
+
const url = new URL(spec.baseUrl);
|
|
40
|
+
return (
|
|
41
|
+
url.protocol === "https:" &&
|
|
42
|
+
(url.hostname.endsWith(".openai.azure.com") || url.hostname === "models.inference.ai.azure.com")
|
|
43
|
+
);
|
|
44
|
+
} catch {
|
|
33
45
|
return false;
|
|
34
46
|
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function explicitComputerUseConfig(spec: ModelSpec<Api>): boolean | undefined {
|
|
50
|
+
return "supportsComputerUseConfig" in spec
|
|
51
|
+
? (spec as Model<Api>).supportsComputerUseConfig
|
|
52
|
+
: spec.supportsComputerUse;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function supportsOpenAIGAComputerUse(spec: ModelSpec<Api>, explicitSupport: boolean | undefined): boolean {
|
|
56
|
+
if (explicitSupport !== undefined) return explicitSupport;
|
|
57
|
+
if (!isDirectOpenAIResponsesEndpoint(spec)) return false;
|
|
35
58
|
return OPENAI_GA_COMPUTER_MODEL_RE.test(spec.requestModelId ?? spec.id);
|
|
36
59
|
}
|
|
37
60
|
|
|
38
61
|
export function buildModel<TApi extends Api>(spec: ModelSpec<TApi>): Model<TApi> {
|
|
39
62
|
const compat = buildCompat(spec) as CompatOf<TApi>;
|
|
63
|
+
const supportsComputerUseConfig = explicitComputerUseConfig(spec);
|
|
40
64
|
return {
|
|
41
65
|
...spec,
|
|
42
66
|
name: cleanModelName(spec.name),
|
|
43
67
|
thinking: resolveModelThinking(spec, compat),
|
|
44
|
-
supportsComputerUse: supportsOpenAIGAComputerUse(spec),
|
|
68
|
+
supportsComputerUse: supportsOpenAIGAComputerUse(spec, supportsComputerUseConfig),
|
|
69
|
+
supportsComputerUseConfig,
|
|
45
70
|
compat,
|
|
46
71
|
compatConfig: spec.compat,
|
|
47
72
|
} as Model<TApi>;
|
package/src/compat/anthropic.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { modelMatchesHost } from "../hosts";
|
|
|
8
8
|
import {
|
|
9
9
|
hasOpus47ApiRestrictions,
|
|
10
10
|
isAnthropicFableOrMythosModel,
|
|
11
|
+
isKimiK3ModelId,
|
|
11
12
|
supportsMidConversationSystemMessages,
|
|
12
13
|
} from "../identity/family";
|
|
13
14
|
import type { ModelSpec, ResolvedAnthropicCompat } from "../types";
|
|
@@ -28,12 +29,25 @@ export function isOfficialAnthropicApiUrl(baseUrl?: string): boolean {
|
|
|
28
29
|
return lower === OFFICIAL_ANTHROPIC_URL || lower.startsWith(`${OFFICIAL_ANTHROPIC_URL}/`);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Kimi models whose native endpoints (api.kimi.com, api.moonshot.ai) keep
|
|
34
|
+
* thinking enabled server-side no matter what the request carries: they
|
|
35
|
+
* reject disabled thinking (#3852) AND reject forced tool_choice
|
|
36
|
+
* (`tool_choice 'specified' is incompatible with thinking enabled`), so compat
|
|
37
|
+
* must keep thinking on and downgrade forced choices to `auto`. Covers the
|
|
38
|
+
* K2.7 Code family — public id, its Highspeed variant, and the kimi-code
|
|
39
|
+
* `kimi-for-coding[-highspeed]` aliases — plus K3 (`kimi-k3`, bare `k3` on
|
|
40
|
+
* kimi-code). Broader than the `compat/openai.ts` mirror: the OpenAI surface
|
|
41
|
+
* has its own per-dialect policies (K3 forced-choice guard, explicit disable
|
|
42
|
+
* shapes), while this surface has no working way to force a tool.
|
|
43
|
+
* Native-only host gating is the caller's responsibility.
|
|
44
|
+
*/
|
|
32
45
|
const KIMI_K27_CODE_MODEL_PATTERN = /(?:^|\/)kimi[-._]?k2(?:[._-]?|p)7[-._]?code(?:[-._]?highspeed)?$/i;
|
|
33
46
|
|
|
34
|
-
function
|
|
47
|
+
function matchesKimiMandatoryThinkingModel(spec: ModelSpec<"anthropic-messages">): boolean {
|
|
35
48
|
if (KIMI_K27_CODE_MODEL_PATTERN.test(spec.id)) return true;
|
|
36
|
-
|
|
49
|
+
if (spec.id === "kimi-for-coding" || spec.id === "kimi-for-coding-highspeed") return true;
|
|
50
|
+
return isKimiK3ModelId(spec.id) || spec.id === "k3";
|
|
37
51
|
}
|
|
38
52
|
|
|
39
53
|
const CLOUDFLARE_ANTHROPIC_GATEWAY_URL_MARKER = /gateway\.ai\.cloudflare\.com\/.+\/anthropic(?:\/|$)/i;
|
|
@@ -98,7 +112,7 @@ export function buildAnthropicCompat(spec: ModelSpec<"anthropic-messages">): Res
|
|
|
98
112
|
// signature-enforcing Anthropic — same failure class as GitHub Copilot #2851
|
|
99
113
|
// (issue #4192).
|
|
100
114
|
const isZenmux = modelMatchesHost(spec, "zenmux");
|
|
101
|
-
const requiresThinkingEnabled = modelMatchesHost(spec, "moonshotNative") &&
|
|
115
|
+
const requiresThinkingEnabled = modelMatchesHost(spec, "moonshotNative") && matchesKimiMandatoryThinkingModel(spec);
|
|
102
116
|
const isVertex = isVertexAnthropicRoute(baseUrl);
|
|
103
117
|
const isBedrock = isBedrockAnthropicRoute(baseUrl);
|
|
104
118
|
const isAzure = isAzureAnthropicRoute(baseUrl);
|
package/src/compat/bedrock.ts
CHANGED
|
@@ -42,6 +42,15 @@ const EXPLICIT_CHECKPOINTS_4096_1H: ResolvedBedrockCompat = {
|
|
|
42
42
|
promptCacheMaximumCheckpoints: 4,
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
+
// AWS モデルカード: 512 トークン、最大 4 個のキャッシュチェックポイント、5 分と 1 時間の TTL。
|
|
46
|
+
// https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-5.html
|
|
47
|
+
const EXPLICIT_CHECKPOINTS_512_1H: ResolvedBedrockCompat = {
|
|
48
|
+
promptCacheMode: "explicit",
|
|
49
|
+
supportsLongPromptCacheRetention: true,
|
|
50
|
+
promptCacheMinimumTokens: 512,
|
|
51
|
+
promptCacheMaximumCheckpoints: 4,
|
|
52
|
+
};
|
|
53
|
+
|
|
45
54
|
/**
|
|
46
55
|
* Explicit Nova cache points complement Bedrock's automatic prefix caching:
|
|
47
56
|
* AWS recommends them for consistent cache hits and input-cost savings. Keep
|
|
@@ -82,6 +91,9 @@ function detectedBedrockCompat(modelId: string): ResolvedBedrockCompat {
|
|
|
82
91
|
) {
|
|
83
92
|
return EXPLICIT_CHECKPOINTS_4096_1H;
|
|
84
93
|
}
|
|
94
|
+
if (id.includes("anthropic.claude-opus-5")) {
|
|
95
|
+
return EXPLICIT_CHECKPOINTS_512_1H;
|
|
96
|
+
}
|
|
85
97
|
if (id.includes("anthropic.claude-opus-4-6")) {
|
|
86
98
|
return EXPLICIT_CHECKPOINTS_4096_5M;
|
|
87
99
|
}
|
package/src/identity/family.ts
CHANGED
|
@@ -261,6 +261,19 @@ export const modelFamilyToken = memo((modelId: string): string => {
|
|
|
261
261
|
return "";
|
|
262
262
|
});
|
|
263
263
|
|
|
264
|
+
/**
|
|
265
|
+
* True for Claude generations that support extended thinking: Sonnet/Opus 3.7+,
|
|
266
|
+
* every 4.x/5+ Opus/Sonnet, and the Fable/Mythos generation. Pre-thinking
|
|
267
|
+
* models (Claude 3.5 and older) are excluded so no thinking effort dial is
|
|
268
|
+
* fabricated for a model that rejects thinking parameters. Classifier-based, so
|
|
269
|
+
* dotted and dashed version forms both match; ids the classifier does not parse
|
|
270
|
+
* (e.g. Haiku, bare dated ids) return false.
|
|
271
|
+
*/
|
|
272
|
+
export const anthropicModelSupportsThinking = memo((modelId: string): boolean => {
|
|
273
|
+
const parsed = parseAnthropicModel(bareModelId(modelId));
|
|
274
|
+
return parsed !== null && semverGte(parsed.version, "3.7");
|
|
275
|
+
});
|
|
276
|
+
|
|
264
277
|
/**
|
|
265
278
|
* Adaptive thinking `display` is supported starting with Claude Opus 4.7+,
|
|
266
279
|
* Sonnet 5+, and the Claude Fable/Mythos 5 generation. Older adaptive-thinking
|
package/src/model-cache.ts
CHANGED
|
@@ -9,9 +9,9 @@ import type { Api, Model, ModelSpec } from "./types";
|
|
|
9
9
|
// Rows persist ModelSpec JSON (sparse `compat`, never the resolved record);
|
|
10
10
|
// the model manager rebuilds via `buildModel` on load. Request headers are
|
|
11
11
|
// intentionally omitted: arbitrary provider-defined header names can carry
|
|
12
|
-
// credentials.
|
|
13
|
-
//
|
|
14
|
-
//
|
|
12
|
+
// credentials. v11 invalidates rows that may persist derived computer-use
|
|
13
|
+
// support without provenance; v10 deletes rows that may contain persisted
|
|
14
|
+
// headers and records which model ids lost headers or cannot be rebuilt.
|
|
15
15
|
// v9 invalidated Kimi Code rows predating live effort and protocol metadata;
|
|
16
16
|
// v8 invalidated Codex discovery rows predating provider-native V2 compaction
|
|
17
17
|
// metadata; v7 invalidated rows predating the Antigravity Gemini budget-mode
|
|
@@ -20,7 +20,7 @@ import type { Api, Model, ModelSpec } from "./types";
|
|
|
20
20
|
// retired unknown-limit sentinels (222222/8888); v5 invalidated rows predating
|
|
21
21
|
// effort-tier variant collapsing (raw `-low`/`-high`/`-thinking` member ids);
|
|
22
22
|
// v4 dropped the pre-efforts ThinkingConfig shape.
|
|
23
|
-
const CACHE_SCHEMA_VERSION =
|
|
23
|
+
const CACHE_SCHEMA_VERSION = 11;
|
|
24
24
|
const HEADER_RESTORE_VERSION = 1;
|
|
25
25
|
|
|
26
26
|
interface CacheRow {
|
|
@@ -204,8 +204,8 @@ function hasModelHeaders(model: Model<Api>): boolean {
|
|
|
204
204
|
* headers and reject/refetch dynamic-only cached models that need live headers.
|
|
205
205
|
*/
|
|
206
206
|
function toCachedModelSpec<TApi extends Api>(model: Model<TApi>): ModelSpec<TApi> {
|
|
207
|
-
const { headers: _headers, compatConfig, ...rest } = model;
|
|
208
|
-
return { ...rest, compat: compatConfig };
|
|
207
|
+
const { headers: _headers, compatConfig, supportsComputerUseConfig, ...rest } = model;
|
|
208
|
+
return { ...rest, supportsComputerUse: supportsComputerUseConfig, compat: compatConfig };
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
/** Whether two in-memory header records are byte-for-byte equivalent. */
|
|
@@ -228,6 +228,7 @@ export function writeModelCache<TApi extends Api>(
|
|
|
228
228
|
staticFingerprint: string,
|
|
229
229
|
dbPath?: string,
|
|
230
230
|
staticHeaderSources: readonly Model<TApi>[] = [],
|
|
231
|
+
restorableHeaderFallback?: Record<string, string>,
|
|
231
232
|
): void {
|
|
232
233
|
try {
|
|
233
234
|
withModelCacheDb(dbPath, db => {
|
|
@@ -244,7 +245,14 @@ export function writeModelCache<TApi extends Api>(
|
|
|
244
245
|
// unrestorable and dropped on the next offline read (#6037, #6284).
|
|
245
246
|
const staticHeaderSource =
|
|
246
247
|
staticById.get(model.id) ?? (model.requestModelId ? staticById.get(model.requestModelId) : undefined);
|
|
247
|
-
|
|
248
|
+
// A model with no static source is still restorable when its live
|
|
249
|
+
// headers equal the provider's trusted constant (a compile-time,
|
|
250
|
+
// non-credential value the reader can reattach by value). This keeps
|
|
251
|
+
// reference-less Copilot models (e.g. claude-opus-5) alive offline.
|
|
252
|
+
const matchesStatic = staticHeaderSource
|
|
253
|
+
? headersEqual(model.headers, staticHeaderSource.headers)
|
|
254
|
+
: headersEqual(model.headers, restorableHeaderFallback);
|
|
255
|
+
if (!matchesStatic) {
|
|
248
256
|
unrestorableHeaderModelIds.push(model.id);
|
|
249
257
|
}
|
|
250
258
|
}
|
package/src/model-manager.ts
CHANGED
|
@@ -41,6 +41,14 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
|
|
|
41
41
|
dynamicModelsAuthoritative?: boolean;
|
|
42
42
|
/** Cached model ids to ignore when the cache was written against a different static catalog fingerprint. */
|
|
43
43
|
dropCachedModelIdsOnStaticMismatch?: readonly string[];
|
|
44
|
+
/**
|
|
45
|
+
* Trusted, provider-wide request headers (compile-time constants, never
|
|
46
|
+
* credentials) that the cache may restore by value for any model whose live
|
|
47
|
+
* headers matched them at write time. Lets header-bearing dynamic models
|
|
48
|
+
* without a bundled static entry survive offline reads instead of being
|
|
49
|
+
* dropped as unrestorable (e.g. GitHub Copilot's User-Agent + API version).
|
|
50
|
+
*/
|
|
51
|
+
restorableHeaderFallback?: Record<string, string>;
|
|
44
52
|
/** Optional dynamic endpoint fetcher. */
|
|
45
53
|
fetchDynamicModels?: () => Promise<readonly ModelSpec<TApi>[] | null>;
|
|
46
54
|
/** Optional models.dev fallback hook. */
|
|
@@ -53,7 +61,8 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
|
|
|
53
61
|
* Resolution result.
|
|
54
62
|
*
|
|
55
63
|
* `stale` is false when the resolved catalog is authoritative for the selected provider:
|
|
56
|
-
* - dynamic endpoint
|
|
64
|
+
* - a dynamic endpoint fetch succeeded in this call (an empty catalog is still
|
|
65
|
+
* authoritative for the cycle, so downstream pruning of removed models runs),
|
|
57
66
|
* - a still-fresh authoritative cache was reused in `online-if-uncached` mode, or
|
|
58
67
|
* - the provider has no dynamic fetcher configured.
|
|
59
68
|
*/
|
|
@@ -123,6 +132,7 @@ function restoreCachedModelHeaders<TApi extends Api>(
|
|
|
123
132
|
headerOmittedModelIds: readonly string[],
|
|
124
133
|
unrestorableHeaderModelIds: readonly string[],
|
|
125
134
|
legacyHeaderRestoreMarkers: boolean,
|
|
135
|
+
restorableHeaderFallback: Record<string, string> | undefined,
|
|
126
136
|
): CachedHeaderRestoreResult<TApi> {
|
|
127
137
|
const models = passModelList<TApi>(cachedModels);
|
|
128
138
|
if (headerOmittedModelIds.length === 0) {
|
|
@@ -144,6 +154,13 @@ function restoreCachedModelHeaders<TApi extends Api>(
|
|
|
144
154
|
: undefined
|
|
145
155
|
: (staticById.get(model.id) ?? (model.requestModelId ? staticById.get(model.requestModelId) : undefined));
|
|
146
156
|
if (!staticModel?.headers) {
|
|
157
|
+
// A non-unrestorable row whose static source is gone was cached with
|
|
158
|
+
// headers matching the provider's trusted constant (e.g. a Copilot
|
|
159
|
+
// model with no bundled entry). Reattach the constant by value instead
|
|
160
|
+
// of dropping the model on this offline read.
|
|
161
|
+
if (!unrestorable && restorableHeaderFallback) {
|
|
162
|
+
return { ...model, headers: { ...restorableHeaderFallback } };
|
|
163
|
+
}
|
|
147
164
|
unresolvedModelIds.add(model.id);
|
|
148
165
|
return model;
|
|
149
166
|
}
|
|
@@ -166,6 +183,7 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
166
183
|
const now = options.now ?? Date.now;
|
|
167
184
|
const ttlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
168
185
|
const dbPath = options.cacheDbPath;
|
|
186
|
+
const restorableHeaderFallback = options.restorableHeaderFallback;
|
|
169
187
|
const staticModels = options.staticModels
|
|
170
188
|
? passModelList<TApi>(options.staticModels)
|
|
171
189
|
: (getBundledModels(options.providerId as GeneratedProvider) as Model<TApi>[]);
|
|
@@ -176,6 +194,7 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
176
194
|
cache?.headerOmittedModelIds ?? [],
|
|
177
195
|
cache?.unrestorableHeaderModelIds ?? [],
|
|
178
196
|
cache?.legacyHeaderRestoreMarkers ?? false,
|
|
197
|
+
restorableHeaderFallback,
|
|
179
198
|
);
|
|
180
199
|
const usableCachedModels = restoredCache.models.filter(model => !restoredCache.unresolvedModelIds.has(model.id));
|
|
181
200
|
const cacheHasUnresolvedHeaders = restoredCache.unresolvedModelIds.size > 0;
|
|
@@ -226,6 +245,12 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
226
245
|
options.dropCachedModelIdsOnStaticMismatch,
|
|
227
246
|
);
|
|
228
247
|
const dynamicModels = fetchedDynamicModels ?? [];
|
|
248
|
+
// A successful empty result stays authoritative for THIS cycle (so an
|
|
249
|
+
// intentional catalog emptying still prunes removed models downstream), but
|
|
250
|
+
// is NOT pinned into the cache as authoritative — that would suppress the
|
|
251
|
+
// short retry that recovers a transient empty response (#6620). The two
|
|
252
|
+
// concerns are deliberately separate: result authority vs. cache retry.
|
|
253
|
+
const dynamicCacheAuthoritative = dynamicFetchSucceeded && dynamicModels.length > 0;
|
|
229
254
|
const mergedWithCache = mergeDynamicModels(mergeModelSources(staticModels, modelsDevModels), cacheModels);
|
|
230
255
|
const mergedModels = mergeDynamicModels(mergedWithCache, dynamicModels);
|
|
231
256
|
const models = collapseBuiltModelVariants(
|
|
@@ -242,10 +267,11 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
242
267
|
cacheProviderId,
|
|
243
268
|
now(),
|
|
244
269
|
collapseBuiltModelVariants(snapshotModels),
|
|
245
|
-
|
|
270
|
+
dynamicCacheAuthoritative,
|
|
246
271
|
staticFingerprint,
|
|
247
272
|
dbPath,
|
|
248
273
|
staticModels,
|
|
274
|
+
restorableHeaderFallback,
|
|
249
275
|
);
|
|
250
276
|
} else {
|
|
251
277
|
// Dynamic fetch failed — update cache with a non-authoritative snapshot so
|
|
@@ -257,28 +283,31 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
257
283
|
latestCache?.headerOmittedModelIds ?? cache?.headerOmittedModelIds ?? [],
|
|
258
284
|
latestCache?.unrestorableHeaderModelIds ?? cache?.unrestorableHeaderModelIds ?? [],
|
|
259
285
|
latestCache?.legacyHeaderRestoreMarkers ?? cache?.legacyHeaderRestoreMarkers ?? false,
|
|
286
|
+
restorableHeaderFallback,
|
|
260
287
|
);
|
|
261
288
|
const latestUsableCacheModels = latestRestoredCache.models.filter(
|
|
262
289
|
model => !latestRestoredCache.unresolvedModelIds.has(model.id),
|
|
263
290
|
);
|
|
291
|
+
const fallbackSnapshotModels = collapseBuiltModelVariants(
|
|
292
|
+
mergeDynamicModels(
|
|
293
|
+
mergeModelSources(staticModels, modelsDevModels),
|
|
294
|
+
prepareCacheModelsForStaticMismatch(
|
|
295
|
+
latestUsableCacheModels,
|
|
296
|
+
staticModels,
|
|
297
|
+
cacheFingerprintMatches,
|
|
298
|
+
options.dropCachedModelIdsOnStaticMismatch,
|
|
299
|
+
),
|
|
300
|
+
),
|
|
301
|
+
);
|
|
264
302
|
writeModelCache(
|
|
265
303
|
cacheProviderId,
|
|
266
304
|
now(),
|
|
267
|
-
|
|
268
|
-
mergeDynamicModels(
|
|
269
|
-
mergeModelSources(staticModels, modelsDevModels),
|
|
270
|
-
prepareCacheModelsForStaticMismatch(
|
|
271
|
-
latestUsableCacheModels,
|
|
272
|
-
staticModels,
|
|
273
|
-
cacheFingerprintMatches,
|
|
274
|
-
options.dropCachedModelIdsOnStaticMismatch,
|
|
275
|
-
),
|
|
276
|
-
),
|
|
277
|
-
),
|
|
305
|
+
fallbackSnapshotModels,
|
|
278
306
|
false,
|
|
279
307
|
staticFingerprint,
|
|
280
308
|
dbPath,
|
|
281
309
|
staticModels,
|
|
310
|
+
restorableHeaderFallback,
|
|
282
311
|
);
|
|
283
312
|
}
|
|
284
313
|
}
|