@oh-my-pi/pi-catalog 18.11.2 → 18.12.0

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/types/compat/behavior.d.ts +5 -0
  3. package/dist/types/compat/catalog-endpoint.d.ts +8 -0
  4. package/dist/types/compat/judgment-images.d.ts +25 -0
  5. package/dist/types/compat/request-compression.d.ts +21 -0
  6. package/dist/types/compat/request-size.d.ts +2 -1
  7. package/dist/types/compat/types.d.ts +2 -0
  8. package/dist/types/model-cache.d.ts +7 -0
  9. package/dist/types/provider-models/openai-compat.d.ts +0 -46
  10. package/dist/types/types.d.ts +14 -1
  11. package/package.json +4 -4
  12. package/src/build.ts +13 -0
  13. package/src/compat/axes.ts +51 -1
  14. package/src/compat/behavior.ts +9 -1
  15. package/src/compat/catalog-endpoint.ts +43 -0
  16. package/src/compat/judgment-images.ts +83 -0
  17. package/src/compat/request-compression.ts +41 -0
  18. package/src/compat/request-size.ts +3 -1
  19. package/src/compat/resolve.ts +34 -49
  20. package/src/compat/rules/README.md +17 -1
  21. package/src/compat/rules/auth/google-antigravity.kdl +1 -1
  22. package/src/compat/rules/classes/anthropic.kdl +2 -3
  23. package/src/compat/rules/classes/deepseek.kdl +4 -0
  24. package/src/compat/rules/classes/minimax.kdl +10 -0
  25. package/src/compat/rules/classes/openai.kdl +8 -0
  26. package/src/compat/rules/providers/anthropic.kdl +4 -0
  27. package/src/compat/rules/providers/azure.kdl +2 -0
  28. package/src/compat/rules/providers/cerebras.kdl +3 -0
  29. package/src/compat/rules/providers/cline-pass.kdl +16 -0
  30. package/src/compat/rules/providers/commandcode.kdl +10 -0
  31. package/src/compat/rules/providers/deepinfra.kdl +3 -1
  32. package/src/compat/rules/providers/devin.kdl +7 -1
  33. package/src/compat/rules/providers/factory-droid.kdl +6 -0
  34. package/src/compat/rules/providers/firepass.kdl +15 -0
  35. package/src/compat/rules/providers/fireworks.kdl +26 -0
  36. package/src/compat/rules/providers/github-copilot.kdl +24 -0
  37. package/src/compat/rules/providers/google-antigravity.kdl +11 -0
  38. package/src/compat/rules/providers/moonshot.kdl +2 -0
  39. package/src/compat/rules/providers/ollama-cloud.kdl +3 -0
  40. package/src/compat/rules/providers/openai-codex.kdl +9 -0
  41. package/src/compat/rules/providers/opencode-go.kdl +4 -0
  42. package/src/compat/rules/providers/opencode-zen.kdl +4 -0
  43. package/src/compat/rules/providers/openrouter.kdl +15 -0
  44. package/src/compat/rules/providers/tool-call-names.kdl +37 -0
  45. package/src/compat/rules/providers/typesafe.kdl +8 -0
  46. package/src/compat/rules/providers/umans.kdl +6 -0
  47. package/src/compat/rules/providers/venice.kdl +7 -0
  48. package/src/compat/rules/providers/xai-oauth.kdl +4 -0
  49. package/src/compat/rules/providers/zai.kdl +5 -0
  50. package/src/compat/rules/runtime/behavior.kdl +115 -4
  51. package/src/compat/rules.json +1 -1
  52. package/src/compat/types.ts +2 -0
  53. package/src/model-cache.ts +8 -0
  54. package/src/model-manager.ts +29 -20
  55. package/src/models.json +1 -1
  56. package/src/provider-models/openai-compat.ts +8 -90
  57. package/src/types.ts +14 -0
@@ -330,7 +330,6 @@ function isOfficialOpenAIEndpoint(provider: string, baseUrl: string): boolean {
330
330
 
331
331
  interface OpenAIDetection {
332
332
  facts: IdentityFacts;
333
- isClinePass: boolean;
334
333
  isZai: boolean;
335
334
  isZhipu: boolean;
336
335
  isMoonshotNative: boolean;
@@ -368,7 +367,6 @@ function detectOpenAI(
368
367
  (LOCAL_OPENAI_COMPAT_PROVIDERS[backendProvider] === true || hasLocalLoopbackBaseUrl(baseUrl));
369
368
  return {
370
369
  facts,
371
- isClinePass: provider === "cline-pass",
372
370
  isZai,
373
371
  isZhipu,
374
372
  isMoonshotNative,
@@ -474,7 +472,9 @@ function detectOpenAICompat(
474
472
  provider === "github-copilot" ||
475
473
  provider === "zenmux");
476
474
 
477
- // Endpoint-only watchdogs remain here; provider and lineage policy lives in KDL.
475
+ // URL-keyed watchdogs: they also cover custom provider ids pointed at these
476
+ // hosts, which provider-keyed rules (`stream-idle-timeout-ms` in
477
+ // classes/{glm,kimi,mimo}.kdl) cannot match. KDL rules override them.
478
478
  const streamIdleTimeoutMs =
479
479
  facts.is("glm") &&
480
480
  facts.revGte("5") &&
@@ -521,21 +521,17 @@ function detectOpenAICompat(
521
521
  supportsMultipleSystemMessages: supportsMultipleSystemMessagesDefault,
522
522
  supportsReasoningEffort: !isGrok && !d.isXiaomiMimo && (!(d.isZai || d.isZhipu) || supportsZaiReasoningEffort),
523
523
  trustExplicitThinkingOnly: undefined,
524
- // API-conditional: this completions-only Copilot exclusion cannot be a
525
- // provider rule without changing Copilot Responses rows.
526
- supportsReasoningParams: provider !== "github-copilot",
524
+ supportsReasoningParams: true,
527
525
  supportsSamplingParams: true,
528
526
  supportsPenaltyAndStopParams: !(isGrok && reasoningCapable),
529
527
  reasoningEffortMap: {},
530
528
  supportsUsageInStreaming: !isCerebrasHost,
531
529
  alwaysSendMaxTokens: facts.is("kimi"),
532
- disableReasoningOnForcedToolChoice:
533
- !d.isClinePass && ((facts.is("kimi") && !isMoonshotKimiK3) || isAnthropicModel),
534
- disableReasoningOnToolChoice: !d.isClinePass && isDeepseekFamily && reasoningCapable && !d.isOpenRouter,
530
+ disableReasoningOnForcedToolChoice: (facts.is("kimi") && !isMoonshotKimiK3) || isAnthropicModel,
531
+ disableReasoningOnToolChoice: isDeepseekFamily && reasoningCapable && !d.isOpenRouter,
535
532
  disableReasoningWithTools: false,
536
- supportsToolChoice: d.isClinePass || !d.isDirectDeepseekReasoning,
537
- supportsForcedToolChoice:
538
- !d.requiresEnabledThinking && !(d.isOpenCodeHost && d.isDeepseekReasoning) && !(d.isClinePass && isQwen),
533
+ supportsToolChoice: !d.isDirectDeepseekReasoning,
534
+ supportsForcedToolChoice: !d.requiresEnabledThinking && !(d.isOpenCodeHost && d.isDeepseekReasoning),
539
535
  supportsNamedToolChoice: true,
540
536
  maxTokensField: useMaxTokens ? "max_tokens" : "max_completion_tokens",
541
537
  requiresToolResultName: isMistral,
@@ -544,16 +540,12 @@ function detectOpenAICompat(
544
540
  requiresMistralToolIds: isMistral,
545
541
  thinkingFormat,
546
542
  kimiApiFormat: undefined,
547
- reasoningDisableMode: d.isClinePass
548
- ? "cline-enabled-false"
549
- : d.isVenice
550
- ? "venice-disable-thinking"
551
- : resolveReasoningDisableMode(thinkingFormat),
543
+ reasoningDisableMode: d.isVenice ? "venice-disable-thinking" : resolveReasoningDisableMode(thinkingFormat),
552
544
  omitReasoningEffort: false,
553
545
  includeEncryptedReasoning: true,
554
546
  filterReasoningHistory: d.isOpenRouter && isAnthropicModel,
555
547
  thinkingKeep: usesMoonshotKimiPreservedThinking ? "all" : undefined,
556
- reasoningContentField: d.isClinePass ? "reasoning" : "reasoning_content",
548
+ reasoningContentField: "reasoning_content",
557
549
  mistralReasoningContentParts: undefined,
558
550
  requiresReasoningContentForToolCalls:
559
551
  (facts.is("kimi") && !d.isOpenCodeProvider) ||
@@ -571,10 +563,7 @@ function detectOpenAICompat(
571
563
  // Template effort support is a reviewed backend × model contract in KDL.
572
564
  qwenTemplateReasoningEffort: false,
573
565
  requiresAssistantContentForToolCalls: facts.is("kimi") || d.isDirectDeepseekReasoning,
574
- cacheControlFormat:
575
- (d.isClinePass && (isQwen || isAnthropicModel)) || (d.isOpenRouter && isAnthropicModel)
576
- ? "anthropic"
577
- : undefined,
566
+ cacheControlFormat: d.isOpenRouter && isAnthropicModel ? "anthropic" : undefined,
578
567
  supportsPromptCacheBreakpoints,
579
568
  promptCacheBreakpointTtl: supportsPromptCacheBreakpoints ? "30m" : undefined,
580
569
  openRouterRouting: undefined,
@@ -599,7 +588,11 @@ function detectOpenAICompat(
599
588
  : undefined,
600
589
  streamFirstEventTimeoutMs: d.isLocalServingBackend ? 0 : undefined,
601
590
  streamIdleTimeoutMs,
602
- stripDeepseekSpecialTokens: facts.is("deepseek") && (provider === "nvidia" || provider === "deepseek"),
591
+ stripDeepseekSpecialTokens: false,
592
+ // Present as a key so models.yml `compat` can set it; unset unless a rule assigns it.
593
+ stripThinkingHistory: undefined,
594
+ // URL-derived like the Responses field; unset off the official OpenAI API.
595
+ officialEndpoint: isOfficialOpenAIEndpoint(spec.provider, baseUrl) || undefined,
603
596
  streamMarkupHealingPattern: detectStreamMarkupHealing(spec.provider, facts, baseUrl),
604
597
  reasoningDeltasMayBeCumulative: false,
605
598
  emptyLengthFinishIsContextError: false,
@@ -657,15 +650,13 @@ function fixupOpenAICompat(
657
650
  compat.extraBody = Object.keys(extraBody).length > 0 ? extraBody : undefined;
658
651
  }
659
652
  if (spec.compat?.reasoningDisableMode === undefined && !("reasoningDisableMode" in axes.wire)) {
660
- compat.reasoningDisableMode = d.isClinePass
661
- ? "cline-enabled-false"
662
- : d.requiresEnabledThinking
663
- ? "omit"
664
- : d.isDirectDeepseekReasoning
665
- ? "zai-thinking-disabled"
666
- : d.isVenice
667
- ? "venice-disable-thinking"
668
- : resolveReasoningDisableMode(compat.thinkingFormat);
653
+ compat.reasoningDisableMode = d.requiresEnabledThinking
654
+ ? "omit"
655
+ : d.isDirectDeepseekReasoning
656
+ ? "zai-thinking-disabled"
657
+ : d.isVenice
658
+ ? "venice-disable-thinking"
659
+ : resolveReasoningDisableMode(compat.thinkingFormat);
669
660
  }
670
661
  if (
671
662
  spec.compat?.omitReasoningEffort === undefined &&
@@ -754,7 +745,8 @@ function resolveOpenAIResponsesPolicy(
754
745
  supportsLongPromptCacheRetention: isOpenAIUrl,
755
746
  supportsPromptCacheBreakpoints,
756
747
  promptCacheBreakpointTtl: supportsPromptCacheBreakpoints ? "30m" : undefined,
757
- strictResponsesPairing: isAzure || provider === "github-copilot",
748
+ // Provider ids are rule-owned; the URL arm keeps custom Azure endpoints paired.
749
+ strictResponsesPairing: hostMatchesUrl(baseUrl, "azureOpenAI"),
758
750
  // Azure's provider id alone only implies support while its endpoint is
759
751
  // resolved at runtime; an explicit non-Azure baseUrl is a proxy, like Codex.
760
752
  supportsImageDetailOriginal:
@@ -783,7 +775,7 @@ function resolveOpenAIResponsesPolicy(
783
775
  disableReasoningOnToolChoice: isDeepseekFamily && reasoningCapable && !isOpenRouter,
784
776
  disableReasoningWithTools: false,
785
777
  supportsToolChoice: true,
786
- supportsForcedToolChoice: provider !== "opencode-go" && provider !== "opencode-zen",
778
+ supportsForcedToolChoice: true,
787
779
  supportsNamedToolChoice: true,
788
780
  reasoningContentField: "reasoning_content",
789
781
  requiresReasoningContentForToolCalls:
@@ -822,7 +814,7 @@ function resolveOpenAIResponsesPolicy(
822
814
  rejectRootObjectUnion: false,
823
815
  retryWithoutStrictOnGrammarError: false,
824
816
  cacheControlFormat: isOpenRouter && isAnthropicModel ? "anthropic" : undefined,
825
- stripDeepseekSpecialTokens: facts.is("deepseek") && (provider === "nvidia" || provider === "deepseek"),
817
+ stripDeepseekSpecialTokens: false,
826
818
  streamMarkupHealingPattern: detectStreamMarkupHealing(provider, facts, baseUrl),
827
819
  reasoningDeltasMayBeCumulative: false,
828
820
  emptyLengthFinishIsContextError: false,
@@ -984,7 +976,8 @@ function resolveOllamaPolicy(spec: ModelSpec<"ollama-chat">, axes: ResolvedAxes)
984
976
 
985
977
  function resolveDevinPolicy(spec: ModelSpec<"devin-agent">, axes: ResolvedAxes): ResolvedDevinCompat {
986
978
  const compat: ResolvedDevinCompat = {
987
- trustExplicitThinkingOnly: true,
979
+ // Rule-owned: `on-api "devin-agent"` in providers/devin.kdl opts in.
980
+ trustExplicitThinkingOnly: false,
988
981
  modelRouter: false,
989
982
  supportsParallelToolCalls: false,
990
983
  };
@@ -1037,7 +1030,11 @@ function readCompatEffortMap(compat: CompatOf<Api>): Partial<Record<Effort, stri
1037
1030
  const map = compat.reasoningEffortMap;
1038
1031
  return map && Object.keys(map).length > 0 ? map : undefined;
1039
1032
  }
1040
- /** Host-quirk effort remaps detected outside the rules (URL/API compounds). */
1033
+ /**
1034
+ * URL-keyed Fireworks effort remap: covers custom provider ids pointed at the
1035
+ * Fireworks host, which the `fireworks`/`firepass` `thinking-effort-map`
1036
+ * rules cannot match. Rule and configured maps override it.
1037
+ */
1041
1038
  const FIREWORKS_THINKING_EFFORT_MAP: Readonly<Partial<Record<Effort, string>>> = {
1042
1039
  [Effort.Minimal]: "none",
1043
1040
  };
@@ -1051,10 +1048,6 @@ function defaultThinkingMode<TApi extends Api>(spec: ModelSpec<TApi>, facts: Ide
1051
1048
  // Gemini 3+ `google-level` is a `thinking-mode` rule in classes/gemini.kdl.
1052
1049
  return "budget";
1053
1050
  case "anthropic-messages":
1054
- if (facts.is("minimax") && facts.family("m2", "m3")) return "anthropic-adaptive";
1055
- if (facts.is("glm") && facts.revGte("5.2") && (spec.provider === "umans" || spec.provider === "zai")) {
1056
- return "anthropic-budget-effort";
1057
- }
1058
1051
  if (facts.is("anthropic")) {
1059
1052
  // Haiku stays on budget thinking until 5.5, its first adaptive generation.
1060
1053
  if (facts.revGte(facts.family("haiku") ? "5.5" : "4.6")) return "anthropic-adaptive";
@@ -1201,14 +1194,6 @@ function resolveThinkingPolicy<TApi extends Api>(
1201
1194
  ) {
1202
1195
  return undefined;
1203
1196
  }
1204
- if (
1205
- spec.provider === "cline-pass" &&
1206
- compat !== undefined &&
1207
- "supportsReasoningEffort" in compat &&
1208
- compat.supportsReasoningEffort === false
1209
- ) {
1210
- return undefined;
1211
- }
1212
1197
  if (omitsWireReasoningEffort(spec.api, compat)) return undefined;
1213
1198
  if (explicitThinking !== undefined) {
1214
1199
  return fillExplicitThinking(spec, facts, compat, explicitThinking, rule);
@@ -221,19 +221,29 @@ One object axis carries a computed form: `long-context-cost` accepts either the
221
221
 
222
222
  `context-window-authoritative #true` preserves a host's supplied context window through runtime model selection instead of applying inferred expansion or reference-price-tier caps. Explicit user context overrides still apply afterward. It applies to rows materialized through `buildModel` (discovery and regenerated bundles). See [`providers/factory-droid.kdl`](providers/factory-droid.kdl).
223
223
 
224
+ `long-context-window-uncapped #true` keeps a model's `long-context-cost` tier from capping its runtime context window while extended context is off: the coding agent normally lowers the window to the tier's input threshold so compaction fires before a request crosses into premium pricing. Use it where the tier is an API-equivalent estimate on a subscription host (`xai-oauth`) or where the model is served at its full window regardless (GPT-5.4, except on GitHub Copilot). `buildModel` materializes it as `Model.longContextWindowUncapped`; explicit user context overrides still apply.
225
+
226
+ `max-tokens-fallback` seeds `maxTokens` only when the row reports none, and `max-tokens-ceiling` lowers a larger reported `maxTokens` to the host's output cap (an unknown budget stays unknown unless a fallback seeded it). Both apply in `buildModel` after `limits-patch`, so discovery mappers pass upstream output budgets through verbatim. See the Kimi rules in [`providers/fireworks.kdl`](providers/fireworks.kdl).
227
+
224
228
  `inline-image-byte-budget` caps retained base64 image characters for a deployment's request body. It leaves room for tool schemas and text beneath the provider's body-size limit; the coding agent reads it through `resolveInlineImageByteBudget` before sending live history, and snapcompact gives its archive frames half of it (`frameDataBytesBudget`). The budget applies only while the request reaches the official endpoint (the Anthropic API, first-party OpenAI, or the Codex backend at its default URL), which callers decide with pi-ai's `reachesOfficialEndpoint`: `compat.officialEndpoint` for the configured `baseUrl`, and for first-party Anthropic models also the `ANTHROPIC_BASE_URL` or Foundry `FOUNDRY_BASE_URL` reroute applied at request time. A custom or rerouted endpoint imposes its own body limit.
225
229
 
226
230
  `image-count-budget` is the most images one request may carry, archive frames, screenshots and imaged tool results combined. The coding agent reads it through `resolveImageCountBudget`, drops the oldest images past it, and caps snapcompact frames at it; unassigned hosts get a floor of 5. `official-image-count-budget` replaces it while the request reaches the official endpoint (same gate as `inline-image-byte-budget`), for limits established on the host's own endpoint (Anthropic: 600, 100 on 200k-window models; Codex: 2,000); a custom or rerouted endpoint keeps `image-count-budget` (Anthropic 90, Codex 200).
227
231
 
232
+ `endpoint-hosts` lists the HTTPS hostnames a provider serves besides its bundled base URL's host, such as Copilot's per-plan `api.<plan>.githubcopilot.com` hosts. That host plus these define the provider's own endpoint (`isCatalogEndpointUrl`), which separates first-party traffic from a custom model or provider override that keeps a catalog provider id but points `baseUrl` at a gateway, proxy, or local server. Connection prewarming and request compression act only on the provider's own endpoint.
233
+
234
+ `request-encodings` lists the `Content-Encoding`s (`zstd`, `gzip`) a provider's own endpoint decodes on request bodies, most preferred first; pi-ai's `wrapFetchForRequestCompression` reads them through `resolveRequestCompression` and compresses bodies of 1 KiB or more. A proxy, relay, or `ANTHROPIC_BASE_URL` reroute never receives bytes it cannot read. `request-encoding-rejection` is a regular expression for the 400/415 error body the host returns when it cannot decode the body; unassigned, every 400/415 is re-checked with an uncompressed resend. A rejected encoding is dropped for that host only after the uncompressed resend succeeds. Assign them only from a live probe of every endpoint host: a compressed body, the same plain JSON mislabeled as compressed (a decoding host must reject it), and a real request.
235
+
228
236
  `judgment-questions-max` is the most questions one System One judgment request may carry (razorback16/openjev: 256; Cloudflare Clef on Workers AI and through OpenRouter Decisions: 64; unidentified `*openjev*` deployments: 16). The coding agent reads it through `resolveJudgmentQuestionsMax` and `NativeJudge` splits larger requests into parallel chunks; unassigned means the endpoint takes any batch size. [`providers/judgment-questions.kdl`](providers/judgment-questions.kdl) lists the servers verified uncapped or not isolatable.
229
237
 
238
+ `judgment-image-input` assigns model-specific native judgment image bounds, read through `resolveJudgmentImageInput`. Its object accepts `mime-types` as a comma-separated lowercase MIME list and positive integer `max-count`, `max-bytes`, `max-total-bytes`, `min-pixels`, `max-pixels`, `min-side`, and `max-side`; byte limits count decoded data. `NativeJudge` enforces these alongside its wire's encoding constraints without resizing, and candidate filtering skips models whose bounds reject the images. Unassigned adds no model-specific bounds. The Jev selectors in [`providers/typesafe.kdl`](providers/typesafe.kdl) and [`providers/openrouter.kdl`](providers/openrouter.kdl) own Jev's limits rather than imposing them on every model using those APIs.
239
+
230
240
  `prompt-cache-lookback` is how many block positions back from a cache breakpoint the model's prompt cache looks for an earlier request's entry (Claude: 20, counting the breakpoint; a run of consecutive `tool_use` or `tool_result` blocks is one position). The coding agent reads it through `resolvePromptCacheLookback` and keeps warm-cache tool-result pruning inside the window; unassigned means no known bound.
231
241
 
232
242
  `task-sibling-prompt-cache #true` lets Task children use an automatically derived sibling cache key on the matching request model. Missing or `#false` keeps the provider's per-child default; explicit cache keys still win. The session resolves this catalog policy against the actual model for primary turns, handoff, and ephemeral requests that reuse the live context, including model changes, without sharing conversation IDs or response state. The automatic candidate is persisted separately from explicit keys and survives full forks, `/tan`, and session reopening; new and partial sessions discard it. A fork carrying an automatic candidate uses its own conversation ID on unsupported models, and re-enables the candidate when returning to an eligible model. Ordinary forks without an automatic candidate keep their existing inherited cache routing. Conventional compaction keeps its existing cache routing because its serialized summary prompt differs from the primary context. The opt-in covers Muse Code's `muse-spark-1.3`, `muse-spark-1.3-contributor`, `muse-wonder-1.0`, `muse-wonder-1.0-contributor`, `muse-wonder-full-1.0`, and `muse-wonder-full-1.0-contributor` over `openai-responses`. Meta documents shared-prefix `prompt_cache_key` routing for Responses. Warmed sibling cache reuse was measured on Spark 1.3 Contributor; the standard Spark variant shares this scope following maintainer guidance. A bounded synthetic probe observed cache reuse on all four Wonder variants; it does not establish production or latency benefits. Auxiliary-request latency has not been measured. Provider selectors also match custom providers that declare the same `providerType`; no benefit is established for custom endpoints.
233
243
 
234
244
  `image-max-edge` is the longest image edge, in pixels, a host passes to the model; it downscales larger images to fit first (Cursor: 1024 for Claude and GPT). Snapcompact reads it through `resolveImageMaxEdge` and renders frames no wider, so their glyphs reach the model at the size they were drawn; unassigned means the host keeps the image's size.
235
245
 
236
- `snapcompact-image-reads #true` opts a reader in to getting a fresh `read` result as snapcompact frames when `snapcompact.toolResults` is on. Frames lose exact indentation, which a weaker reader then miscopies into its edits (Haiku 5.5: 22 of 54 first edits exact, against 53 of 54 from text); Opus 5.5 edited exactly from frames, so Opus 5.5+ opts in ([`classes/anthropic.kdl`](classes/anthropic.kdl)). Unassigned: `read` results stay text; other tools' results are imaged either way.
246
+ `snapcompact-image-reads #true` opts a reader in to getting a fresh `read` result as snapcompact frames when `snapcompact.toolResults` is on. Frames collapse whitespace, so snapcompact draws each row's leading indentation as a count bracket (`[»2]`, `[·4]`) and keeps the `[path#TAG]` header as text beside them. On 3–4 frame reads Opus 5.5 then made exact first edits from frames within one of text (38 vs 39 of 40), so Opus 5.5+ opts in ([`classes/anthropic.kdl`](classes/anthropic.kdl)); Sonnet 5.5 (37 vs 40) and Haiku 5.5 (35 vs 40) still miscounted deep tab indents. Unassigned: `read` results stay text; other tools' results are imaged either way.
237
247
 
238
248
  The routed-subscription registry axes (`upstream-rotation`, `region-upstreams-global|us|eu`, `region-limits-eu`, `credit-rates`, `list-price-from`, `routing-family`, `policy-aliases`, `entitlement`, `default-reasoning-off`) describe a gateway whose proxy fans one model out to several upstreams. They are read through `src/compat/factory-droid.ts` by Factory Droid discovery and its request provider, not materialized by `buildModel`. A provider-wide `region-upstreams-*` rule is the upstream serving table and a model rule replaces it for that region. `list-price-from "<provider>" ["<id>"]` shows a bundled row's list price beside the subscription's own billing; unlike seed values it is resolved at runtime and degrades to the seed's zero cost when the row is gone. See [`providers/factory-droid.kdl`](providers/factory-droid.kdl), whose wire and billing pool per model are `api-routes` and `quota-tiers` rules in `runtime/behavior.kdl`.
239
249
 
@@ -325,11 +335,17 @@ behavior {
325
335
  pricing-peer provider="google-antigravity" peers="google" "google-vertex" {
326
336
  alias "gemini-3-pro" peer-id="gemini-3-pro-preview"
327
337
  }
338
+ role-defaults "smol" {
339
+ model "claude-haiku-5-5"
340
+ model "openai-codex/gpt-5.3-codex-spark"
341
+ }
328
342
  }
329
343
  ```
330
344
 
331
345
  `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`.
332
346
 
347
+ `role-defaults "<role>"` declares one agent role's built-in default model selector chain, one `model "<selector>"` child per entry, most preferred first. Selectors are copied verbatim (`provider/id`, bare ids, fuzzy fragments such as `-mini`, and `@role` references); duplicates within a role and repeated roles are compile errors. `roleDefaultModels()` returns every chain keyed by role in declaration order.
348
+
333
349
  ## Auth grammar
334
350
 
335
351
  Every provider has exactly one `auth "<id>" { … }` node under `auth/`; `auth/_order.kdl` holds a single `login-order "id" …` node pinning the `/login` roster order (every provider with a `login` and `show-in-login-list` unset/true must appear; providers without a login sort alphabetically after it). Runtime accessors live in `src/compat/auth.ts`; the id unions in the generated `src/compat/auth-ids.ts`. Every provider in `provider-models/descriptors.ts` needs an auth node (`@oh-my-pi/pi-ai` type-checks this).
@@ -1,5 +1,5 @@
1
1
  auth "google-antigravity" {
2
- name "Antigravity (Gemini 3, Claude, GPT-OSS)"
2
+ name "Google Antigravity (Gemini, Claude, GPT-OSS)"
3
3
  login "oauth-code" {
4
4
  // Antigravity uses different Google OAuth credentials for its additional models.
5
5
  client-id "MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==" encoding="base64"
@@ -120,9 +120,8 @@ class "anthropic" {
120
120
  supports-sampling-params #false
121
121
  }
122
122
  }
123
- // Opus 5.5 made every first edit exactly right from `read` results it saw
124
- // only as snapcompact frames (24/24); Haiku 5.5 lost leading whitespace
125
- // (22/54), so other lines keep `read` as text until measured.
123
+ // Opus 5.5 edits from imaged `read` results within one miss of text; Sonnet
124
+ // and Haiku 5.5 miscount deep tab indents (measurements: rules/README.md).
126
125
  family "opus" {
127
126
  revision ">=5.5 <10" {
128
127
  snapcompact-image-reads #true
@@ -59,6 +59,10 @@ class "deepseek" {
59
59
  on "alibaba-token-plan" "deepseek" {
60
60
  supports-developer-role #false
61
61
  }
62
+ // Replaces the provider-keyed leaked chat-template special-token strip.
63
+ on "nvidia" "deepseek" {
64
+ strip-deepseek-special-tokens #true
65
+ }
62
66
  on "deepseek" "opencode-go" {
63
67
  family "pro" {
64
68
  max-tokens-field "max_tokens"
@@ -7,6 +7,16 @@ class "minimax" {
7
7
  }
8
8
  // Replaces the MiniMax lineage branch in both OpenAI baselines.
9
9
  reasoning-deltas-may-be-cumulative #true
10
+ // Replaces the identity default: M2/M3 thinking rides Anthropic adaptive
11
+ // thinking on every Messages route, custom provider ids included.
12
+ on-api "anthropic-messages" {
13
+ family "m2" {
14
+ thinking-mode "anthropic-adaptive"
15
+ }
16
+ family "m3" {
17
+ thinking-mode "anthropic-adaptive"
18
+ }
19
+ }
10
20
  on "aimlapi" "coreweave" "fireworks" "huggingface" "kilo" "nanogpt" "novita" "nvidia" \
11
21
  "opencode-go" "opencode-zen" "together" "venice" "wafer-serverless" "zenmux" {
12
22
  family "m3" {
@@ -11,6 +11,14 @@ class "openai" {
11
11
  revision ">=5" {
12
12
  supports-sampling-params #false
13
13
  }
14
+ // GPT-5.4 serves its 1M window, and its long-context price tier does not
15
+ // cap that window. GitHub Copilot keeps its own cap (github-copilot.kdl).
16
+ models "gpt-5.4" {
17
+ limits-patch {
18
+ context-window 1000000
19
+ }
20
+ long-context-window-uncapped #true
21
+ }
14
22
  // GPT wire ladders are model generations, not host contracts: <5.2 keeps
15
23
  // the classic four-tier scale, 5.2+ drops minimal for xhigh, and 5.6+
16
24
  // exposes the wire-exact five-tier low..max scale.
@@ -92,6 +92,10 @@ provider "anthropic" {
92
92
  // https://platform.claude.com/docs/en/build-with-claude/vision
93
93
  image-count-budget 90
94
94
  official-image-count-budget 600
95
+ // The Messages API decodes gzip request bodies and rejects zstd and br
96
+ // with a 400 (`request_body_encoding_invalid`).
97
+ request-encodings "gzip"
98
+ request-encoding-rejection "request_body_encoding_invalid|Unsupported Content-Encoding"
95
99
  class "anthropic" {
96
100
  revision "<4.6" {
97
101
  official-image-count-budget 100
@@ -3,6 +3,8 @@
3
3
  provider "azure" {
4
4
  default-model "gpt-5.5"
5
5
  env "AZURE_OPENAI_API_KEY"
6
+ // Replaces the provider-keyed strict Responses tool-call/result pairing.
7
+ strict-responses-pairing #true
6
8
 
7
9
  class "openai" {
8
10
  thinking-mode "effort"
@@ -10,6 +10,9 @@ provider "cerebras" {
10
10
  // Replaces the Cerebras provider branches in the completions baseline.
11
11
  supports-usage-in-streaming #false
12
12
  tool-strict-mode "all_strict"
13
+ // Replaces the provider check in agent-loop history normalization: replayed
14
+ // assistant history drops its thinking blocks.
15
+ strip-thinking-history #true
13
16
  // Prompt caching carries no discount: cached input tokens bill at the
14
17
  // standard input rate (https://inference-docs.cerebras.ai/capabilities/prompt-caching).
15
18
  cache-read-at-input-rate #true
@@ -10,4 +10,20 @@ provider "cline-pass" {
10
10
  wire-model-id-mode "cline-pass"
11
11
  // Replaces the Cline Pass thinking-format branch.
12
12
  thinking-format "openai"
13
+ // Replaces the Cline Pass completions baseline branches: the reasoning
14
+ // field, the disable shape, and reasoning kept on under tool choice.
15
+ reasoning-content-field "reasoning"
16
+ reasoning-disable-mode "cline-enabled-false"
17
+ disable-reasoning-on-forced-tool-choice #false
18
+ disable-reasoning-on-tool-choice #false
19
+ // Discovery reports each row's thinking ladder; a row without one
20
+ // (`supportsReasoningEffort: false`) has no reasoning control at all.
21
+ trust-explicit-thinking-only #true
22
+ class "qwen" {
23
+ supports-forced-tool-choice #false
24
+ cache-control-format "anthropic"
25
+ }
26
+ class "anthropic" {
27
+ cache-control-format "anthropic"
28
+ }
13
29
  }
@@ -337,6 +337,16 @@ provider "commandcode" {
337
337
  cache-write 0.0
338
338
  }
339
339
  }
340
+ // Restate the 64K provider output cap beside the openai class's GPT-5.4
341
+ // window patch, or the class rule wins the whole limits axis.
342
+ class "openai" {
343
+ models "gpt-5.4" {
344
+ limits-patch {
345
+ context-window 1000000
346
+ max-tokens 65536
347
+ }
348
+ }
349
+ }
340
350
  models "gpt-5.3-codex" {
341
351
  cost-patch {
342
352
  input 2.0
@@ -1,4 +1,4 @@
1
- // Catalog entry for "deepinfra"; no deployment-contract compat rules are needed.
1
+ // Catalog entry for "deepinfra".
2
2
 
3
3
  provider "deepinfra" {
4
4
  default-model "deepseek-ai/DeepSeek-V4-Flash-0731"
@@ -9,6 +9,8 @@ provider "deepinfra" {
9
9
  image "openai-images"
10
10
  tts "openai-speech"
11
11
  }
12
+ // Same-provider image-generation swap target for chat models.
13
+ image-model "black-forest-labs/FLUX-2-pro"
12
14
 
13
15
  seed base-url="https://api.deepinfra.com/v1/openai" bundle="always" {
14
16
  model "black-forest-labs/FLUX-2-pro" name="FLUX.2 Pro" api="openai-images" {
@@ -1,4 +1,4 @@
1
- // Catalog entry for "devin"; no deployment-contract compat rules are needed.
1
+ // Catalog entry and deployment contract for "devin".
2
2
 
3
3
  provider "devin" {
4
4
  default-model "swe-1-6"
@@ -191,3 +191,9 @@ provider "devin" {
191
191
  }
192
192
  }
193
193
  }
194
+
195
+ // Devin's Cascade catalog reports each model's thinking surface; a row
196
+ // without one carries no reasoning ladder.
197
+ on-api "devin-agent" {
198
+ trust-explicit-thinking-only #true
199
+ }
@@ -691,6 +691,12 @@ provider "factory-droid" {
691
691
  thinking-requires-effort #true
692
692
  thinking-default-level "medium"
693
693
  }
694
+ // The native 922K window is authoritative; the class-wide gpt-5.4 1M patch does not apply.
695
+ class "openai" {
696
+ models "gpt-5.4" {
697
+ limits-patch {}
698
+ }
699
+ }
694
700
  models "gpt-5.4-fast" {
695
701
  routing-family "openai"
696
702
  upstream-rotation "openai" "azure_openai"
@@ -38,4 +38,19 @@ provider "firepass" {
38
38
  thinking-mode "effort"
39
39
  thinking-efforts "low" "high" "max"
40
40
  }
41
+ // The Fire Pass router shares Fireworks' 32,768 Kimi K2 output cap (#1849).
42
+ class "kimi" {
43
+ family "k2" {
44
+ max-tokens-ceiling 32768
45
+ }
46
+ family "k2.5" {
47
+ max-tokens-ceiling 32768
48
+ }
49
+ family "k2.6" {
50
+ max-tokens-ceiling 32768
51
+ }
52
+ family "k2-thinking" {
53
+ max-tokens-ceiling 32768
54
+ }
55
+ }
41
56
  }
@@ -24,6 +24,32 @@ provider "fireworks" priority=-1 {
24
24
  class "unknown" {
25
25
  thinking-efforts "minimal" "low" "medium" "high" "xhigh"
26
26
  }
27
+ // Replaces the Fireworks Kimi output caps in provider-models/openai-compat.ts
28
+ // (#1849). The K2 family ships long reasoning traces Fireworks bounds at
29
+ // 32,768 output tokens; the control plane reports no output budget, so the
30
+ // cap is also the fallback. K2.7-Code serves its full 65,536 (verified with
31
+ // a 58,971-token completion), which seeds rows that report none.
32
+ class "kimi" {
33
+ family "k2" {
34
+ max-tokens-fallback 32768
35
+ max-tokens-ceiling 32768
36
+ }
37
+ family "k2.5" {
38
+ max-tokens-fallback 32768
39
+ max-tokens-ceiling 32768
40
+ }
41
+ family "k2.6" {
42
+ max-tokens-fallback 32768
43
+ max-tokens-ceiling 32768
44
+ }
45
+ family "k2-thinking" {
46
+ max-tokens-fallback 32768
47
+ max-tokens-ceiling 32768
48
+ }
49
+ family "k2.7-code" {
50
+ max-tokens-fallback 65536
51
+ }
52
+ }
27
53
  // residue: taxonomy ranks and exact globs do not isolate these models.
28
54
  models "deepseek-v4-flash" {
29
55
  requires-reasoning-content-for-all-assistant-turns #true
@@ -14,6 +14,30 @@ provider "github-copilot" {
14
14
  // not belong to this connection`: a resumed session rebuilds prior turns
15
15
  // until its first response.
16
16
  connection-bound-native-history #true
17
+ // Per-plan API hosts the Copilot token resolves to; the bundled base URL
18
+ // is `api.githubcopilot.com`.
19
+ endpoint-hosts "api.individual.githubcopilot.com" "api.business.githubcopilot.com" \
20
+ "api.enterprise.githubcopilot.com"
21
+ // `/chat/completions`, `/responses` and `/v1/messages` decode zstd, br and
22
+ // gzip request bodies (deflate is rejected), probed live 2026-10. GHE.com
23
+ // tenants (`copilot-api.<tenant>.ghe.com`) are unverified and stay plain.
24
+ request-encodings "zstd" "gzip"
25
+ request-encoding-rejection "invalid_request_body|[Ff]ailed to (?:parse|read) request"
26
+ // Replaces the provider-keyed strict Responses tool-call/result pairing.
27
+ strict-responses-pairing #true
28
+ // Replaces the provider check that disabled reasoning params on chat completions.
29
+ on-api "openai-completions" {
30
+ supports-reasoning-params #false
31
+ }
32
+ // Copilot caps GPT-5.4 below the lineage's 1M window (model-limits in
33
+ // runtime/behavior.kdl), so the class-wide window patch and uncapped
34
+ // window do not apply here.
35
+ class "openai" {
36
+ models "gpt-5.4" {
37
+ limits-patch {}
38
+ long-context-window-uncapped #false
39
+ }
40
+ }
17
41
  class "anthropic" {
18
42
  revision ">=4 <4.6" {
19
43
  thinking-efforts "minimal" "low" "medium" "high" "xhigh"
@@ -41,6 +41,17 @@ provider "google-antigravity" {
41
41
  // any image edge over 3,000 px. The clamp above caps each image; with no
42
42
  // byte budget on this wire, the count bounds the body.
43
43
  image-count-budget 20
44
+ // Cache-warmer lifetime. Claude here is Vertex Anthropic, whose prompt
45
+ // cache has a five-minute default TTL refreshed on every hit; this wire
46
+ // sends no `cache_control`, so the 1h tier is never requested.
47
+ // https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/partner-models/claude/prompt-caching
48
+ // Gemini declares none: its implicit cache keeps entries for a
49
+ // load-dependent time ("caches always deleted within 24 hours"), so no
50
+ // fixed lifetime exists to warm against.
51
+ // https://cloud.google.com/blog/products/ai-machine-learning/vertex-ai-context-caching
52
+ prompt-cache {
53
+ short 300
54
+ }
44
55
  family "opus" {
45
56
  thinking-mode "budget"
46
57
  }
@@ -6,6 +6,8 @@ provider "moonshot" {
6
6
  // as a fallback so China users need not learn MOONSHOT_API_KEY. (#2883)
7
7
  env "MOONSHOT_API_KEY" "KIMI_API_KEY"
8
8
  discovery label="Moonshot"
9
+ // Moonshot's China platform, selected with MOONSHOT_BASE_URL (#2883).
10
+ endpoint-hosts "api.moonshot.cn"
9
11
 
10
12
  class "kimi" {
11
13
  // Replaces the Moonshot Kimi thinking-format branch.
@@ -10,6 +10,9 @@ provider "ollama-cloud" {
10
10
  thinking-mode "effort"
11
11
  // Same runtime as local Ollama: a trailing assistant turn is continued verbatim.
12
12
  supports-assistant-prefill #true
13
+ // Replaces the provider checks in model-registry.ts and the generator:
14
+ // never send a caller output limit.
15
+ omit-max-output-tokens #true
13
16
  class "unknown" {
14
17
  thinking-efforts "minimal" "low" "medium" "high"
15
18
  }
@@ -21,6 +21,9 @@ provider "openai-codex" {
21
21
  inline-image-byte-budget 24000000
22
22
  image-count-budget 200
23
23
  official-image-count-budget 2000
24
+ // The backend decodes zstd request bodies, as the official client sends.
25
+ // Its rejection body is undocumented, so any 400/415 re-checks uncompressed.
26
+ request-encodings "zstd"
24
27
 
25
28
  seed api="openai-codex-responses" base-url="https://chatgpt.com/backend-api" bundle="always" {
26
29
  // Label only: the Codex backend ignores `tool.model` and runs gpt-image-2-codex.
@@ -224,3 +227,9 @@ provider "openai-codex" {
224
227
  context-window-floor 1000000
225
228
  }
226
229
  }
230
+
231
+ // The Codex backend rejects every sampling control (`temperature`, `top_p`,
232
+ // penalties, stop sequences), whichever model it serves (#3117).
233
+ on-api "openai-codex-responses" {
234
+ supports-sampling-params #false
235
+ }
@@ -4,6 +4,10 @@ provider "opencode-go" {
4
4
  default-model "kimi-k2.7-code"
5
5
  env "OPENCODE_API_KEY"
6
6
  dynamic-models-authoritative #true
7
+ // Replaces the provider check that disabled forced tool choice on Responses.
8
+ on-api "openai-responses" {
9
+ supports-forced-tool-choice #false
10
+ }
7
11
 
8
12
  // The Flash lanes accept images despite their IDs lacking a vision suffix;
9
13
  // verified against the gateway (unguessable pixel-rendered text read back
@@ -4,6 +4,10 @@ provider "opencode-zen" {
4
4
  default-model "claude-opus-5"
5
5
  env "OPENCODE_API_KEY"
6
6
  dynamic-models-authoritative #true
7
+ // Replaces the provider check that disabled forced tool choice on Responses.
8
+ on-api "openai-responses" {
9
+ supports-forced-tool-choice #false
10
+ }
7
11
 
8
12
  // Replaces the OpenCode reasoning-model whenThinking fallback object.
9
13
  // Applies to every completions-routed reasoning model, discovered ids