@oh-my-pi/pi-catalog 17.1.3 → 17.1.5

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 CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.1.5] - 2026-07-27
6
+
7
+ ### Fixed
8
+
9
+ - Fixed Kimi Code (`kimi-code`) reporting `maxTokens: 32000` for every model — its `/coding/v1/models` discovery mapper and the bundled catalog applied a blanket constant, truncating `k3`/`k3-256k` output at ~4x below their real 131072 ceiling and `kimi-for-coding`/`kimi-for-coding-highspeed` below their 32768 ceiling. Output caps are now derived per family, and the model cache is invalidated so upgrades drop the stale `maxTokens: 32000` rows (including the discovery-only `k3-256k`) instead of serving them until the next network refresh ([#6711](https://github.com/can1357/oh-my-pi/issues/6711)).
10
+ - Fixed Anthropic model discovery 404ing when the registry derived the provider base URL from a bundled model without the `/v1` suffix (`https://api.anthropic.com/models` instead of `/v1/models`), which let a stale text-only cache row shadow fresh models.dev vision metadata — surfacing as snapcompact refusing to run on `claude-opus-5`. Discovery now always targets `/v1/models` while model rows keep the provider base URL ([#6563](https://github.com/can1357/oh-my-pi/issues/6563)).
11
+
12
+ ## [17.1.4] - 2026-07-26
13
+
14
+ ### Added
15
+
16
+ - Added Claude Opus 5 model entries for Amazon Bedrock: `anthropic.claude-opus-5` plus its `us.`, `eu.`, `au.`, and `global.` regional/geo IDs.
17
+
18
+ ### Fixed
19
+
20
+ - 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)).
21
+ - 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`.
22
+ - 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)).
23
+ - 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)).
24
+
5
25
  ## [17.1.3] - 2026-07-24
6
26
 
7
27
  ### Fixed
@@ -7,5 +7,12 @@ import type { ModelSpec, ResolvedAnthropicCompat } from "../types.js";
7
7
  * prefix check would accept lookalikes like `https://api.anthropic.com.evil.com`.
8
8
  */
9
9
  export declare function isOfficialAnthropicApiUrl(baseUrl?: string): boolean;
10
+ /**
11
+ * Known non-official URLs that enforce Anthropic thinking signatures on replay.
12
+ *
13
+ * Runtime routing calls this with the effective URL because a model's resolved
14
+ * compat can be stale after Foundry or a provider base-URL override reroutes it.
15
+ */
16
+ export declare function isAnthropicSigningProxyUrl(baseUrl?: string): boolean;
10
17
  /** Build the resolved anthropic-messages compat record for a model spec. */
11
18
  export declare function buildAnthropicCompat(spec: ModelSpec<"anthropic-messages">): ResolvedAnthropicCompat;
@@ -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 data was fetched in this call,
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
  */
@@ -8,4 +8,6 @@ import type { Api, Model, ModelSpec } from "../types.js";
8
8
  */
9
9
  export declare function toModelSpec<TApi extends Api>(model: Model<TApi>): ModelSpec<TApi>;
10
10
  export declare function createBundledReferenceMap<TApi extends Api>(provider: Parameters<typeof getBundledModels>[0]): Map<string, ModelSpec<TApi>>;
11
- export declare function createReferenceResolver<TApi extends Api>(providerRefs: Map<string, ModelSpec<TApi>>): (modelId: string) => ModelSpec<TApi> | undefined;
11
+ type ProviderReferenceSource<TApi extends Api> = Map<string, ModelSpec<TApi>> | (() => Map<string, ModelSpec<TApi>>);
12
+ export declare function createReferenceResolver<TApi extends Api>(providerReferenceSource: ProviderReferenceSource<TApi>): (modelId: string) => ModelSpec<TApi> | undefined;
13
+ export {};
@@ -0,0 +1,7 @@
1
+ export interface ModelCacheProviderIdOptions {
2
+ apiKey?: string;
3
+ baseUrl?: string;
4
+ }
5
+ export declare function getDefaultModelDiscoveryBaseUrl(providerId: string): string | undefined;
6
+ /** Resolve the cache namespace used by a provider's model-manager options without constructing those options. */
7
+ export declare function resolveModelCacheProviderId(providerId: string, options?: ModelCacheProviderIdOptions): string;
@@ -1,3 +1,4 @@
1
+ export * from "./cache-provider-id.js";
1
2
  export * from "./descriptor-types.js";
2
3
  export * from "./descriptors.js";
3
4
  export * from "./google.js";
@@ -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 declare const ALIBABA_TOKEN_PLAN_BASE_URL = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1";
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;
@@ -318,6 +319,22 @@ export interface KimiCodeModelManagerConfig {
318
319
  baseUrl?: string;
319
320
  fetch?: FetchImpl;
320
321
  }
322
+ /**
323
+ * Kimi Code output ceilings by model family. The `/coding/v1/models` discovery
324
+ * envelope carries no output-limit field, so the mapper supplies the documented
325
+ * per-family caps instead of a blanket constant. Values match models.dev's
326
+ * `kimi-for-coding` and `moonshotai` kimi-k3 entries. See #6711.
327
+ */
328
+ export declare const KIMI_CODE_K3_MAX_TOKENS = 131072;
329
+ export declare const KIMI_CODE_FOR_CODING_MAX_TOKENS = 32768;
330
+ /** Fallback output cap for Kimi Code families without a documented ceiling (legacy K2 discovery rows). */
331
+ export declare const KIMI_CODE_DEFAULT_MAX_TOKENS = 32000;
332
+ /**
333
+ * Resolve a Kimi Code model's output ceiling from its id: `k3` / `k3-256k` ->
334
+ * 131072, `kimi-for-coding[-highspeed]` -> 32768, everything else -> `fallback`.
335
+ */
336
+ export declare function kimiCodeMaxTokens(modelId: string, fallback?: number): number;
337
+ export declare function kimiCodeMaxTokens(modelId: string, fallback: number | null): number | null;
321
338
  export declare function kimiCodeModelManagerOptions(config?: KimiCodeModelManagerConfig): ModelManagerOptions<"openai-completions">;
322
339
  /** Native LM Studio metadata keyed by model id from `/api/v0/models`. */
323
340
  export interface LmStudioNativeModelMetadata {
@@ -506,4 +523,3 @@ export interface ModelsDevProviderDescriptor {
506
523
  export declare function mapModelsDevToModels(data: Record<string, unknown>, descriptors: readonly ModelsDevProviderDescriptor[]): ModelSpec<Api>[];
507
524
  /** All provider descriptors for models.dev data mapping in generate-models.ts. */
508
525
  export declare const MODELS_DEV_PROVIDER_DESCRIPTORS: readonly ModelsDevProviderDescriptor[];
509
- export {};
@@ -432,6 +432,20 @@ export interface AnthropicCompat {
432
432
  * blocks instead of normal `tool_use` calls.
433
433
  */
434
434
  escapeBuiltinToolNames?: boolean;
435
+ /**
436
+ * The configured endpoint enforces Anthropic's signature protocol on
437
+ * replayed thinking blocks — either the official API itself or a proxy
438
+ * that forwards to it (GitHub Copilot, ZenMux, Cloudflare AI Gateway's
439
+ * `/anthropic` route, Google Vertex's `publishers/anthropic/…`).
440
+ * Downstream transforms strip stale cross-model thinking signatures on
441
+ * these endpoints so the signing proxy doesn't 400 with
442
+ * `Invalid signature in thinking block` (#4297), and adaptive-thinking
443
+ * models keep the interleaved-thinking beta on them (#6717). Known hosts
444
+ * are auto-detected from provider id and baseUrl; set this to mark an
445
+ * opaque signing proxy the URL list can't recognize. Superset of
446
+ * {@link ResolvedAnthropicCompat.officialEndpoint}.
447
+ */
448
+ signingEndpoint?: boolean;
435
449
  }
436
450
  /**
437
451
  * Compatibility settings for Bedrock Converse prompt caching. Cache pricing is
@@ -597,17 +611,6 @@ export type ResolvedAnthropicCompat = Required<AnthropicCompat> & {
597
611
  * env headers, and cache-TTL shaping without per-request URL parsing.
598
612
  */
599
613
  officialEndpoint: boolean;
600
- /**
601
- * The configured endpoint enforces Anthropic's signature protocol on
602
- * replayed thinking blocks — either the official API itself or a proxy
603
- * that forwards to it (GitHub Copilot, ZenMux, Cloudflare AI Gateway's
604
- * `/anthropic` route, Google Vertex's `publishers/anthropic/…`).
605
- * Downstream transforms strip stale cross-model thinking signatures on
606
- * these endpoints so the signing proxy doesn't 400 with
607
- * `Invalid signature in thinking block` (#4297). Superset of
608
- * {@link officialEndpoint}.
609
- */
610
- signingEndpoint: boolean;
611
614
  };
612
615
  /**
613
616
  * Compatibility settings for the devin-agent (Codeium Cascade) API. Cascade
@@ -686,6 +689,8 @@ export interface Model<TApi extends Api = Api> {
686
689
  supportsTools?: boolean;
687
690
  /** Whether this model accepts the GA OpenAI Responses `{ type: "computer" }` native tool. */
688
691
  supportsComputerUse?: boolean;
692
+ /** Verbatim explicit computer-use support from the spec; undefined when `buildModel` inferred the runtime value. */
693
+ supportsComputerUseConfig?: boolean;
689
694
  /** GitLab Duo Workflow root namespace selected during catalog discovery. */
690
695
  gitlabDuoWorkflowRootNamespaceId?: string;
691
696
  /** Cursor `max_mode` request flag returned by `GetUsableModels` for premium models that require max mode. */
@@ -770,7 +775,7 @@ export interface Model<TApi extends Api = Api> {
770
775
  * vocabulary of `buildModel`. Identical to `Model` except `compat` carries the
771
776
  * sparse override shape and nothing is resolved yet.
772
777
  */
773
- export interface ModelSpec<TApi extends Api = Api> extends Omit<Model<TApi>, "compat" | "compatConfig"> {
778
+ export interface ModelSpec<TApi extends Api = Api> extends Omit<Model<TApi>, "compat" | "compatConfig" | "supportsComputerUseConfig"> {
774
779
  /** Sparse compatibility overrides; resolved into `Model.compat` by `buildModel`. */
775
780
  compat?: CompatConfigOf<TApi>;
776
781
  }
@@ -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.3",
4
+ "version": "17.1.5",
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.3",
37
+ "@oh-my-pi/pi-utils": "17.1.5",
38
38
  "arktype": "2.2.3",
39
39
  "zod": "^4"
40
40
  },
41
41
  "devDependencies": {
42
- "@oh-my-pi/pi-ai": "17.1.3",
42
+ "@oh-my-pi/pi-ai": "17.1.5",
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 supportsOpenAIGAComputerUse(spec: ModelSpec<Api>): boolean {
24
- if (spec.supportsComputerUse !== undefined) return spec.supportsComputerUse;
25
- if (
26
- spec.api !== "openai-responses" &&
27
- spec.api !== "openai-codex-responses" &&
28
- spec.api !== "azure-openai-responses"
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.api !== "azure-openai-responses" && spec.provider !== "openai" && spec.provider !== "openai-codex") {
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>;
@@ -4,10 +4,11 @@
4
4
  * defaults come from provider ids, strict host checks, and model-id
5
5
  * classification, with explicit spec overrides assigned on top.
6
6
  */
7
- import { modelMatchesHost } from "../hosts";
7
+ import { hostMatchesUrl, 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
- /** Mirrors `compat/openai.ts`; native-only host gating is the caller's responsibility. */
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 matchesKimiK27CodeFamily(spec: ModelSpec<"anthropic-messages">): boolean {
47
+ function matchesKimiMandatoryThinkingModel(spec: ModelSpec<"anthropic-messages">): boolean {
35
48
  if (KIMI_K27_CODE_MODEL_PATTERN.test(spec.id)) return true;
36
- return spec.id === "kimi-for-coding" && /k2\.?7 code/i.test(spec.name ?? "");
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;
@@ -82,6 +96,23 @@ function isAzureAnthropicRoute(baseUrl?: string): boolean {
82
96
  return baseUrl !== undefined && AZURE_ANTHROPIC_URL_MARKER.test(baseUrl);
83
97
  }
84
98
 
99
+ /**
100
+ * Known non-official URLs that enforce Anthropic thinking signatures on replay.
101
+ *
102
+ * Runtime routing calls this with the effective URL because a model's resolved
103
+ * compat can be stale after Foundry or a provider base-URL override reroutes it.
104
+ */
105
+ export function isAnthropicSigningProxyUrl(baseUrl?: string): boolean {
106
+ return (
107
+ hostMatchesUrl(baseUrl, "githubCopilot") ||
108
+ hostMatchesUrl(baseUrl, "zenmux") ||
109
+ isCloudflareAnthropicGateway(baseUrl) ||
110
+ isVertexAnthropicRoute(baseUrl) ||
111
+ isBedrockAnthropicRoute(baseUrl) ||
112
+ isAzureAnthropicRoute(baseUrl)
113
+ );
114
+ }
115
+
85
116
  /** Build the resolved anthropic-messages compat record for a model spec. */
86
117
  export function buildAnthropicCompat(spec: ModelSpec<"anthropic-messages">): ResolvedAnthropicCompat {
87
118
  const baseUrl = spec.baseUrl;
@@ -98,12 +129,9 @@ export function buildAnthropicCompat(spec: ModelSpec<"anthropic-messages">): Res
98
129
  // signature-enforcing Anthropic — same failure class as GitHub Copilot #2851
99
130
  // (issue #4192).
100
131
  const isZenmux = modelMatchesHost(spec, "zenmux");
101
- const requiresThinkingEnabled = modelMatchesHost(spec, "moonshotNative") && matchesKimiK27CodeFamily(spec);
102
- const isVertex = isVertexAnthropicRoute(baseUrl);
103
- const isBedrock = isBedrockAnthropicRoute(baseUrl);
132
+ const requiresThinkingEnabled = modelMatchesHost(spec, "moonshotNative") && matchesKimiMandatoryThinkingModel(spec);
104
133
  const isAzure = isAzureAnthropicRoute(baseUrl);
105
- const signingEndpoint =
106
- official || isCopilot || isZenmux || isCloudflareAnthropicGateway(baseUrl) || isVertex || isBedrock || isAzure;
134
+ const signingEndpoint = official || isCopilot || isZenmux || isAnthropicSigningProxyUrl(baseUrl);
107
135
  const compat: ResolvedAnthropicCompat = {
108
136
  officialEndpoint: official,
109
137
  signingEndpoint,
@@ -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
  }
@@ -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
@@ -9,9 +9,11 @@ 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. v10 deletes rows that may contain persisted headers and records
13
- // which model ids lost headers and which cannot be rebuilt from static inputs,
14
- // so the manager can restore the safe subset or refetch dynamic-only headers;
12
+ // credentials. v12 invalidates Kimi Code rows carrying the blanket
13
+ // maxTokens: 32000 that predate per-family output caps (k3/k3-256k -> 131072,
14
+ // kimi-for-coding[-highspeed] -> 32768, #6711); v11 invalidates rows that may
15
+ // persist derived computer-use
16
+ // headers and records which model ids lost headers or cannot be rebuilt.
15
17
  // v9 invalidated Kimi Code rows predating live effort and protocol metadata;
16
18
  // v8 invalidated Codex discovery rows predating provider-native V2 compaction
17
19
  // metadata; v7 invalidated rows predating the Antigravity Gemini budget-mode
@@ -20,7 +22,7 @@ import type { Api, Model, ModelSpec } from "./types";
20
22
  // retired unknown-limit sentinels (222222/8888); v5 invalidated rows predating
21
23
  // effort-tier variant collapsing (raw `-low`/`-high`/`-thinking` member ids);
22
24
  // v4 dropped the pre-efforts ThinkingConfig shape.
23
- const CACHE_SCHEMA_VERSION = 10;
25
+ const CACHE_SCHEMA_VERSION = 12;
24
26
  const HEADER_RESTORE_VERSION = 1;
25
27
 
26
28
  interface CacheRow {
@@ -204,8 +206,8 @@ function hasModelHeaders(model: Model<Api>): boolean {
204
206
  * headers and reject/refetch dynamic-only cached models that need live headers.
205
207
  */
206
208
  function toCachedModelSpec<TApi extends Api>(model: Model<TApi>): ModelSpec<TApi> {
207
- const { headers: _headers, compatConfig, ...rest } = model;
208
- return { ...rest, compat: compatConfig };
209
+ const { headers: _headers, compatConfig, supportsComputerUseConfig, ...rest } = model;
210
+ return { ...rest, supportsComputerUse: supportsComputerUseConfig, compat: compatConfig };
209
211
  }
210
212
 
211
213
  /** Whether two in-memory header records are byte-for-byte equivalent. */
@@ -228,6 +230,7 @@ export function writeModelCache<TApi extends Api>(
228
230
  staticFingerprint: string,
229
231
  dbPath?: string,
230
232
  staticHeaderSources: readonly Model<TApi>[] = [],
233
+ restorableHeaderFallback?: Record<string, string>,
231
234
  ): void {
232
235
  try {
233
236
  withModelCacheDb(dbPath, db => {
@@ -244,7 +247,14 @@ export function writeModelCache<TApi extends Api>(
244
247
  // unrestorable and dropped on the next offline read (#6037, #6284).
245
248
  const staticHeaderSource =
246
249
  staticById.get(model.id) ?? (model.requestModelId ? staticById.get(model.requestModelId) : undefined);
247
- if (!headersEqual(model.headers, staticHeaderSource?.headers)) {
250
+ // A model with no static source is still restorable when its live
251
+ // headers equal the provider's trusted constant (a compile-time,
252
+ // non-credential value the reader can reattach by value). This keeps
253
+ // reference-less Copilot models (e.g. claude-opus-5) alive offline.
254
+ const matchesStatic = staticHeaderSource
255
+ ? headersEqual(model.headers, staticHeaderSource.headers)
256
+ : headersEqual(model.headers, restorableHeaderFallback);
257
+ if (!matchesStatic) {
248
258
  unrestorableHeaderModelIds.push(model.id);
249
259
  }
250
260
  }