@oh-my-pi/pi-catalog 18.2.0 → 18.2.1
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 +27 -0
- package/dist/types/compat/behavior.d.ts +11 -0
- package/dist/types/compat/types.d.ts +21 -0
- package/dist/types/discovery/antigravity.d.ts +10 -1
- package/dist/types/model-thinking.d.ts +7 -0
- package/dist/types/provider-models/openai-compat.d.ts +2 -0
- package/dist/types/types.d.ts +26 -1
- package/package.json +4 -4
- package/src/compat/axes.ts +14 -0
- package/src/compat/behavior.ts +22 -2
- package/src/compat/cascade.ts +3 -1
- package/src/compat/context-window.ts +11 -1
- package/src/compat/resolve.ts +42 -16
- package/src/compat/rules/README.md +3 -1
- package/src/compat/rules/classes/deepseek.kdl +9 -1
- package/src/compat/rules/classes/kimi.kdl +6 -0
- package/src/compat/rules/providers/alibaba-token-plan.kdl +16 -8
- package/src/compat/rules/providers/amazon-bedrock.kdl +30 -0
- package/src/compat/rules/providers/azure.kdl +6 -0
- package/src/compat/rules/providers/cerebras.kdl +10 -0
- package/src/compat/rules/providers/commandcode.kdl +20 -4
- package/src/compat/rules/providers/cursor.kdl +32 -0
- package/src/compat/rules/providers/deepseek.kdl +5 -5
- package/src/compat/rules/providers/google-vertex.kdl +15 -0
- package/src/compat/rules/providers/meta.kdl +3 -0
- package/src/compat/rules/providers/muse-code.kdl +3 -0
- package/src/compat/rules/providers/openrouter.kdl +6 -0
- package/src/compat/rules/runtime/behavior.kdl +17 -0
- package/src/compat/rules/taxonomy/deepseek.kdl +5 -0
- package/src/compat/rules.json +1 -1
- package/src/compat/types.ts +23 -0
- package/src/discovery/antigravity.ts +80 -43
- package/src/identity/bundled.ts +4 -3
- package/src/model-cache.ts +115 -37
- package/src/model-thinking.ts +10 -7
- package/src/models.json +1 -1
- package/src/provider-models/bundled-references.ts +4 -3
- package/src/provider-models/cache-provider-id.ts +14 -8
- package/src/provider-models/ollama.ts +11 -31
- package/src/provider-models/openai-compat.ts +94 -24
- package/src/types.ts +28 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.2.1] - 2026-09-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- DeepSeek V4.1 Flash (`deepseek-flash`) now accepts image inputs and resolves its reasoning metadata, name, and effort ladder from upstream instead of shipping as a text-only row without them.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed Cerebras Qwen 3.8 reasoning control by enabling correct effort tiers and disable behavior
|
|
14
|
+
- Fixed Cerebras Qwen models using incorrect reasoning format, enabling proper support for thinking modes
|
|
15
|
+
- Fixed Command Code's `deepseek/deepseek-v4.1-flash` row exposing its documented low/high/max thinking levels and image input, and made live discovery resolve the reasoning contract its rules declare ([#1666](https://github.com/can1357/oh-my-pi/issues/1666), [#11703](https://github.com/can1357/oh-my-pi/pull/11703) by [@aliefe04](https://github.com/aliefe04)).
|
|
16
|
+
- Fixed Meta Model API and Muse Code requests failing with 400 whenever omp forced a tool choice: `api.meta.ai/v1` accepts only `tool_choice: "auto"`, so `none`, `required`, and named choices (subagent final-retry `yield`, forced tools, structured output, compaction handoff) are now omitted instead of sent ([#11635](https://github.com/can1357/oh-my-pi/pull/11635) by [@quantmind-br](https://github.com/quantmind-br)).
|
|
17
|
+
- Bedrock's Qwen rows no longer ask for more output tokens than the model accepts, which Bedrock rejected with a 400. ([#12117](https://github.com/can1357/oh-my-pi/pull/12117) by [@Huang-404-Q](https://github.com/Huang-404-Q))
|
|
18
|
+
- Gemini 2.5 Flash Lite on Vertex AI no longer requests `maxOutputTokens=65536`, which the endpoint rejects with a 400; the output cap is clamped to 65535 ([#10595](https://github.com/can1357/oh-my-pi/pull/10595) by [@WeMingT](https://github.com/WeMingT)).
|
|
19
|
+
- Fixed Meta muse-spark models on OpenRouter wedging every turn with `400 Referenced reasoning item ... was not found or has expired`: replayed Responses reasoning history is now filtered for the muse-spark family, matching the existing Anthropic-on-OpenRouter treatment ([#10675](https://github.com/can1357/oh-my-pi/pull/10675) by [@Giardi77](https://github.com/Giardi77)).
|
|
20
|
+
- Fixed Ollama Cloud model discovery synthesizing a generic `minimal`/`low`/`medium`/`high` effort ladder for every thinking-capable model, which shadowed the per-model compat rules and made `max` unreachable on the DeepSeek V4 line (including the served `deepseek-v4.1-flash`, `deepseek-v4-flash:0731`, and `deepseek-v4-pro:0813` ids): discovery now leaves the ladder to the rule tree, so those models advertise the wire-exact `low`/`high`/`max` and GLM-5.3 exposes `low`/`high`/`max` ([#8334](https://github.com/can1357/oh-my-pi/issues/8334)).
|
|
21
|
+
- Fixed Meta Model API and Muse Code requests failing with 400 whenever omp forced a tool choice: `api.meta.ai/v1` accepts only `tool_choice: "auto"`, so `none`, `required`, and named choices (subagent final-retry `yield`, forced tools, structured output, compaction handoff) are now omitted instead of sent.
|
|
22
|
+
- Fixed Azure GPT-6 Astra Chat Completions policy to disable reasoning with function tools and encode thinking-off as `reasoning_effort: "none"` ([#11052](https://github.com/can1357/oh-my-pi/issues/11052)).
|
|
23
|
+
- Fixed Z.AI and Zhipu timezone-naive quota reset timestamps resolving eight hours late by declaring their UTC+8 reset timezone ([#11014](https://github.com/can1357/oh-my-pi/issues/11014)).
|
|
24
|
+
- Fixed Amazon Bedrock Claude Fable 5.1 effort metadata to expose `xhigh` and `max` instead of the unsupported `minimal` level. ([#10788](https://github.com/can1357/oh-my-pi/pull/10788) by [@voonfoo](https://github.com/voonfoo))
|
|
25
|
+
- LiteLLM discovery no longer exposes known task-specific models, including embedding, media, moderation, reranking, and search models, as coding models.
|
|
26
|
+
- Fixed custom `anthropic-messages` providers whose adaptive effort ladder exposes `minimal` sending `output_config.effort: "minimal"`, which the Anthropic Messages API rejects with `400 level "minimal" not supported`; the adaptive effort mapper now clamps `minimal` to `low` ([#10994](https://github.com/can1357/oh-my-pi/issues/10994)).
|
|
27
|
+
- Qwen 3.8 models on Alibaba Token Plan now send the selected reasoning effort and replay reasoning history across turns.
|
|
28
|
+
- Fixed DeepInfra model cost reporting so promotional pricing is reflected: the `metadata.discount` fraction is now applied to input, output, and cache-read rates ([#10935](https://github.com/can1357/oh-my-pi/issues/10935)).
|
|
29
|
+
- Fixed LiteLLM model discovery leaking ClinePass display names and pricing into models with colliding ids ([#10932](https://github.com/can1357/oh-my-pi/issues/10932)).
|
|
30
|
+
- Raised Cursor context windows to Cursor's documented sizes (Grok 4.5/4.6 256k, default/Auto 256k, Kimi K2.7 Code 262k, GPT-5.6 272k, Claude Opus 5 and Fable 300k by default) so compaction no longer fires too early, without lowering existing 1M Max-mode windows.
|
|
31
|
+
|
|
5
32
|
## [18.2.0] - 2026-09-15
|
|
6
33
|
|
|
7
34
|
### Breaking Changes
|
|
@@ -29,6 +29,8 @@ export declare function cursorModelParameters(model: string): readonly {
|
|
|
29
29
|
* preserve quota semantics for newly discovered ids.
|
|
30
30
|
*/
|
|
31
31
|
export declare function quotaTierFor(provider: string, model: string): string | undefined;
|
|
32
|
+
/** UTC offset for a provider's timezone-naive absolute retry-reset timestamps. */
|
|
33
|
+
export declare function retryResetTimezoneOffsetFor(provider: string): string | undefined;
|
|
32
34
|
/** Whether a provider has catalog-authored model quota scopes. */
|
|
33
35
|
export declare function hasQuotaTierPolicy(provider: string): boolean;
|
|
34
36
|
/** The provider-default wire model for a model-less hosted operation. */
|
|
@@ -55,11 +57,20 @@ export declare function modelLimitsFor(provider: string, model: string): {
|
|
|
55
57
|
} | undefined;
|
|
56
58
|
/** Whether a provider roster entry is a declared non-chat/unsupported SKU. */
|
|
57
59
|
export declare function isExcludedModel(provider: string, model: string): boolean;
|
|
60
|
+
/** Whether an exact upstream discovery mode is excluded from a provider's coding-model roster. */
|
|
61
|
+
export declare function isExcludedDiscoveryMode(provider: string, mode: string): boolean;
|
|
58
62
|
/**
|
|
59
63
|
* Whether a provider is retired: its entire roster is excluded from the
|
|
60
64
|
* generated bundle and must never be resurrected from the previous snapshot.
|
|
61
65
|
*/
|
|
62
66
|
export declare function isRetiredProvider(provider: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Whether a provider's bundled rows may seed cross-provider bare-id enrichment
|
|
69
|
+
* references. Gateway-namespaced providers (ClinePass) are isolated so their
|
|
70
|
+
* limits, pricing, and reasoning controls never contaminate an unrelated proxy
|
|
71
|
+
* model that happens to advertise the same bare id.
|
|
72
|
+
*/
|
|
73
|
+
export declare function isBareIdReferenceProvider(provider: string): boolean;
|
|
63
74
|
/** The declared subscription tier required to use a provider model id, if any. */
|
|
64
75
|
export declare function planRequirementFor(provider: string, model: string): string | undefined;
|
|
65
76
|
/** Cross-provider pricing-peer resolution for one provider model id. */
|
|
@@ -272,6 +272,11 @@ export interface CompiledExcludeModels {
|
|
|
272
272
|
provider: string;
|
|
273
273
|
match: CompiledMatchList;
|
|
274
274
|
}
|
|
275
|
+
/** Exact upstream discovery modes excluded from one provider's coding-model roster. */
|
|
276
|
+
export interface CompiledExcludeDiscoveryModes {
|
|
277
|
+
provider: string;
|
|
278
|
+
modes: string[];
|
|
279
|
+
}
|
|
275
280
|
/** Provider plan-requirement tiers keyed by matcher token lists. */
|
|
276
281
|
export interface CompiledPlanRequirement {
|
|
277
282
|
provider: string;
|
|
@@ -289,6 +294,11 @@ export interface CompiledPricingPeer {
|
|
|
289
294
|
peerId: string;
|
|
290
295
|
}[];
|
|
291
296
|
}
|
|
297
|
+
/** Provider timezone assumption for offset-less absolute retry-reset timestamps. */
|
|
298
|
+
export interface CompiledRetryResetTimezone {
|
|
299
|
+
provider: string;
|
|
300
|
+
offset: string;
|
|
301
|
+
}
|
|
292
302
|
/** Compiled runtime behavior vocabulary (`runtime/behavior.kdl`). */
|
|
293
303
|
export interface CompiledBehavior {
|
|
294
304
|
openaiResponsesHeuristic?: CompiledResponsesHeuristic;
|
|
@@ -299,10 +309,13 @@ export interface CompiledBehavior {
|
|
|
299
309
|
hostedDefaults: CompiledHostedDefault[];
|
|
300
310
|
apiRoutes: CompiledApiRoutes[];
|
|
301
311
|
modelLimits: CompiledModelLimits[];
|
|
312
|
+
excludeDiscoveryModes: CompiledExcludeDiscoveryModes[];
|
|
302
313
|
excludeModels: CompiledExcludeModels[];
|
|
303
314
|
planRequirements: CompiledPlanRequirement[];
|
|
304
315
|
pricingPeers: CompiledPricingPeer[];
|
|
316
|
+
retryResetTimezones: CompiledRetryResetTimezone[];
|
|
305
317
|
retiredProviders: string[];
|
|
318
|
+
referenceIsolatedProviders: string[];
|
|
306
319
|
}
|
|
307
320
|
/**
|
|
308
321
|
* A string setting from `auth/*.kdl` that may be overridden by environment
|
|
@@ -647,4 +660,12 @@ export interface ResolvedAxes {
|
|
|
647
660
|
wire: Record<string, unknown>;
|
|
648
661
|
thinking: Record<string, unknown>;
|
|
649
662
|
catalog: Record<string, unknown>;
|
|
663
|
+
/**
|
|
664
|
+
* Reasoning capability after the exact-model effort upgrade: `true` when the
|
|
665
|
+
* target reported reasoning or an exact rule declares a ladder for it (the
|
|
666
|
+
* reviewed correction to metadata-less discovery rows). Compat resolvers
|
|
667
|
+
* read this instead of the raw spec flag, or one id resolves two different
|
|
668
|
+
* wire contracts depending on whether it came from discovery or the bake.
|
|
669
|
+
*/
|
|
670
|
+
reasoning: boolean;
|
|
650
671
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FetchImpl } from "@oh-my-pi/pi-utils";
|
|
1
2
|
import { type VariantCollapseTable } from "../compat/collapse.js";
|
|
2
3
|
import type { ModelSpec } from "../types.js";
|
|
3
4
|
export declare const ANTIGRAVITY_PRIMARY_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com";
|
|
@@ -37,6 +38,7 @@ export interface AntigravityDiscoveryAgentModelSort {
|
|
|
37
38
|
export interface AntigravityDiscoveryApiResponse {
|
|
38
39
|
models?: Record<string, AntigravityDiscoveryApiModel>;
|
|
39
40
|
agentModelSorts?: AntigravityDiscoveryAgentModelSort[];
|
|
41
|
+
imageGenerationModelIds?: string[];
|
|
40
42
|
}
|
|
41
43
|
/**
|
|
42
44
|
* Options for fetching Antigravity discovery models.
|
|
@@ -53,7 +55,7 @@ export interface FetchAntigravityDiscoveryModelsOptions {
|
|
|
53
55
|
/** Optional abort signal for request cancellation. */
|
|
54
56
|
signal?: AbortSignal;
|
|
55
57
|
/** Optional fetch implementation override for tests. */
|
|
56
|
-
fetcher?:
|
|
58
|
+
fetcher?: FetchImpl;
|
|
57
59
|
/**
|
|
58
60
|
* Hand collapse table to apply to the discovered list. Defaults to the
|
|
59
61
|
* Antigravity (budget-transport) table; `googleGeminiCli` passes the
|
|
@@ -68,3 +70,10 @@ export interface FetchAntigravityDiscoveryModelsOptions {
|
|
|
68
70
|
* Returns `[]` only when the endpoint responds successfully with no usable models.
|
|
69
71
|
*/
|
|
70
72
|
export declare function fetchAntigravityDiscoveryModels(options: FetchAntigravityDiscoveryModelsOptions): Promise<ModelSpec<"google-gemini-cli">[] | null>;
|
|
73
|
+
/** Advertised image model and serving endpoint for one Antigravity account. */
|
|
74
|
+
export interface AntigravityImageModel {
|
|
75
|
+
id: string;
|
|
76
|
+
endpoint: string;
|
|
77
|
+
}
|
|
78
|
+
/** Resolves the first image-generation model advertised by an Antigravity account. */
|
|
79
|
+
export declare function fetchAntigravityImageModel(options: FetchAntigravityDiscoveryModelsOptions): Promise<AntigravityImageModel | null>;
|
|
@@ -35,6 +35,13 @@ export declare function mapEffortToGoogleThinkingLevel<TApi extends Api>(effort:
|
|
|
35
35
|
/**
|
|
36
36
|
* Maps a normalized thinking effort to Anthropic adaptive effort values via
|
|
37
37
|
* the model's baked `thinking.effortMap` (identity for unmapped efforts).
|
|
38
|
+
*
|
|
39
|
+
* The Anthropic adaptive wire vocabulary has no `minimal` tier (valid values:
|
|
40
|
+
* `low`, `medium`, `high`, `xhigh`, `max`); a model ladder that exposes
|
|
41
|
+
* `minimal` — custom `anthropic-messages` providers do, unlike the built-in
|
|
42
|
+
* Claude ladders — would otherwise serialize `output_config.effort: "minimal"`
|
|
43
|
+
* and 400 (`level "minimal" not supported`). Clamp it to the lowest real tier,
|
|
44
|
+
* mirroring {@link mapEffortToGoogleThinkingLevel}'s `minimal` handling.
|
|
38
45
|
*/
|
|
39
46
|
export declare function mapEffortToAnthropicAdaptiveEffort<TApi extends Api>(model: ApiModel<TApi>, effort: Effort): "low" | "medium" | "high" | "xhigh" | "max" | "adaptive";
|
|
40
47
|
/**
|
|
@@ -523,6 +523,8 @@ export interface FetchLiteLLMRichModelsOptions<TApi extends Api> {
|
|
|
523
523
|
}
|
|
524
524
|
export declare const OPENAI_COMPAT_DISCOVERY_DEFAULT_CONTEXT_WINDOW = 128000;
|
|
525
525
|
export declare const OPENAI_COMPAT_DISCOVERY_DEFAULT_MAX_TOKENS = 32768;
|
|
526
|
+
/** Exclude only known non-conversational modes; unknown and non-string modes remain selectable for aliases. */
|
|
527
|
+
export declare function isSelectableLiteLLMModelMode(mode: unknown): boolean;
|
|
526
528
|
export declare function normalizeLiteLLMManagementBaseUrl(baseUrl: string): string;
|
|
527
529
|
export declare function resolveLiteLLMApi(entry: Record<string, unknown> | undefined, id: string, fallbackApi?: Api): Api;
|
|
528
530
|
export declare function fetchLiteLLMRichModels<TApi extends Api>(options: FetchLiteLLMRichModelsOptions<TApi>): Promise<ModelSpec<TApi>[] | null>;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -278,6 +278,12 @@ export interface OpenAICompat {
|
|
|
278
278
|
* Default: auto-detected (DeepSeek reasoning models).
|
|
279
279
|
*/
|
|
280
280
|
disableReasoningOnToolChoice?: boolean;
|
|
281
|
+
/**
|
|
282
|
+
* Disable reasoning whenever the request advertises function tools.
|
|
283
|
+
* Use for model surfaces that reject every tools-plus-reasoning combination.
|
|
284
|
+
* Default: false.
|
|
285
|
+
*/
|
|
286
|
+
disableReasoningWithTools?: boolean;
|
|
281
287
|
/** OpenRouter-specific routing preferences. Only used when baseUrl points to OpenRouter. */
|
|
282
288
|
openRouterRouting?: OpenRouterRouting;
|
|
283
289
|
/** Vercel AI Gateway routing preferences. Only used when baseUrl points to Vercel AI Gateway. */
|
|
@@ -372,12 +378,24 @@ export interface OpenAICompat {
|
|
|
372
378
|
strictResponsesPairing?: boolean;
|
|
373
379
|
/** Whether the Responses API accepts the `detail: "original"` image hint. Default: auto-detected (false for GitHub Copilot, which rejects it with a 400). */
|
|
374
380
|
supportsImageDetailOriginal?: boolean;
|
|
381
|
+
/**
|
|
382
|
+
* Whether the Responses endpoint accepts `configuration_update` input items
|
|
383
|
+
* that change `reasoning.effort` mid-conversation while the request-level
|
|
384
|
+
* effort stays pinned for prompt caching (GPT-6 Astra). Default:
|
|
385
|
+
* rule-detected (`true` for `gpt-6-astra` on any host, `false` otherwise).
|
|
386
|
+
* Set `false` for custom `openai-responses` / `openai-codex-responses`
|
|
387
|
+
* endpoints that reject the item type with HTTP 400; effort changes are then
|
|
388
|
+
* sent as the top-level `reasoning.effort`.
|
|
389
|
+
*/
|
|
390
|
+
supportsConfigurationUpdate?: boolean;
|
|
375
391
|
/** Whether streamed reasoning deltas for the same field may repeat the full cumulative text snapshot. Default: false. */
|
|
376
392
|
reasoningDeltasMayBeCumulative?: boolean;
|
|
377
393
|
/** Strip leaked DeepSeek chat-template special tokens from visible content deltas. Default: auto-detected. */
|
|
378
394
|
stripDeepseekSpecialTokens?: boolean;
|
|
379
395
|
/** Heal leaked chat-template/tool-call/thinking markup from visible content deltas. Default: auto-detected. */
|
|
380
396
|
streamMarkupHealingPattern?: OpenAIStreamMarkupHealingPattern;
|
|
397
|
+
/** Whether this wire may revise already-streamed text (`stream-revision` axis). Unassigned: append-only. */
|
|
398
|
+
streamRevision?: "none" | "possible";
|
|
381
399
|
/** Treat an empty length-finished stream as a context-window error. Default: auto-detected. */
|
|
382
400
|
emptyLengthFinishIsContextError?: boolean;
|
|
383
401
|
/** Normalize tool call ids to OpenAI's 40-character limit. Default: auto-detected. */
|
|
@@ -548,6 +566,8 @@ export interface AnthropicCompat {
|
|
|
548
566
|
export interface BedrockCompat {
|
|
549
567
|
/** Whether this endpoint accepts no checkpoints, automatic caching, or explicit cachePoint blocks. */
|
|
550
568
|
promptCacheMode?: "none" | "automatic" | "explicit";
|
|
569
|
+
/** Whether this wire may revise already-streamed text (`stream-revision` axis). Unassigned: append-only. */
|
|
570
|
+
streamRevision?: "none" | "possible";
|
|
551
571
|
/** Whether explicit cachePoint blocks accept `ttl: "1h"`; omitted TTL means Bedrock's 5-minute default. */
|
|
552
572
|
supportsLongPromptCacheRetention?: boolean;
|
|
553
573
|
/**
|
|
@@ -571,6 +591,8 @@ export interface BedrockCompat {
|
|
|
571
591
|
/** Fully-resolved Bedrock Converse prompt-cache capabilities, materialized once by `buildModel`. */
|
|
572
592
|
export interface ResolvedBedrockCompat {
|
|
573
593
|
promptCacheMode: NonNullable<BedrockCompat["promptCacheMode"]>;
|
|
594
|
+
/** See {@link BedrockCompat.streamRevision}. */
|
|
595
|
+
streamRevision?: BedrockCompat["streamRevision"];
|
|
574
596
|
supportsLongPromptCacheRetention: boolean;
|
|
575
597
|
promptCacheMinimumTokens: number;
|
|
576
598
|
promptCacheMaximumCheckpoints: number;
|
|
@@ -632,6 +654,7 @@ export interface ResolvedOpenAISharedCompat {
|
|
|
632
654
|
filterReasoningHistory: boolean;
|
|
633
655
|
disableReasoningOnForcedToolChoice: boolean;
|
|
634
656
|
disableReasoningOnToolChoice: boolean;
|
|
657
|
+
disableReasoningWithTools?: boolean;
|
|
635
658
|
supportsToolChoice: boolean;
|
|
636
659
|
supportsForcedToolChoice: boolean;
|
|
637
660
|
supportsNamedToolChoice: boolean;
|
|
@@ -649,6 +672,8 @@ export interface ResolvedOpenAISharedCompat {
|
|
|
649
672
|
requiresAssistantContentForToolCalls: boolean;
|
|
650
673
|
stripDeepseekSpecialTokens: boolean;
|
|
651
674
|
streamMarkupHealingPattern?: OpenAIStreamMarkupHealingPattern;
|
|
675
|
+
/** See {@link OpenAICompat.streamRevision}. */
|
|
676
|
+
streamRevision?: OpenAICompat["streamRevision"];
|
|
652
677
|
/** See {@link OpenAICompat.streamFirstEventTimeoutMs}. */
|
|
653
678
|
streamFirstEventTimeoutMs?: number;
|
|
654
679
|
reasoningDeltasMayBeCumulative: boolean;
|
|
@@ -689,7 +714,7 @@ export interface ResolvedOpenAISharedCompat {
|
|
|
689
714
|
* `buildModel`; request handlers read fields and never detect, resolve, or
|
|
690
715
|
* allocate.
|
|
691
716
|
*/
|
|
692
|
-
export type ResolvedOpenAICompat = ResolvedOpenAISharedCompat & Required<Omit<OpenAICompat, "supportsDeveloperRole" | "supportsReasoningEffort" | "reasoningEffortMap" | "supportsReasoningParams" | "supportsReasoningSummary" | "supportsSamplingParams" | "supportsPenaltyAndStopParams" | "thinkingFormat" | "kimiApiFormat" | "reasoningDisableMode" | "omitReasoningEffort" | "includeEncryptedReasoning" | "filterReasoningHistory" | "disableReasoningOnForcedToolChoice" | "disableReasoningOnToolChoice" | "supportsToolChoice" | "supportsForcedToolChoice" | "supportsNamedToolChoice" | "reasoningContentField" | "requiresReasoningContentForToolCalls" | "requiresReasoningContentForAllAssistantTurns" | "allowsSyntheticReasoningContentForToolCalls" | "replayReasoningContent" | "qwenPreserveThinking" | "qwenTemplateReasoningEffort" | "requiresThinkingAsText" | "requiresMistralToolIds" | "requiresToolResultName" | "requiresAssistantAfterToolResult" | "requiresAssistantContentForToolCalls" | "stripDeepseekSpecialTokens" | "streamMarkupHealingPattern" | "reasoningDeltasMayBeCumulative" | "emptyLengthFinishIsContextError" | "usesOpenAIToolCallIdLimit" | "promptCacheSessionHeader" | "supportsPromptCacheBreakpoints" | "promptCacheBreakpointTtl" | "openRouterRouting" | "isOpenRouterHost" | "supportsStrictMode" | "supportsLongPromptCacheRetention" | "alwaysSendMaxTokens" | "clampOutputToModelMax" | "wireModelIdMode" | "vercelGatewayRouting" | "extraBody" | "toolStrictMode" | "toolSchemaFlavor" | "streamFirstEventTimeoutMs" | "streamIdleTimeoutMs" | "cacheControlFormat" | "thinkingKeep" | "strictResponsesPairing" | "supportsImageDetailOriginal" | "stripImageInput" | "thinkingLoopGuard" | "whenThinking">> & {
|
|
717
|
+
export type ResolvedOpenAICompat = ResolvedOpenAISharedCompat & Required<Omit<OpenAICompat, "supportsDeveloperRole" | "supportsReasoningEffort" | "reasoningEffortMap" | "supportsReasoningParams" | "supportsReasoningSummary" | "supportsSamplingParams" | "supportsPenaltyAndStopParams" | "thinkingFormat" | "kimiApiFormat" | "reasoningDisableMode" | "omitReasoningEffort" | "includeEncryptedReasoning" | "filterReasoningHistory" | "disableReasoningOnForcedToolChoice" | "disableReasoningOnToolChoice" | "disableReasoningWithTools" | "supportsToolChoice" | "supportsForcedToolChoice" | "supportsNamedToolChoice" | "reasoningContentField" | "requiresReasoningContentForToolCalls" | "requiresReasoningContentForAllAssistantTurns" | "allowsSyntheticReasoningContentForToolCalls" | "replayReasoningContent" | "qwenPreserveThinking" | "qwenTemplateReasoningEffort" | "requiresThinkingAsText" | "requiresMistralToolIds" | "requiresToolResultName" | "requiresAssistantAfterToolResult" | "requiresAssistantContentForToolCalls" | "stripDeepseekSpecialTokens" | "streamMarkupHealingPattern" | "reasoningDeltasMayBeCumulative" | "emptyLengthFinishIsContextError" | "usesOpenAIToolCallIdLimit" | "promptCacheSessionHeader" | "supportsPromptCacheBreakpoints" | "promptCacheBreakpointTtl" | "openRouterRouting" | "isOpenRouterHost" | "supportsStrictMode" | "supportsLongPromptCacheRetention" | "alwaysSendMaxTokens" | "clampOutputToModelMax" | "wireModelIdMode" | "vercelGatewayRouting" | "extraBody" | "toolStrictMode" | "toolSchemaFlavor" | "streamFirstEventTimeoutMs" | "streamIdleTimeoutMs" | "streamRevision" | "cacheControlFormat" | "thinkingKeep" | "strictResponsesPairing" | "supportsImageDetailOriginal" | "supportsConfigurationUpdate" | "stripImageInput" | "thinkingLoopGuard" | "whenThinking">> & {
|
|
693
718
|
vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"];
|
|
694
719
|
extraBody?: OpenAICompat["extraBody"];
|
|
695
720
|
cacheControlFormat?: OpenAICompat["cacheControlFormat"];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-catalog",
|
|
4
|
-
"version": "18.2.
|
|
4
|
+
"version": "18.2.1",
|
|
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": "Stencil Labs, Inc.",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"gen:proto": "bun scripts/generate-protocols.ts"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@oh-my-pi/omptype": "18.2.
|
|
39
|
-
"@oh-my-pi/pi-utils": "18.2.
|
|
38
|
+
"@oh-my-pi/omptype": "18.2.1",
|
|
39
|
+
"@oh-my-pi/pi-utils": "18.2.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@bgotink/kdl": "0.4.0",
|
|
43
|
-
"@oh-my-pi/pi-ai": "18.2.
|
|
43
|
+
"@oh-my-pi/pi-ai": "18.2.1",
|
|
44
44
|
"@types/bun": "^1.3.14"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
package/src/compat/axes.ts
CHANGED
|
@@ -93,6 +93,7 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
93
93
|
"clamp-output-to-model-max": wire("clampOutputToModelMax", OAI),
|
|
94
94
|
"disable-reasoning-on-forced-tool-choice": wire("disableReasoningOnForcedToolChoice", OAI),
|
|
95
95
|
"disable-reasoning-on-tool-choice": wire("disableReasoningOnToolChoice", OAI),
|
|
96
|
+
"disable-reasoning-with-tools": wire("disableReasoningWithTools", ["openai"]),
|
|
96
97
|
"drop-thinking-when-reasoning-effort": wire("dropThinkingWhenReasoningEffort", ["openai"]),
|
|
97
98
|
"empty-length-finish-is-context-error": wire("emptyLengthFinishIsContextError", OAI),
|
|
98
99
|
"extra-body": { ...wire("extraBody", ["openai"], "object"), verbatimKeys: true },
|
|
@@ -235,6 +236,19 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
235
236
|
"supports-function-part-id": wire("supportsFunctionPartId", ["google"]),
|
|
236
237
|
|
|
237
238
|
// ── wire: shared across surfaces ──
|
|
239
|
+
/**
|
|
240
|
+
* Whether this wire may revise text it has already streamed: bytes
|
|
241
|
+
* reclassified out of the visible channel (a leaned-on thinking opener),
|
|
242
|
+
* carved into a tool call, reordered by content-block index, or replaced
|
|
243
|
+
* wholesale by an authoritative final payload. Unassigned means the wire
|
|
244
|
+
* only appends, so the transcript may retire finished lines into native
|
|
245
|
+
* scrollback while the turn is still streaming (see
|
|
246
|
+
* `AssistantMessageComponent`). Declare `possible` only with a citable
|
|
247
|
+
* mechanism: the renderer also verifies published rows every frame and stops
|
|
248
|
+
* retiring the block on the first mismatch, so this axis decides where
|
|
249
|
+
* mid-stream retirement is attempted, not whether it is safe.
|
|
250
|
+
*/
|
|
251
|
+
"stream-revision": wire("streamRevision", [...OAI, "bedrock"], "scalar", ["none", "possible"]),
|
|
238
252
|
"stream-first-event-timeout-ms": wire("streamFirstEventTimeoutMs", [...OAI, "google"]),
|
|
239
253
|
"stream-idle-timeout-ms": wire("streamIdleTimeoutMs", [...OAI, "anthropic", "bedrock", "google"]),
|
|
240
254
|
"strip-image-input": wire("stripImageInput", [...OAI, "anthropic", "google"]),
|
package/src/compat/behavior.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Typed accessors over the compiled runtime-behavior vocabulary
|
|
3
3
|
* (`rules/runtime/behavior.kdl`): provider/model heuristics that run before
|
|
4
4
|
* or outside exact bundled-model lookup — responses routing, API routing,
|
|
5
|
-
* quota tiers, plan requirements, model limits, roster
|
|
6
|
-
* defaults, and pricing peers.
|
|
5
|
+
* quota tiers, plan requirements, model limits, roster and discovery-mode
|
|
6
|
+
* exclusions, hosted defaults, and pricing peers.
|
|
7
7
|
*/
|
|
8
8
|
import { globMatch } from "./cascade";
|
|
9
9
|
import rules from "./rules.json";
|
|
@@ -103,6 +103,11 @@ export function quotaTierFor(provider: string, model: string): string | undefine
|
|
|
103
103
|
return undefined;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/** UTC offset for a provider's timezone-naive absolute retry-reset timestamps. */
|
|
107
|
+
export function retryResetTimezoneOffsetFor(provider: string): string | undefined {
|
|
108
|
+
return behavior.retryResetTimezones.find(rule => rule.provider === provider)?.offset;
|
|
109
|
+
}
|
|
110
|
+
|
|
106
111
|
/** Whether a provider has catalog-authored model quota scopes. */
|
|
107
112
|
export function hasQuotaTierPolicy(provider: string): boolean {
|
|
108
113
|
return behavior.quotaTiers.some(rule => rule.provider === provider);
|
|
@@ -176,6 +181,11 @@ export function isExcludedModel(provider: string, model: string): boolean {
|
|
|
176
181
|
return behavior.excludeModels.some(rule => rule.provider === provider && matchesList(rule.match, lower, lower));
|
|
177
182
|
}
|
|
178
183
|
|
|
184
|
+
/** Whether an exact upstream discovery mode is excluded from a provider's coding-model roster. */
|
|
185
|
+
export function isExcludedDiscoveryMode(provider: string, mode: string): boolean {
|
|
186
|
+
return behavior.excludeDiscoveryModes.some(rule => rule.provider === provider && rule.modes.includes(mode));
|
|
187
|
+
}
|
|
188
|
+
|
|
179
189
|
/**
|
|
180
190
|
* Whether a provider is retired: its entire roster is excluded from the
|
|
181
191
|
* generated bundle and must never be resurrected from the previous snapshot.
|
|
@@ -184,6 +194,16 @@ export function isRetiredProvider(provider: string): boolean {
|
|
|
184
194
|
return behavior.retiredProviders.includes(provider);
|
|
185
195
|
}
|
|
186
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Whether a provider's bundled rows may seed cross-provider bare-id enrichment
|
|
199
|
+
* references. Gateway-namespaced providers (ClinePass) are isolated so their
|
|
200
|
+
* limits, pricing, and reasoning controls never contaminate an unrelated proxy
|
|
201
|
+
* model that happens to advertise the same bare id.
|
|
202
|
+
*/
|
|
203
|
+
export function isBareIdReferenceProvider(provider: string): boolean {
|
|
204
|
+
return !behavior.referenceIsolatedProviders.includes(provider);
|
|
205
|
+
}
|
|
206
|
+
|
|
187
207
|
/** The declared subscription tier required to use a provider model id, if any. */
|
|
188
208
|
export function planRequirementFor(provider: string, model: string): string | undefined {
|
|
189
209
|
const rule = behavior.planRequirements.find(candidate => candidate.provider === provider);
|
package/src/compat/cascade.ts
CHANGED
|
@@ -293,6 +293,7 @@ function cloneAxes(axes: ResolvedAxes): ResolvedAxes {
|
|
|
293
293
|
wire: cloneAxisRecord(axes.wire),
|
|
294
294
|
thinking: cloneAxisRecord(axes.thinking),
|
|
295
295
|
catalog: cloneAxisRecord(axes.catalog),
|
|
296
|
+
reasoning: axes.reasoning,
|
|
296
297
|
};
|
|
297
298
|
}
|
|
298
299
|
|
|
@@ -326,7 +327,7 @@ export function resolveCascadeRules(cascade: CompiledCascade, target: ResolveTar
|
|
|
326
327
|
|
|
327
328
|
function resolveOverIndex(index: RuleIndex, target: ResolveTarget): ResolvedAxes {
|
|
328
329
|
const ranked = rankRelevantRules(index, prepareTarget(target));
|
|
329
|
-
let reasoning = target.reasoning;
|
|
330
|
+
let reasoning = target.reasoning === true;
|
|
330
331
|
if (!reasoning) {
|
|
331
332
|
for (const { rule, rank } of ranked) {
|
|
332
333
|
if (rule.hasExactEffortsRule && rank[0] === 2) {
|
|
@@ -347,5 +348,6 @@ function resolveOverIndex(index: RuleIndex, target: ResolveTarget): ResolvedAxes
|
|
|
347
348
|
wire: collect(wire, rule => rule.wire),
|
|
348
349
|
thinking: collect(thinking, rule => rule.thinking),
|
|
349
350
|
catalog: collect(catalog, rule => rule.catalog),
|
|
351
|
+
reasoning,
|
|
350
352
|
};
|
|
351
353
|
}
|
|
@@ -6,8 +6,11 @@ import { resolveModelPolicy } from "./resolve";
|
|
|
6
6
|
* Rule-owned maxima by provider/id/api. Resolve once per process rather than
|
|
7
7
|
* walking the static policy cascade on every catalog rebuild. Null caches the
|
|
8
8
|
* absence of a curated maximum; undefined means the key has not been resolved.
|
|
9
|
+
* Bounded: one entry per distinct model; the wire-id set is bounded.
|
|
9
10
|
*/
|
|
10
11
|
const ruleMaximumCache = new Map<string, number | null>();
|
|
12
|
+
const clampOverrideCache = new Map<string, boolean>();
|
|
13
|
+
const RULE_POLICY_CACHE_MAX = 8192;
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* Extended-context capacity. Curated maxima correct stale lower discovery
|
|
@@ -20,6 +23,7 @@ export function resolveMaxContextWindow(model: Model): number | undefined {
|
|
|
20
23
|
if (curated === undefined) {
|
|
21
24
|
const maximum = resolveModelPolicy(toModelSpec(model)).catalog.maxContextWindow;
|
|
22
25
|
curated = typeof maximum === "number" && Number.isFinite(maximum) && maximum > 0 ? maximum : null;
|
|
26
|
+
if (ruleMaximumCache.size >= RULE_POLICY_CACHE_MAX) ruleMaximumCache.clear();
|
|
23
27
|
ruleMaximumCache.set(key, curated);
|
|
24
28
|
}
|
|
25
29
|
|
|
@@ -49,7 +53,13 @@ export function codexOverrideCeiling(model: Model): number | undefined {
|
|
|
49
53
|
* it here keeps provider deployment contracts out of TypeScript.
|
|
50
54
|
*/
|
|
51
55
|
export function clampsContextOverride(model: Model): boolean {
|
|
52
|
-
|
|
56
|
+
const key = `${model.provider} ${model.id} ${model.api}`;
|
|
57
|
+
const cached = clampOverrideCache.get(key);
|
|
58
|
+
if (cached !== undefined) return cached;
|
|
59
|
+
const clamps = resolveModelPolicy(toModelSpec(model)).catalog.clampContextOverride === true;
|
|
60
|
+
if (clampOverrideCache.size >= RULE_POLICY_CACHE_MAX) clampOverrideCache.clear();
|
|
61
|
+
clampOverrideCache.set(key, clamps);
|
|
62
|
+
return clamps;
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
/**
|
package/src/compat/resolve.ts
CHANGED
|
@@ -305,7 +305,11 @@ interface OpenAIDetection {
|
|
|
305
305
|
isOpenRouter: boolean;
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
function detectOpenAI(
|
|
308
|
+
function detectOpenAI(
|
|
309
|
+
spec: ModelSpec<"openai-completions" | "openrouter">,
|
|
310
|
+
facts: IdentityFacts,
|
|
311
|
+
reasoningCapable: boolean,
|
|
312
|
+
): OpenAIDetection {
|
|
309
313
|
const provider = spec.provider;
|
|
310
314
|
const baseUrl = spec.baseUrl;
|
|
311
315
|
const hostModel = { provider, baseUrl };
|
|
@@ -315,7 +319,7 @@ function detectOpenAI(spec: ModelSpec<"openai-completions" | "openrouter">, fact
|
|
|
315
319
|
const isXiaomiHost = modelMatchesHost(hostModel, "xiaomi");
|
|
316
320
|
const isDirectDeepseekApi = modelMatchesHost(hostModel, "deepseekDirect");
|
|
317
321
|
const isDeepseekFamily = modelMatchesHost(hostModel, "deepseekFamily") || facts.is("deepseek");
|
|
318
|
-
const isDeepseekReasoning = isDeepseekFamily &&
|
|
322
|
+
const isDeepseekReasoning = isDeepseekFamily && reasoningCapable;
|
|
319
323
|
const isLocalOpenAICompatBackend =
|
|
320
324
|
PROXY_OPENAI_COMPAT_PROVIDERS[provider] !== true &&
|
|
321
325
|
(LOCAL_OPENAI_COMPAT_PROVIDERS[provider] === true || hasLocalLoopbackBaseUrl(baseUrl));
|
|
@@ -348,6 +352,7 @@ function detectOpenAI(spec: ModelSpec<"openai-completions" | "openrouter">, fact
|
|
|
348
352
|
function detectOpenAICompat(
|
|
349
353
|
spec: ModelSpec<"openai-completions" | "openrouter">,
|
|
350
354
|
d: OpenAIDetection,
|
|
355
|
+
reasoningCapable: boolean,
|
|
351
356
|
): ResolvedOpenAICompat {
|
|
352
357
|
const provider = spec.provider;
|
|
353
358
|
const baseUrl = spec.baseUrl;
|
|
@@ -435,12 +440,12 @@ function detectOpenAICompat(
|
|
|
435
440
|
? GLM_CODING_PLAN_STREAM_IDLE_TIMEOUT_MS
|
|
436
441
|
: facts.is("mimo") && hostMatchesUrl(baseUrl, "xiaomi")
|
|
437
442
|
? 300_000
|
|
438
|
-
:
|
|
443
|
+
: reasoningCapable &&
|
|
439
444
|
facts.is("kimi") &&
|
|
440
445
|
(facts.family("k3") || facts.family("k2.7-code")) &&
|
|
441
446
|
hostMatchesUrl(baseUrl, "moonshotNative")
|
|
442
447
|
? 300_000
|
|
443
|
-
:
|
|
448
|
+
: reasoningCapable && facts.is("deepseek") && hostMatchesUrl(baseUrl, "deepseekDirect")
|
|
444
449
|
? 300_000
|
|
445
450
|
: d.isLocalServingBackend
|
|
446
451
|
? LOCAL_OPENAI_COMPAT_STREAM_IDLE_TIMEOUT_MS
|
|
@@ -476,13 +481,14 @@ function detectOpenAICompat(
|
|
|
476
481
|
// provider rule without changing Copilot Responses rows.
|
|
477
482
|
supportsReasoningParams: provider !== "github-copilot",
|
|
478
483
|
supportsSamplingParams: !(facts.is("openai") && (facts.family("o-series") || facts.revGte("5"))),
|
|
479
|
-
supportsPenaltyAndStopParams: !(isGrok &&
|
|
484
|
+
supportsPenaltyAndStopParams: !(isGrok && reasoningCapable),
|
|
480
485
|
reasoningEffortMap: {},
|
|
481
486
|
supportsUsageInStreaming: !isCerebrasHost,
|
|
482
487
|
alwaysSendMaxTokens: facts.is("kimi"),
|
|
483
488
|
disableReasoningOnForcedToolChoice:
|
|
484
489
|
!d.isClinePass && ((facts.is("kimi") && !isMoonshotKimiK3) || isAnthropicModel),
|
|
485
|
-
disableReasoningOnToolChoice: !d.isClinePass && isDeepseekFamily &&
|
|
490
|
+
disableReasoningOnToolChoice: !d.isClinePass && isDeepseekFamily && reasoningCapable && !d.isOpenRouter,
|
|
491
|
+
disableReasoningWithTools: false,
|
|
486
492
|
supportsToolChoice: d.isClinePass || !d.isDirectDeepseekReasoning,
|
|
487
493
|
supportsForcedToolChoice:
|
|
488
494
|
!d.requiresEnabledThinking && !(d.isOpenCodeHost && d.isDeepseekReasoning) && !(d.isClinePass && isQwen),
|
|
@@ -506,12 +512,12 @@ function detectOpenAICompat(
|
|
|
506
512
|
reasoningContentField: d.isClinePass ? "reasoning" : "reasoning_content",
|
|
507
513
|
requiresReasoningContentForToolCalls:
|
|
508
514
|
(facts.is("kimi") && !d.isOpenCodeProvider) ||
|
|
509
|
-
(isDeepseekFamily &&
|
|
515
|
+
(isDeepseekFamily && reasoningCapable) ||
|
|
510
516
|
d.isXiaomiMimo ||
|
|
511
|
-
(d.isOpenRouter &&
|
|
517
|
+
(d.isOpenRouter && reasoningCapable),
|
|
512
518
|
requiresReasoningContentForAllAssistantTurns:
|
|
513
|
-
((isDeepseekFamily &&
|
|
514
|
-
allowsSyntheticReasoningContentForToolCalls: (!isDeepseekFamily || !
|
|
519
|
+
((isDeepseekFamily && reasoningCapable) || d.isXiaomiMimo) && !d.isOpenRouter,
|
|
520
|
+
allowsSyntheticReasoningContentForToolCalls: (!isDeepseekFamily || !reasoningCapable) && !d.isXiaomiMimo,
|
|
515
521
|
replayReasoningContent: d.isLocalOpenAICompatBackend,
|
|
516
522
|
qwenPreserveThinking:
|
|
517
523
|
(thinkingFormat === "qwen" || thinkingFormat === "qwen-chat-template") && d.isLocalOpenAICompatBackend,
|
|
@@ -577,6 +583,11 @@ const DSML_HEALING_PROVIDERS: Record<string, true> = {
|
|
|
577
583
|
nanogpt: true,
|
|
578
584
|
"opencode-go": true,
|
|
579
585
|
openrouter: true,
|
|
586
|
+
// Transparent gateways / user-configured hosts forward the upstream model's
|
|
587
|
+
// native chat template unchanged, so a deepseek-classed model behind them
|
|
588
|
+
// still emits DSML tool-call envelopes and needs the DSML healer.
|
|
589
|
+
litellm: true,
|
|
590
|
+
nous: true,
|
|
580
591
|
};
|
|
581
592
|
|
|
582
593
|
/**
|
|
@@ -635,7 +646,7 @@ function fixupOpenAICompat(
|
|
|
635
646
|
compat.omitReasoningEffort = true;
|
|
636
647
|
}
|
|
637
648
|
|
|
638
|
-
const axisWhenThinking = spec
|
|
649
|
+
const axisWhenThinking = compatReasoning(spec, axes) ? objectPayload(axes.wire.whenThinking) : undefined;
|
|
639
650
|
const whenThinkingPolicy =
|
|
640
651
|
spec.compat?.whenThinking ??
|
|
641
652
|
axisWhenThinking ??
|
|
@@ -662,8 +673,9 @@ function resolveOpenAICompletionsPolicy(
|
|
|
662
673
|
facts: IdentityFacts,
|
|
663
674
|
axes: ResolvedAxes,
|
|
664
675
|
): ResolvedOpenAICompat {
|
|
665
|
-
const
|
|
666
|
-
const
|
|
676
|
+
const reasoningCapable = compatReasoning(spec, axes);
|
|
677
|
+
const d = detectOpenAI(spec, facts, reasoningCapable);
|
|
678
|
+
const compat = detectOpenAICompat(spec, d, reasoningCapable);
|
|
667
679
|
applyWireAxes(compat, axes.wire, "openai-completions");
|
|
668
680
|
applyCompatOverrides(compat, spec.compat);
|
|
669
681
|
overlayEffortMapAxis(compat, axes, spec.compat);
|
|
@@ -689,7 +701,7 @@ function resolveOpenAIResponsesPolicy(
|
|
|
689
701
|
const supportsPromptCacheBreakpoints =
|
|
690
702
|
isOfficialOpenAIEndpoint(provider, baseUrl) && facts.is("openai") && facts.revGte("5.6");
|
|
691
703
|
const thinkingFormat: ResolvedOpenAISharedCompat["thinkingFormat"] = isOpenRouter ? "openrouter" : "openai";
|
|
692
|
-
const reasoningCapable =
|
|
704
|
+
const reasoningCapable = compatReasoning(spec, axes);
|
|
693
705
|
const isLocalServingBackend =
|
|
694
706
|
(PROXY_OPENAI_COMPAT_PROVIDERS[provider] !== true && LOCAL_OPENAI_COMPAT_PROVIDERS[provider] === true) ||
|
|
695
707
|
hasLocalLoopbackBaseUrl(baseUrl);
|
|
@@ -729,6 +741,7 @@ function resolveOpenAIResponsesPolicy(
|
|
|
729
741
|
filterReasoningHistory: isOpenRouter && isAnthropicModel,
|
|
730
742
|
disableReasoningOnForcedToolChoice: facts.is("kimi"),
|
|
731
743
|
disableReasoningOnToolChoice: isDeepseekFamily && reasoningCapable && !isOpenRouter,
|
|
744
|
+
disableReasoningWithTools: false,
|
|
732
745
|
supportsToolChoice: true,
|
|
733
746
|
supportsForcedToolChoice: provider !== "opencode-go" && provider !== "opencode-zen",
|
|
734
747
|
supportsNamedToolChoice: true,
|
|
@@ -860,7 +873,8 @@ function resolveAnthropicPolicy(
|
|
|
860
873
|
supportsSamplingParams: !facts.anthropicAdaptiveGenAtLeast("4.7"),
|
|
861
874
|
requiresToolResultId: false,
|
|
862
875
|
requiresThinkingEnabled,
|
|
863
|
-
replayUnsignedThinking:
|
|
876
|
+
replayUnsignedThinking:
|
|
877
|
+
!signingEndpoint && (compatReasoning(spec, axes) || modelMatchesHost(spec, "deepseekFamily")),
|
|
864
878
|
escapeBuiltinToolNames: false,
|
|
865
879
|
injectClaudeCodeInstruction: true,
|
|
866
880
|
stripImageInput: false,
|
|
@@ -884,7 +898,7 @@ function resolveBedrockPolicy(spec: ModelSpec<"bedrock-converse-stream">, axes:
|
|
|
884
898
|
promptCacheMaximumCheckpoints: 0,
|
|
885
899
|
};
|
|
886
900
|
// Reasoning capability is a mechanism gate; adaptive-lineage duration is rule-owned.
|
|
887
|
-
compat.streamIdleTimeoutMs = spec
|
|
901
|
+
compat.streamIdleTimeoutMs = compatReasoning(spec, axes) ? BEDROCK_REASONING_STREAM_IDLE_TIMEOUT_MS : undefined;
|
|
888
902
|
applyWireAxes(compat, axes.wire, "bedrock-converse-stream");
|
|
889
903
|
applyCompatOverrides(compat, spec.compat);
|
|
890
904
|
return compat;
|
|
@@ -1047,6 +1061,18 @@ function readRuleThinking(axes: ResolvedAxes): RuleThinking {
|
|
|
1047
1061
|
return out;
|
|
1048
1062
|
}
|
|
1049
1063
|
|
|
1064
|
+
/**
|
|
1065
|
+
* Compat-time reasoning capability. `axes.reasoning` also promotes targets on
|
|
1066
|
+
* any exact `thinking-efforts` rule (the cascade's thinking-axis gate), but
|
|
1067
|
+
* compat may only be repaired where the provider contract opted in with
|
|
1068
|
+
* `thinking-upgrade-neutral`; everywhere else a spec that reports no reasoning
|
|
1069
|
+
* stays the authoritative capability surface.
|
|
1070
|
+
*/
|
|
1071
|
+
function compatReasoning<TApi extends Api>(spec: ModelSpec<TApi>, axes: ResolvedAxes): boolean {
|
|
1072
|
+
if (spec.reasoning) return true;
|
|
1073
|
+
return axes.reasoning && readRuleThinking(axes).upgradeNeutral === true;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1050
1076
|
/** Identity-derived `requiresEffort` default (mandatory-reasoning lineages). */
|
|
1051
1077
|
function impliesMandatoryReasoning(facts: IdentityFacts, modelId: string): boolean {
|
|
1052
1078
|
if (facts.identity.thinkingVariant) return true;
|
|
@@ -288,15 +288,17 @@ behavior {
|
|
|
288
288
|
route "openai-completions" prefix="openai/" strip-prefix=#true
|
|
289
289
|
}
|
|
290
290
|
model-limits provider="github-copilot" { limits "gpt-5.6" context=272000 max-tokens=128000 }
|
|
291
|
+
exclude-discovery-modes "embedding" "moderation" provider="litellm"
|
|
291
292
|
exclude-models provider="nanogpt" substring="embed" substring="tts"
|
|
292
293
|
plan-requirement provider="openai-codex" { tier "pro" substring="-spark" }
|
|
294
|
+
retry-reset-timezone provider="zai" offset="+08:00"
|
|
293
295
|
pricing-peer provider="google-antigravity" peers="google" "google-vertex" {
|
|
294
296
|
alias "gemini-3-pro" peer-id="gemini-3-pro-preview"
|
|
295
297
|
}
|
|
296
298
|
}
|
|
297
299
|
```
|
|
298
300
|
|
|
299
|
-
Matcher properties on `route` / `exclude-models` / `tier` nodes are `exact=` / `prefix=` / `substring=` / `glob=`, repeatable. `strip-prefix=#true` on a prefix route strips the matched prefix off the wire id. Values are copied verbatim from the TS constants they replaced; runtime accessors live in `src/compat/behavior.ts`.
|
|
301
|
+
`exclude-discovery-modes` takes one or more exact, case-sensitive upstream mode strings plus `provider=`; discovery mappers preserve missing, malformed, and unknown modes unless the provider policy explicitly lists them. Matcher properties on `route` / `exclude-models` / `tier` nodes are `exact=` / `prefix=` / `substring=` / `glob=`, repeatable. `strip-prefix=#true` on a prefix route strips the matched prefix off the wire id. Values are copied verbatim from the TS constants they replaced; runtime accessors live in `src/compat/behavior.ts`.
|
|
300
302
|
|
|
301
303
|
## Auth grammar
|
|
302
304
|
|
|
@@ -84,7 +84,15 @@ class "deepseek" {
|
|
|
84
84
|
models "*v4.1-flash*" priority=10 {
|
|
85
85
|
strip-image-input #false
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
// V4.1 Flash is natively multimodal (vision encoder + projector, image-text
|
|
88
|
+
// pre-training) but its release id carries no `vision` token to key on.
|
|
89
|
+
// Same priority as the V4.1 Flash carve-out: the OpenCode Go provider rule
|
|
90
|
+
// declares the same value for its `deepseek-flash` lane and would otherwise
|
|
91
|
+
// tie at equal rank.
|
|
92
|
+
models "deepseek-flash" priority=10 {
|
|
93
|
+
strip-image-input #false
|
|
94
|
+
}
|
|
95
|
+
on "ollama-cloud" "nvidia" "deepseek" "fireworks" "nanogpt" "opencode-go" "openrouter" "litellm" "nous" {
|
|
88
96
|
stream-markup-healing-pattern "dsml"
|
|
89
97
|
}
|
|
90
98
|
thinking-loop-guard "deepseek"
|