@oh-my-pi/pi-catalog 18.2.0 → 18.2.2
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 +39 -0
- package/dist/types/compat/behavior.d.ts +11 -0
- package/dist/types/compat/cascade.d.ts +10 -0
- package/dist/types/compat/collapse.d.ts +8 -0
- package/dist/types/compat/resolve.d.ts +10 -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 +36 -1
- package/package.json +4 -4
- package/src/build.ts +59 -0
- package/src/compat/axes.ts +15 -0
- package/src/compat/behavior.ts +22 -2
- package/src/compat/cascade.ts +27 -1
- package/src/compat/collapse.ts +100 -8
- package/src/compat/context-window.ts +11 -1
- package/src/compat/resolve.ts +56 -17
- 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/devin.kdl +46 -0
- package/src/compat/rules/providers/google-vertex.kdl +15 -0
- package/src/compat/rules/providers/kimi-code.kdl +48 -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/discovery/devin.ts +22 -4
- package/src/identity/bundled.ts +4 -3
- package/src/model-cache.ts +154 -120
- 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 +297 -37
- package/src/types.ts +38 -0
package/src/compat/collapse.ts
CHANGED
|
@@ -734,6 +734,80 @@ function reconcileDefaultMember<TSpec extends VariantSpecLike>(
|
|
|
734
734
|
return spec;
|
|
735
735
|
}
|
|
736
736
|
|
|
737
|
+
/**
|
|
738
|
+
* Whether a Cursor wire id names an extended tier that upstream serves only in
|
|
739
|
+
* max mode. The compiled taxonomy identifies `xhigh`/`extra-high`/`max`
|
|
740
|
+
* efforts and their optional service lanes. This is an inference, not an
|
|
741
|
+
* upstream marker: it is the only per-tier signal available for bundled rows
|
|
742
|
+
* and for routes live discovery never advertised.
|
|
743
|
+
*/
|
|
744
|
+
export function isCursorMaxModeWireId(wireModelId: string): boolean {
|
|
745
|
+
const effort = collapseVariantId("cursor", wireModelId).effort;
|
|
746
|
+
return effort === Effort.XHigh || effort === Effort.Max;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Recover Cursor's max-mode marker for a bundled collapsed row. The bundled
|
|
751
|
+
* snapshot may contain only the logical row, but its effort routing still
|
|
752
|
+
* records the wire ids, so {@link isCursorMaxModeWireId} preserves the
|
|
753
|
+
* transport invariant even when live discovery contributes no raw members.
|
|
754
|
+
*/
|
|
755
|
+
function reconcileCursorMaxModeFromRouting<TSpec extends VariantSpecLike>(spec: TSpec): TSpec {
|
|
756
|
+
if (spec.provider !== "cursor" || spec.cursorMaxMode === true) return spec;
|
|
757
|
+
const routing = spec.thinking?.effortRouting;
|
|
758
|
+
if (routing === undefined) return spec;
|
|
759
|
+
const hasMaxModeRoute = Object.values(routing).some(
|
|
760
|
+
(target): target is string => typeof target === "string" && isCursorMaxModeWireId(target),
|
|
761
|
+
);
|
|
762
|
+
return hasMaxModeRoute ? { ...spec, cursorMaxMode: true } : spec;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Index the discovered `max_mode` marker of every live Cursor member by its own
|
|
767
|
+
* wire id. The collapsed row's `cursorMaxMode` is an OR across members, so it
|
|
768
|
+
* says nothing per tier; the transport needs the marker addressable by the wire
|
|
769
|
+
* id it actually sends, because upstream marks tiers the slug cannot identify
|
|
770
|
+
* (the whole Opus `-fast` lane is max-mode, `-low-fast` included). Returns
|
|
771
|
+
* `undefined` when no member carries a marker, so unmarked rosters add no field.
|
|
772
|
+
*/
|
|
773
|
+
function cursorMaxModeRoutesOf<TSpec extends VariantSpecLike>(
|
|
774
|
+
provider: string,
|
|
775
|
+
memberSpecs: readonly TSpec[],
|
|
776
|
+
): Record<string, boolean> | undefined {
|
|
777
|
+
if (provider !== "cursor") return undefined;
|
|
778
|
+
let routes: Record<string, boolean> | undefined;
|
|
779
|
+
for (const member of memberSpecs) {
|
|
780
|
+
if (member.cursorMaxMode === undefined) continue;
|
|
781
|
+
routes ??= {};
|
|
782
|
+
routes[member.id] = member.cursorMaxMode;
|
|
783
|
+
}
|
|
784
|
+
return routes;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Lift Cursor's max-mode markers from live member rows onto an already-collapsed
|
|
789
|
+
* snapshot. Bundled catalog and cache rows froze the flag from `memberSpecs[0]`
|
|
790
|
+
* — the `-none`/`-low` tier — so the committed `gpt-5.6-*` / `cursor-grok-*`
|
|
791
|
+
* rows carry `cursorMaxMode: false`. The existing-collapsed pass-through keeps
|
|
792
|
+
* the snapshot verbatim, so a live `GetUsableModels` roster that marks the
|
|
793
|
+
* `-xhigh`/`-max` tiers would be discarded and max-tier requests would keep
|
|
794
|
+
* sending `max_mode: false` on a max-mode-only wire id. The row-level flag
|
|
795
|
+
* mirrors the fresh-collapse aggregation: only the positive case is lifted, so
|
|
796
|
+
* a roster that marks nothing leaves the snapshot alone. The per-wire-id
|
|
797
|
+
* markers are merged on top of the snapshot's own so live rows win per route.
|
|
798
|
+
* Returns `spec` by reference when unchanged.
|
|
799
|
+
*/
|
|
800
|
+
function reconcileCursorMaxMode<TSpec extends VariantSpecLike>(spec: TSpec, memberSpecs: readonly TSpec[]): TSpec {
|
|
801
|
+
const routes = cursorMaxModeRoutesOf(spec.provider, memberSpecs);
|
|
802
|
+
const lifts = spec.cursorMaxMode !== true && memberSpecs.some(member => member.cursorMaxMode === true);
|
|
803
|
+
if (routes === undefined && !lifts) return spec;
|
|
804
|
+
return {
|
|
805
|
+
...spec,
|
|
806
|
+
...(lifts ? { cursorMaxMode: true } : {}),
|
|
807
|
+
...(routes === undefined ? {} : { cursorMaxModeRoutes: { ...spec.cursorMaxModeRoutes, ...routes } }),
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
|
|
737
811
|
/**
|
|
738
812
|
* Collapse every family in `table` found in `specs`. Non-member specs pass
|
|
739
813
|
* through verbatim (by reference), order preserved; the collapsed spec
|
|
@@ -776,7 +850,9 @@ function collapseWithTable<TSpec extends VariantSpecLike>(
|
|
|
776
850
|
// Recycled extraAliases rows are healed in a later pass.
|
|
777
851
|
const refreshed =
|
|
778
852
|
existing !== undefined && existingCollapsed
|
|
779
|
-
?
|
|
853
|
+
? reconcileCursorMaxModeFromRouting(
|
|
854
|
+
reconcileDefaultMember(refreshCollapsedThinking(reconciled ?? existing, family, retired), family),
|
|
855
|
+
)
|
|
780
856
|
: reconciled;
|
|
781
857
|
if (refreshed !== undefined && refreshed !== existing) {
|
|
782
858
|
familyIdBySpecId.set(family.id, family.id);
|
|
@@ -788,20 +864,26 @@ function collapseWithTable<TSpec extends VariantSpecLike>(
|
|
|
788
864
|
for (const id of rawPresent) familyIdBySpecId.set(id, family.id);
|
|
789
865
|
if (existing) familyIdBySpecId.set(family.id, family.id);
|
|
790
866
|
|
|
867
|
+
const memberSpecs: TSpec[] = [];
|
|
868
|
+
for (const id of rawPresent) {
|
|
869
|
+
const member = byId.get(id);
|
|
870
|
+
if (member !== undefined) memberSpecs.push(member);
|
|
871
|
+
}
|
|
872
|
+
|
|
791
873
|
if (existingCollapsed && reconciled !== undefined) {
|
|
792
874
|
// Mixed input: the collapsed entry wins; stale raw members are deduped
|
|
793
875
|
// away. Retired targets are re-pointed first, then the default wire id
|
|
794
876
|
// prefers the family's declared member when live and otherwise falls
|
|
795
|
-
// back to the first member the account actually advertised.
|
|
796
|
-
|
|
877
|
+
// back to the first member the account actually advertised. The live
|
|
878
|
+
// members still own `cursorMaxMode`: the snapshot froze it from the
|
|
879
|
+
// lowest tier.
|
|
880
|
+
replacement.set(
|
|
881
|
+
family.id,
|
|
882
|
+
reconcileCursorMaxMode(reconcileDefaultMember(reconciled, family, new Set(rawPresent)), memberSpecs),
|
|
883
|
+
);
|
|
797
884
|
continue;
|
|
798
885
|
}
|
|
799
886
|
|
|
800
|
-
const memberSpecs: TSpec[] = [];
|
|
801
|
-
for (const id of rawPresent) {
|
|
802
|
-
const member = byId.get(id);
|
|
803
|
-
if (member !== undefined) memberSpecs.push(member);
|
|
804
|
-
}
|
|
805
887
|
const firstMember = memberSpecs[0];
|
|
806
888
|
if (firstMember === undefined) continue;
|
|
807
889
|
const presentSet = new Set(rawPresent);
|
|
@@ -836,6 +918,14 @@ function collapseWithTable<TSpec extends VariantSpecLike>(
|
|
|
836
918
|
if (memberSpecs.some(spec => spec.input.includes("text"))) input.push("text");
|
|
837
919
|
if (memberSpecs.some(spec => spec.input.includes("image"))) input.push("image");
|
|
838
920
|
|
|
921
|
+
// `cursorMaxMode` gates the `max_mode` request flag. The collapsed row
|
|
922
|
+
// otherwise inherits `memberSpecs[0]`, so a family whose max-mode member
|
|
923
|
+
// is not the first one would advertise `false` and send `max_mode: false`
|
|
924
|
+
// on a max-mode wire id. Only the positive case is aggregated — an
|
|
925
|
+
// unmarked family keeps whatever the first member carried. The OR loses
|
|
926
|
+
// which tier needed it, so the members' own markers are kept per wire id.
|
|
927
|
+
const cursorMaxMode = memberSpecs.some(spec => spec.cursorMaxMode === true) ? true : undefined;
|
|
928
|
+
const cursorMaxModeRoutes = cursorMaxModeRoutesOf(firstMember.provider, memberSpecs);
|
|
839
929
|
const collapsed: TSpec = {
|
|
840
930
|
...firstMember,
|
|
841
931
|
id: family.id,
|
|
@@ -844,6 +934,8 @@ function collapseWithTable<TSpec extends VariantSpecLike>(
|
|
|
844
934
|
input,
|
|
845
935
|
contextWindow: maxOrNull(memberSpecs.map(spec => spec.contextWindow)),
|
|
846
936
|
maxTokens: maxOrNull(memberSpecs.map(spec => spec.maxTokens)),
|
|
937
|
+
...(cursorMaxMode === undefined ? {} : { cursorMaxMode }),
|
|
938
|
+
...(cursorMaxModeRoutes === undefined ? {} : { cursorMaxModeRoutes }),
|
|
847
939
|
};
|
|
848
940
|
// The default wire id is the family's declared `defaultMember` when live,
|
|
849
941
|
// else the highest-priority live member. Omitted when it equals the
|
|
@@ -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
|
@@ -34,7 +34,7 @@ import type {
|
|
|
34
34
|
import { isAnthropicSigningProxyUrl, isAzureAnthropicRoute, isOfficialAnthropicApiUrl } from "./anthropic";
|
|
35
35
|
import { applyCompatOverrides } from "./apply";
|
|
36
36
|
import { API_COMPAT_RECORDS, AXES, type CompatRecordName } from "./axes";
|
|
37
|
-
import { resolveCascade } from "./cascade";
|
|
37
|
+
import { hasModelScopedEffortsRule, resolveCascade } from "./cascade";
|
|
38
38
|
import { compareRevision, parseRevision, type Revision } from "./revision";
|
|
39
39
|
import { classifyModel, stripThinkingVariantSuffix } from "./taxonomy";
|
|
40
40
|
import type { ModelIdentity, ResolvedAxes, ResolveTarget } from "./types";
|
|
@@ -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;
|
|
@@ -1245,3 +1271,16 @@ export function resolveModelPolicy(spec: ModelSpec<Api>): ResolvedModelPolicy<Ap
|
|
|
1245
1271
|
catalog: axes.catalog,
|
|
1246
1272
|
};
|
|
1247
1273
|
}
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* Whether reviewed rules know THIS model's effort ladder, as opposed to it
|
|
1277
|
+
* inheriting a provider-wide default or {@link resolveThinkingPolicy} falling
|
|
1278
|
+
* through to the neutral wire ladder.
|
|
1279
|
+
*
|
|
1280
|
+
* Discovery uses this to tell "omp knows this model's tiers" apart from "omp
|
|
1281
|
+
* is guessing them", so catalog-published tiers can correct the guess without
|
|
1282
|
+
* ever overriding reviewed knowledge.
|
|
1283
|
+
*/
|
|
1284
|
+
export function hasModelScopedEffortLadder<TApi extends Api>(spec: ModelSpec<TApi>): boolean {
|
|
1285
|
+
return hasModelScopedEffortsRule(buildResolveTarget(spec, resolveIdentity(spec)));
|
|
1286
|
+
}
|
|
@@ -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"
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// Model-lineage compat for "kimi"; regenerated from the frozen census using class taxonomy selectors.
|
|
2
2
|
|
|
3
3
|
class "kimi" {
|
|
4
|
+
// Kimi chat templates can revise bytes they have already streamed: a leaned-on
|
|
5
|
+
// thinking opener reclassifies visible text as reasoning, and native
|
|
6
|
+
// section/tool-call tokens are carved out of the visible channel mid-stream.
|
|
7
|
+
// Declared revision-possible so finished prose stays in the live viewport
|
|
8
|
+
// instead of retiring into native scrollback ahead of the turn.
|
|
9
|
+
stream-revision "possible"
|
|
4
10
|
// Replaces the K2.6 reasoning stream-idle baseline on OpenAI-compatible routes.
|
|
5
11
|
on "baseten" "cline-pass" "cloudflare-ai-gateway" "coreweave" "deepinfra" "firepass" "fireworks" \
|
|
6
12
|
"huggingface" "moonshot" "novita" "nvidia" "opencode-go" "opencode-zen" "openrouter" \
|
|
@@ -114,10 +114,17 @@ provider "alibaba-token-plan" {
|
|
|
114
114
|
models "deepseek-v4-pro" {
|
|
115
115
|
thinking-format "qwen"
|
|
116
116
|
}
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
thinking-efforts "low" "
|
|
117
|
+
// Max Preview rides only Alibaba's binary enable_thinking toggle: it stays on
|
|
118
|
+
// the Qwen dialect and must not emit an unsupported reasoning_effort field.
|
|
119
|
+
models "qwen3.8-max-preview" {
|
|
120
|
+
thinking-efforts "low" "high" "xhigh"
|
|
121
|
+
thinking-requires-effort #true
|
|
122
|
+
}
|
|
123
|
+
// Max and Flash steer thinking depth through the OpenAI reasoning_effort
|
|
124
|
+
// control and replay reasoning_content across turns (preserve_thinking
|
|
125
|
+
// defaults on for both).
|
|
126
|
+
models "qwen3.8-max" "qwen3.8-flash" {
|
|
127
|
+
replay-reasoning-content #true
|
|
121
128
|
when-thinking {
|
|
122
129
|
extra-body {
|
|
123
130
|
enable_thinking #true
|
|
@@ -125,9 +132,10 @@ provider "alibaba-token-plan" {
|
|
|
125
132
|
thinking-format "openai"
|
|
126
133
|
}
|
|
127
134
|
}
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
thinking-
|
|
135
|
+
// Max exposes a distinct low/medium/xhigh ladder; Flash retains its
|
|
136
|
+
// provider-authored minimal/low/medium/high ladder.
|
|
137
|
+
models "qwen3.8-max" {
|
|
138
|
+
thinking-default-level "xhigh"
|
|
139
|
+
thinking-efforts "low" "medium" "xhigh"
|
|
132
140
|
}
|
|
133
141
|
}
|
|
@@ -3,7 +3,15 @@
|
|
|
3
3
|
provider "amazon-bedrock" {
|
|
4
4
|
default-model "us.anthropic.claude-opus-4-8"
|
|
5
5
|
|
|
6
|
+
// The Converse assembler positions content blocks by wire index
|
|
7
|
+
// (`amazon-bedrock.ts`), so a block can land above text already rendered.
|
|
8
|
+
stream-revision "possible"
|
|
6
9
|
class "anthropic" {
|
|
10
|
+
family "fable" {
|
|
11
|
+
revision ">=5.1 <5.2" {
|
|
12
|
+
thinking-efforts "low" "medium" "high" "xhigh" "max"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
7
15
|
family "opus" {
|
|
8
16
|
revision ">=4.6 <4.7" {
|
|
9
17
|
thinking-mode "anthropic-adaptive"
|
|
@@ -42,4 +50,26 @@ provider "amazon-bedrock" {
|
|
|
42
50
|
models "moonshot.kimi-k2-thinking" {
|
|
43
51
|
thinking-requires-effort #true
|
|
44
52
|
}
|
|
53
|
+
// The Qwen rows arrive with maxTokens copied from the upstream context
|
|
54
|
+
// window (262000 on a 262144 window), so a request that does not set an
|
|
55
|
+
// output budget of its own asks Bedrock for more than the model accepts
|
|
56
|
+
// and gets a 400 back ("you requested 262000 output tokens"). AWS
|
|
57
|
+
// publishes the real caps on the model cards — 8K for Qwen3-Next 80B A3B
|
|
58
|
+
// and Qwen3 VL 235B A22B, 16K for Qwen3 Coder Next — and the context
|
|
59
|
+
// windows already match, so only the output side needs patching (#12089).
|
|
60
|
+
models "qwen.qwen3-next-80b-a3b" {
|
|
61
|
+
limits-patch {
|
|
62
|
+
max-tokens 8000
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
models "qwen.qwen3-vl-235b-a22b" {
|
|
66
|
+
limits-patch {
|
|
67
|
+
max-tokens 8000
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
models "qwen.qwen3-coder-next" {
|
|
71
|
+
limits-patch {
|
|
72
|
+
max-tokens 16000
|
|
73
|
+
}
|
|
74
|
+
}
|
|
45
75
|
}
|
|
@@ -19,4 +19,10 @@ provider "azure" {
|
|
|
19
19
|
models "gpt-5.1-codex-mini" {
|
|
20
20
|
thinking-efforts "medium" "high"
|
|
21
21
|
}
|
|
22
|
+
// Azure Chat Completions rejects Astra reasoning whenever function tools are
|
|
23
|
+
// present; the documented escape hatch is an explicit `none` effort.
|
|
24
|
+
models "gpt-6-astra*" {
|
|
25
|
+
disable-reasoning-with-tools #true
|
|
26
|
+
reasoning-disable-mode "none-effort"
|
|
27
|
+
}
|
|
22
28
|
}
|
|
@@ -16,6 +16,16 @@ provider "cerebras" {
|
|
|
16
16
|
input-modalities "text" "image"
|
|
17
17
|
}
|
|
18
18
|
thinking-mode "effort"
|
|
19
|
+
// Cerebras accepts OpenAI `reasoning_effort`, not Qwen-native thinking
|
|
20
|
+
// fields such as `enable_thinking`.
|
|
21
|
+
class "qwen" {
|
|
22
|
+
thinking-format "openai"
|
|
23
|
+
}
|
|
24
|
+
// Qwen 3.8 accepts low/medium/high; `none` is its wire-only off value.
|
|
25
|
+
models "qwen-3.8-27b" {
|
|
26
|
+
thinking-efforts "low" "medium" "high"
|
|
27
|
+
reasoning-disable-mode "none-effort"
|
|
28
|
+
}
|
|
19
29
|
// residue: taxonomy ranks and exact globs do not isolate these models.
|
|
20
30
|
models "zai-glm-4.7" {
|
|
21
31
|
thinking-efforts "minimal" "low" "medium" "high" "xhigh"
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
// Command Code Provider API deployment contract. Model capabilities and effort
|
|
2
2
|
// ladders follow command-code@1.44.0: the thinking-efforts below reproduce the
|
|
3
3
|
// bundle's 36-id effort registry 1:1 (verified against its effort map), plus
|
|
4
|
-
// the 5 Muse Spark lineage ids whose ladder OMP's classes/meta.kdl carries
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// the 5 Muse Spark lineage ids whose ladder OMP's classes/meta.kdl carries and
|
|
5
|
+
// `deepseek/deepseek-v4.1-flash`, which postdates that bundle (see its rule).
|
|
6
|
+
// Every id the 1.44.0 census serves without an effort dial keeps a missing
|
|
7
|
+
// ladder here as a deliberate absence, not a gap (`modelSupportsReasoning-
|
|
8
|
+
// Effort` is false for those ids in that bundle). Later bundles (1.53.0) hand
|
|
9
|
+
// dials to a few further ids — MiniMaxAI/MiniMax-M3 and meta/muse-spark-1.3's
|
|
10
|
+
// `max` — which stay out of this change.
|
|
8
11
|
provider "commandcode" {
|
|
9
12
|
default-model "claude-sonnet-4-6"
|
|
10
13
|
env "COMMAND_CODE_API_KEY" "COMMANDCODE_API_KEY"
|
|
@@ -54,6 +57,19 @@ provider "commandcode" {
|
|
|
54
57
|
supports-reasoning-effort #true
|
|
55
58
|
thinking-efforts "low" "high" "max"
|
|
56
59
|
}
|
|
60
|
+
// `deepseek/deepseek-v4.1-flash` postdates command-code@1.44.0, so it is
|
|
61
|
+
// absent from that bundle's effort registry; command-code@1.53.0 lists it
|
|
62
|
+
// in the same low/high/max group as Kimi K3 and GLM-5.3, and the live docs
|
|
63
|
+
// model table carries the same scale plus image input.
|
|
64
|
+
models "deepseek/deepseek-v4.1-flash" {
|
|
65
|
+
supports-reasoning-effort #true
|
|
66
|
+
thinking-efforts "low" "high" "max"
|
|
67
|
+
// The class's `token="vision"` exemption misses this id (its name
|
|
68
|
+
// carries no `vision` token) even though the deployment serves it with
|
|
69
|
+
// image input, so the opt-out is explicit here.
|
|
70
|
+
strip-image-input #false
|
|
71
|
+
input-modalities "text" "image"
|
|
72
|
+
}
|
|
57
73
|
models "google/gemini-3.1-flash-lite" "google/gemini-3.5-flash" \
|
|
58
74
|
"google/gemini-3.5-flash-lite" "google/gemini-3.6-flash" "google/gemini-3.7-flash" \
|
|
59
75
|
"google/gemini-3.8-flash" "gpt-5.4-mini" "xai/grok-4.5" {
|
|
@@ -11,6 +11,15 @@ provider "cursor" {
|
|
|
11
11
|
class "anthropic" {
|
|
12
12
|
family "fable" {
|
|
13
13
|
requires-cursor-tool-schema-projection #true
|
|
14
|
+
// Cursor advertised Context window 300k (Max 1M recovered elsewhere).
|
|
15
|
+
context-window-floor 300000
|
|
16
|
+
}
|
|
17
|
+
family "opus" {
|
|
18
|
+
revision ">=5 <6" {
|
|
19
|
+
// Cursor advertised Claude Opus 5 Context window 300k; Max 1M
|
|
20
|
+
// stays recovered on labeled/maxMode rows via Math.max.
|
|
21
|
+
context-window-floor 300000
|
|
22
|
+
}
|
|
14
23
|
}
|
|
15
24
|
}
|
|
16
25
|
// GetUsableModels advertises no input modalities or context windows.
|
|
@@ -21,11 +30,34 @@ provider "cursor" {
|
|
|
21
30
|
input-modalities "text" "image"
|
|
22
31
|
context-window-floor 1000000
|
|
23
32
|
}
|
|
33
|
+
family "k2.7-code" {
|
|
34
|
+
// Cursor advertised Kimi K2.7 Code Context window 262k.
|
|
35
|
+
context-window-floor 262000
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
class "xai" {
|
|
39
|
+
family "grok" {
|
|
40
|
+
revision ">=4.5 <4.7" {
|
|
41
|
+
// Cursor advertised Grok 4.5/4.6 Context window 256k (product
|
|
42
|
+
// cap; not xAI native 500k). Max context is unmarked.
|
|
43
|
+
context-window-floor 256000
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
class "openai" {
|
|
48
|
+
revision ">=5.6 <5.7" {
|
|
49
|
+
// Cursor advertised GPT-5.6 Sol/Terra/Luna Context window 272k;
|
|
50
|
+
// raises unlabeled -fast variants from the 200k default.
|
|
51
|
+
context-window-floor 272000
|
|
52
|
+
}
|
|
24
53
|
}
|
|
25
54
|
models "k3" "*/k3" {
|
|
26
55
|
input-modalities "text" "image"
|
|
27
56
|
context-window-floor 1000000
|
|
28
57
|
}
|
|
58
|
+
models "default" {
|
|
59
|
+
context-window-floor 256000
|
|
60
|
+
}
|
|
29
61
|
// Cursor-only families verified to accept selectedImages even though the
|
|
30
62
|
// roster carries no modality metadata.
|
|
31
63
|
models "cursor-grok-4" "cursor-grok-4.*" "cursor-grok-4-*" "cursor-grok-4:*" "cursor-grok-4_*" {
|
|
@@ -17,8 +17,8 @@ provider "deepseek" {
|
|
|
17
17
|
// First-party peak USD / 1M tokens; all other UTC hours receive 50% off.
|
|
18
18
|
// Source: https://api-docs.deepseek.com/quick_start/pricing (2026-09-10).
|
|
19
19
|
// residue: the pricing page's Flash-priced names, not a taxonomy family: the
|
|
20
|
-
//
|
|
21
|
-
//
|
|
20
|
+
// retired `v4-flash`/`-vision-exp` ids are still accepted and billed at the
|
|
21
|
+
// Flash card.
|
|
22
22
|
models "deepseek-flash" "deepseek-v4-flash" "deepseek-v4-flash-vision-exp" {
|
|
23
23
|
clamp-output-to-model-max #true
|
|
24
24
|
time-based-cost {
|
|
@@ -43,9 +43,9 @@ provider "deepseek" {
|
|
|
43
43
|
cache-write 0
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
// residue:
|
|
47
|
-
//
|
|
48
|
-
//
|
|
46
|
+
// residue: the pricing page documents this SKU (DeepSeek-V4.1-Flash) as 1M
|
|
47
|
+
// context / 384K output; pinned as a reviewed correction so the documented
|
|
48
|
+
// window survives an upstream metadata gap.
|
|
49
49
|
models "deepseek-flash" {
|
|
50
50
|
limits-patch {
|
|
51
51
|
context-window 1000000
|