@oh-my-pi/pi-catalog 18.10.2 → 18.10.3
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 +32 -0
- package/dist/types/compat/image-tokenization.d.ts +9 -1
- package/dist/types/compat/request-size.d.ts +11 -3
- package/dist/types/compat/thinking-replay.d.ts +9 -0
- package/dist/types/model-thinking.d.ts +11 -1
- package/dist/types/types.d.ts +29 -5
- package/dist/types/wire/codex.d.ts +2 -0
- package/package.json +4 -4
- package/src/build.ts +3 -2
- package/src/compat/axes.ts +43 -2
- package/src/compat/cascade.ts +4 -1
- package/src/compat/image-tokenization.ts +28 -10
- package/src/compat/request-size.ts +18 -5
- package/src/compat/resolve.ts +31 -5
- package/src/compat/rules/README.md +10 -2
- package/src/compat/rules/classes/anthropic.kdl +40 -2
- package/src/compat/rules/classes/meta.kdl +5 -0
- package/src/compat/rules/classes/openai.kdl +26 -3
- package/src/compat/rules/classes/xai.kdl +26 -0
- package/src/compat/rules/providers/amazon-bedrock.kdl +1 -0
- package/src/compat/rules/providers/anthropic.kdl +13 -0
- package/src/compat/rules/providers/cursor.kdl +30 -1
- package/src/compat/rules/providers/google-antigravity.kdl +26 -0
- package/src/compat/rules/providers/google-gemini-cli.kdl +1 -0
- package/src/compat/rules/providers/google-vertex.kdl +1 -0
- package/src/compat/rules/providers/google.kdl +1 -0
- package/src/compat/rules/providers/image-tokenization.kdl +2 -1
- package/src/compat/rules/providers/meta.kdl +16 -0
- package/src/compat/rules/providers/muse-code.kdl +9 -0
- package/src/compat/rules/providers/openai-codex.kdl +8 -0
- package/src/compat/rules/providers/openai.kdl +1 -0
- package/src/compat/rules/providers/openrouter.kdl +1 -0
- package/src/compat/rules/providers/umans.kdl +1 -0
- package/src/compat/rules/providers/xai-oauth.kdl +3 -0
- package/src/compat/rules/providers/xai.kdl +3 -0
- package/src/compat/rules/runtime/behavior.kdl +3 -3
- package/src/compat/rules/taxonomy/meta.kdl +3 -0
- package/src/compat/rules.json +1 -1
- package/src/compat/thinking-replay.ts +33 -0
- package/src/model-thinking.ts +13 -1
- package/src/model-tokenizer.ts +7 -1
- package/src/models.json +1 -1
- package/src/provider-models/bundled-references.ts +5 -2
- package/src/provider-models/openai-compat.ts +3 -1
- package/src/types.ts +31 -4
- package/src/wire/codex.ts +17 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.10.3] - 2026-10-12
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- `resolveInlineImageByteBudget(model, officialEndpoint)` and `resolveImageCountBudget(model, officialEndpoint)` take whether the request reaches the official endpoint instead of reading `compat.officialEndpoint`, which misses request-time reroutes such as `ANTHROPIC_BASE_URL`; pass pi-ai's `reachesOfficialEndpoint(model)` ([#15434](https://github.com/can1357/oh-my-pi/pull/15434) by [@will-bogusz](https://github.com/will-bogusz))
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added Muse Wonder (`muse-wonder-1.0`) to Meta and Muse Code with its `minimal`–`max` effort ladder and pricing
|
|
14
|
+
- Added the `thinking-unverified` rule axis: on hosts whose model list carries no capability metadata (Meta, Muse Code), unrecognized models now offer an opt-in thinking ladder (`ThinkingConfig.unverified`) instead of none
|
|
15
|
+
- Added `thinking-unverified` axis for offering reasoning ladders on unclassified model IDs
|
|
16
|
+
- Added `Muse Wonder 1.0` model definition to Meta provider
|
|
17
|
+
- Added the opt-in `snapcompact-image-reads` axis: snapcompact images a fresh `read` result only for readers whose rule sets it; Claude Opus 5.5 and later opt in ([#14249](https://github.com/can1357/oh-my-pi/pull/14249) by [@will-bogusz](https://github.com/will-bogusz))
|
|
18
|
+
- Added the `image-count-budget` rule for how many images one request may carry (Anthropic 90, Codex 200, other hosts unchanged) and the `official-image-count-budget` override that applies only on the official endpoint: Anthropic 600 (100 on 200k-window Claude models), Codex 2,000 ([#15434](https://github.com/can1357/oh-my-pi/pull/15434) by [@will-bogusz](https://github.com/will-bogusz))
|
|
19
|
+
- Added a 24 MB inline-image byte budget for the Codex backend, which now counts as its official endpoint at its default URL and at paths under it such as `/codex/responses` ([#15434](https://github.com/can1357/oh-my-pi/pull/15434) by [@will-bogusz](https://github.com/will-bogusz))
|
|
20
|
+
- Added `isOfficialCodexApiUrl` to `wire/codex`, moved from pi-ai ([#15434](https://github.com/can1357/oh-my-pi/pull/15434) by [@will-bogusz](https://github.com/will-bogusz))
|
|
21
|
+
- Added `verified` to `image-tokenization` rules, marking the Claude 4.7+ high-res tier and the GPT-5.5 to 6.1 and gpt-6-astra rules as matching measured provider billing ([#15442](https://github.com/can1357/oh-my-pi/pull/15442) by [@will-bogusz](https://github.com/will-bogusz))
|
|
22
|
+
- Added the `image-max-edge` axis and `resolveImageMaxEdge()` for hosts that shrink images before the model reads them; Cursor's Claude and GPT models and its `default` (Auto) route fit 1024×1024 ([#15448](https://github.com/can1357/oh-my-pi/pull/15448) by [@will-bogusz](https://github.com/will-bogusz))
|
|
23
|
+
- Added the `thinking-replay-tokens` axis; Claude lines set it to `output` on every host, since a replayed signed thinking block bills the turn's full reasoning, not the signature's bytes ([#15446](https://github.com/can1357/oh-my-pi/pull/15446) by [@will-bogusz](https://github.com/will-bogusz))
|
|
24
|
+
- Added a KDL opt-in for sibling subagent prompt-cache sharing, enabled for Muse Code's Spark 1.3, Wonder 1.0, and Wonder Full 1.0 variants over the Responses API ([#15467](https://github.com/can1357/oh-my-pi/pull/15467) by [@iliaal](https://github.com/iliaal))
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Updated model definitions and compatibility rules for provider metadata
|
|
29
|
+
- Google Antigravity, xAI and Cursor requests keep up to 60 images, 20 for Claude on Antigravity and Cursor, up from 5; this counts snapcompact frames and live screenshots together ([#15449](https://github.com/can1357/oh-my-pi/pull/15449) by [@will-bogusz](https://github.com/will-bogusz))
|
|
30
|
+
- Snapcompact sizes archives at the frame prices measured for Antigravity Gemini (a fixed 1,089 tokens), Grok 4.6 to 4.20, and Claude Sonnet 4.6's standard tier ([#15449](https://github.com/can1357/oh-my-pi/pull/15449) by [@will-bogusz](https://github.com/will-bogusz))
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- Applied `max-image-dimension` and `max-image-payload-bytes` to Google API models, and declared the 5 MiB per-image limit of Claude on Google Antigravity ([#15444](https://github.com/can1357/oh-my-pi/pull/15444) by [@will-bogusz](https://github.com/will-bogusz))
|
|
35
|
+
- Fixed local token counts for OpenAI GPT-5 and later models (Codex, OpenAI, Azure, OpenRouter, Copilot and other hosts) reading about 12% low: their text is now counted with o200k_base instead of a bytes/4 estimate ([#15447](https://github.com/can1357/oh-my-pi/pull/15447) by [@will-bogusz](https://github.com/will-bogusz))
|
|
36
|
+
|
|
5
37
|
## [18.10.2] - 2026-10-12
|
|
6
38
|
|
|
7
39
|
### Added
|
|
@@ -23,7 +23,7 @@ export interface OpenAiPatchTokenization {
|
|
|
23
23
|
auto: "high" | "original";
|
|
24
24
|
}
|
|
25
25
|
/** One model line's image billing rule: the vendor formula and its per-model numbers. */
|
|
26
|
-
export type ImageTokenization = OpenAiPatchTokenization
|
|
26
|
+
export type ImageTokenization = (OpenAiPatchTokenization
|
|
27
27
|
/** Anthropic 28px patches, resized to fit a padded-edge limit and a visual-token budget. */
|
|
28
28
|
| {
|
|
29
29
|
regime: "anthropic-patch";
|
|
@@ -34,6 +34,9 @@ export type ImageTokenization = OpenAiPatchTokenization
|
|
|
34
34
|
| {
|
|
35
35
|
regime: "fixed";
|
|
36
36
|
tokens: number;
|
|
37
|
+
}) & {
|
|
38
|
+
/** Provider billing was measured to match this rule; unset for docs-only rules and wire fallbacks. */
|
|
39
|
+
verified?: boolean;
|
|
37
40
|
};
|
|
38
41
|
/** What reads the image: a built model, any `{ id }` with optional host and identity, or just a wire `{ api }`. */
|
|
39
42
|
export interface ImageTokenizationTarget {
|
|
@@ -48,5 +51,10 @@ export interface ImageTokenizationTarget {
|
|
|
48
51
|
* unregistered API).
|
|
49
52
|
*/
|
|
50
53
|
export declare function resolveImageTokenization(target: ImageTokenizationTarget): ImageTokenization | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Longest image edge the reader's host passes to the model (`image-max-edge`),
|
|
56
|
+
* or undefined when the host keeps the image's size.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveImageMaxEdge(target: ImageTokenizationTarget): number | undefined;
|
|
51
59
|
/** Billed input tokens for one image of `size` under `rule`; only `openai-patch` reads `detail`. */
|
|
52
60
|
export declare function imageTokens(rule: ImageTokenization, size: ImageSize, detail?: ImageDetail): number;
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import type { Model } from "../types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Maximum encoded inline-image bytes retained per request for a deployment with a documented body-size limit.
|
|
4
|
-
* The axis is a host contract: a rerouted
|
|
5
|
-
*
|
|
4
|
+
* The axis is a host contract: a rerouted endpoint (proxy, gateway) imposes its own limit, so the budget applies
|
|
5
|
+
* only when `officialEndpoint` says the request reaches the model's official endpoint. `compat.officialEndpoint`
|
|
6
|
+
* classifies only the configured `baseUrl`; pi-ai's `reachesOfficialEndpoint` also follows request-time reroutes.
|
|
6
7
|
*/
|
|
7
|
-
export declare function resolveInlineImageByteBudget(model: Model): number | undefined;
|
|
8
|
+
export declare function resolveInlineImageByteBudget(model: Model, officialEndpoint: boolean): number | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Most images one request to this deployment may carry, or `undefined` when no rule names the host. The
|
|
11
|
+
* `official-image-count-budget` axis replaces `image-count-budget` only when `officialEndpoint` says the request
|
|
12
|
+
* reaches the model's official endpoint (see {@link resolveInlineImageByteBudget}); a rerouted endpoint keeps the
|
|
13
|
+
* provider's generic budget.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveImageCountBudget(model: Model, officialEndpoint: boolean): number | undefined;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ImageTokenizationTarget } from "./image-tokenization.js";
|
|
2
|
+
/**
|
|
3
|
+
* `output`: the reasoning the signature carries, at the size its turn billed
|
|
4
|
+
* as output; the signature's own bytes are free. `signature`: the signature's
|
|
5
|
+
* bytes stand in for that reasoning.
|
|
6
|
+
*/
|
|
7
|
+
export type ThinkingReplayTokens = "output" | "signature";
|
|
8
|
+
/** How `reader` bills replayed thinking; `signature` unless its lineage declares otherwise. */
|
|
9
|
+
export declare function resolveThinkingReplayTokens(reader: ImageTokenizationTarget): ThinkingReplayTokens;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* id parsing, no host matching, no compat detection per request.
|
|
8
8
|
*/
|
|
9
9
|
import { Effort } from "./effort.js";
|
|
10
|
-
import type { Api, Model, ModelSpec } from "./types.js";
|
|
10
|
+
import type { Api, Model, ModelSpec, ThinkingConfig } from "./types.js";
|
|
11
11
|
/**
|
|
12
12
|
* Runtime helpers read baked metadata only, so they accept both pre-build
|
|
13
13
|
* specs and built models.
|
|
@@ -19,6 +19,16 @@ type ApiModel<TApi extends Api = Api> = ModelSpec<TApi> | Model<TApi>;
|
|
|
19
19
|
* controllable effort surface (`thinking: undefined`).
|
|
20
20
|
*/
|
|
21
21
|
export declare function getSupportedEfforts<TApi extends Api>(model: ApiModel<TApi>): readonly Effort[];
|
|
22
|
+
/**
|
|
23
|
+
* Neutral view of a model whose thinking ladder is unverified: `reasoning: false`
|
|
24
|
+
* and no ladder. Re-entering policy resolution starts from it (the ladder is
|
|
25
|
+
* derived output, not authored input), and requests without an effort send it so
|
|
26
|
+
* no reasoning or disable fields reach an upstream that may reject them.
|
|
27
|
+
*/
|
|
28
|
+
export declare function withoutUnverifiedThinking<T extends {
|
|
29
|
+
reasoning: boolean;
|
|
30
|
+
thinking?: ThinkingConfig;
|
|
31
|
+
}>(model: T): T;
|
|
22
32
|
/**
|
|
23
33
|
* Clamps a requested thinking level against explicit model metadata.
|
|
24
34
|
*
|
package/dist/types/types.d.ts
CHANGED
|
@@ -93,6 +93,14 @@ export interface ThinkingConfig {
|
|
|
93
93
|
* provides an explicit wire off-path.
|
|
94
94
|
*/
|
|
95
95
|
requiresEffort?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* The catalog has no evidence this model reasons (unclassified id, no
|
|
98
|
+
* capability metadata). The ladder is offered for the user to opt into:
|
|
99
|
+
* sessions leave thinking unset until the user picks an effort, and a
|
|
100
|
+
* request without an effort goes out as non-reasoning (see
|
|
101
|
+
* `withoutUnverifiedThinking`).
|
|
102
|
+
*/
|
|
103
|
+
unverified?: boolean;
|
|
96
104
|
}
|
|
97
105
|
export type Provider = string;
|
|
98
106
|
/** Token budgets for each thinking level (token-based providers only) */
|
|
@@ -936,9 +944,11 @@ export interface ResolvedOpenAIResponsesCompat extends ResolvedOpenAISharedCompa
|
|
|
936
944
|
isVercelGatewayHost: boolean;
|
|
937
945
|
/**
|
|
938
946
|
* The configured endpoint is first-party OpenAI (`provider === "openai"` on
|
|
939
|
-
* an `api.openai.com` or unset baseUrl)
|
|
940
|
-
*
|
|
941
|
-
*
|
|
947
|
+
* an `api.openai.com` or unset baseUrl), or the Codex backend
|
|
948
|
+
* (`openai-codex-responses` on the default Codex baseUrl). Gates platform
|
|
949
|
+
* OpenAI's official-only Responses behavior (default-on stateful
|
|
950
|
+
* `previous_response_id` chaining and the `text.verbosity` field; the Codex
|
|
951
|
+
* adapter is not governed by either gate) and host request-size budgets.
|
|
942
952
|
*/
|
|
943
953
|
officialEndpoint: boolean;
|
|
944
954
|
/** Run Harmony-protocol leak detection/mitigation on streamed output. */
|
|
@@ -1063,18 +1073,32 @@ export interface GoogleCompat {
|
|
|
1063
1073
|
antigravityUsageLabel?: string;
|
|
1064
1074
|
/** Strip image inputs before encoding (text-only serving of a multimodal id). */
|
|
1065
1075
|
stripImageInput?: boolean;
|
|
1076
|
+
/**
|
|
1077
|
+
* Largest width or height a single image may carry for a Claude reader on
|
|
1078
|
+
* this host (see {@link AnthropicCompat.maxImageDimension}). Setting this or
|
|
1079
|
+
* {@link maxImagePayloadBytes} turns on Claude's image clamp, many-image
|
|
1080
|
+
* cap included; unset leaves images as sent.
|
|
1081
|
+
*/
|
|
1082
|
+
maxImageDimension?: number;
|
|
1083
|
+
/**
|
|
1084
|
+
* Largest base64 payload a single image may carry, in bytes (see
|
|
1085
|
+
* {@link AnthropicCompat.maxImagePayloadBytes}).
|
|
1086
|
+
*/
|
|
1087
|
+
maxImagePayloadBytes?: number;
|
|
1066
1088
|
/** Thinking-loop watchdog guard family applied to streamed reasoning. */
|
|
1067
1089
|
thinkingLoopGuard?: "gemini" | "deepseek" | "xai";
|
|
1068
1090
|
/** See {@link BedrockCompat.supportsSamplingParams}. */
|
|
1069
1091
|
supportsSamplingParams?: boolean;
|
|
1070
1092
|
}
|
|
1071
1093
|
/** Fully-resolved google-API compat view, materialized once by `buildModel`. */
|
|
1072
|
-
export type ResolvedGoogleCompat = Required<Omit<GoogleCompat, "streamFirstEventTimeoutMs" | "streamIdleTimeoutMs" | "thinkingLoopGuard" | "antigravityUsageLabel" | "supportsSamplingParams">> & {
|
|
1094
|
+
export type ResolvedGoogleCompat = Required<Omit<GoogleCompat, "streamFirstEventTimeoutMs" | "streamIdleTimeoutMs" | "thinkingLoopGuard" | "antigravityUsageLabel" | "supportsSamplingParams" | "maxImageDimension" | "maxImagePayloadBytes">> & {
|
|
1073
1095
|
streamFirstEventTimeoutMs?: number;
|
|
1074
1096
|
streamIdleTimeoutMs?: number;
|
|
1075
1097
|
thinkingLoopGuard?: GoogleCompat["thinkingLoopGuard"];
|
|
1076
1098
|
antigravityUsageLabel?: string;
|
|
1077
1099
|
supportsSamplingParams?: boolean;
|
|
1100
|
+
maxImageDimension?: number;
|
|
1101
|
+
maxImagePayloadBytes?: number;
|
|
1078
1102
|
};
|
|
1079
1103
|
/** Sparse, user-authored compat overrides for a given API (models.json / config vocabulary). */
|
|
1080
1104
|
export type CompatConfigOf<TApi extends Api> = TApi extends "openai-completions" | "openrouter" | "openai-responses" | "azure-openai-responses" | "openai-codex-responses" ? OpenAICompat : TApi extends "anthropic-messages" ? AnthropicCompat : TApi extends "bedrock-converse-stream" ? BedrockCompat : TApi extends "devin-agent" ? DevinCompat : TApi extends "google-generative-ai" | "google-vertex" | "google-gemini-cli" ? GoogleCompat : undefined;
|
|
@@ -1153,7 +1177,7 @@ export interface ModelCost extends TokenCost {
|
|
|
1153
1177
|
* generations rather than providers: DeepSeek V3 through V4 share
|
|
1154
1178
|
* `"deepseek-v3"`; Kimi K2 through K3 share `"kimi-k2"`.
|
|
1155
1179
|
*/
|
|
1156
|
-
export type ModelTokenizer = "claude-v3" | "claude-v47" | "claude-v5" | "claude-v5-sonnet" | "qwen3" | "deepseek-v3" | "kimi-k2" | "glm5";
|
|
1180
|
+
export type ModelTokenizer = "claude-v3" | "claude-v47" | "claude-v5" | "claude-v5-sonnet" | "qwen3" | "deepseek-v3" | "kimi-k2" | "glm5" | "o200k";
|
|
1157
1181
|
/** One account's discovered entitlements on a model; see {@link Model.accountAccess}. */
|
|
1158
1182
|
export interface ModelAccountAccess {
|
|
1159
1183
|
/**
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Constants for OpenAI Codex (ChatGPT OAuth) backend
|
|
3
3
|
*/
|
|
4
4
|
export declare const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
|
|
5
|
+
/** Strict official-Codex endpoint check; exact origin or a path boundary after {@link CODEX_BASE_URL}. */
|
|
6
|
+
export declare function isOfficialCodexApiUrl(baseUrl: string | undefined): boolean;
|
|
5
7
|
/**
|
|
6
8
|
* Pinned OpenAI Codex client version (corresponds to @openai/codex package version).
|
|
7
9
|
* Updated from npm by `bun run check-spoofed-versions --update`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-catalog",
|
|
3
|
-
"version": "18.10.
|
|
3
|
+
"version": "18.10.3",
|
|
4
4
|
"description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -93,12 +93,12 @@
|
|
|
93
93
|
"gen:proto": "bun scripts/generate-protocols.ts"
|
|
94
94
|
},
|
|
95
95
|
"dependencies": {
|
|
96
|
-
"@oh-my-pi/omptype": "18.10.
|
|
97
|
-
"@oh-my-pi/pi-utils": "18.10.
|
|
96
|
+
"@oh-my-pi/omptype": "18.10.3",
|
|
97
|
+
"@oh-my-pi/pi-utils": "18.10.3"
|
|
98
98
|
},
|
|
99
99
|
"devDependencies": {
|
|
100
100
|
"@bgotink/kdl": "0.4.0",
|
|
101
|
-
"@oh-my-pi/pi-ai": "18.10.
|
|
101
|
+
"@oh-my-pi/pi-ai": "18.10.3",
|
|
102
102
|
"@types/bun": "^1.3.14"
|
|
103
103
|
},
|
|
104
104
|
"engines": {
|
package/src/build.ts
CHANGED
|
@@ -351,8 +351,9 @@ export function buildModel<TApi extends Api>(spec: ModelSpec<TApi>): Model<TApi>
|
|
|
351
351
|
const model: Model<TApi> = {
|
|
352
352
|
...spec,
|
|
353
353
|
// A reviewed `thinking-upgrade-neutral` policy can repair a stale
|
|
354
|
-
// `reasoning: false` discovery default
|
|
355
|
-
//
|
|
354
|
+
// `reasoning: false` discovery default, and an unclassified neutral spec
|
|
355
|
+
// gets an unverified ladder (see `resolveThinkingPolicy`); materialize
|
|
356
|
+
// either for transports and the picker.
|
|
356
357
|
reasoning: spec.reasoning || policy.thinking !== undefined,
|
|
357
358
|
name: cleanModelName(spec.name),
|
|
358
359
|
identity: policy.identity,
|
package/src/compat/axes.ts
CHANGED
|
@@ -244,8 +244,8 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
244
244
|
"first-party-provider": wire("firstPartyProvider", ["anthropic"]),
|
|
245
245
|
"honors-claude-code-beta-kill-switch": wire("honorsClaudeCodeBetaKillSwitch", ["anthropic"]),
|
|
246
246
|
"inject-claude-code-instruction": wire("injectClaudeCodeInstruction", ["anthropic"]),
|
|
247
|
-
"max-image-dimension": wire("maxImageDimension", ["anthropic"]),
|
|
248
|
-
"max-image-payload-bytes": wire("maxImagePayloadBytes", ["anthropic"]),
|
|
247
|
+
"max-image-dimension": wire("maxImageDimension", ["anthropic", "google"]),
|
|
248
|
+
"max-image-payload-bytes": wire("maxImagePayloadBytes", ["anthropic", "google"]),
|
|
249
249
|
"official-endpoint": wire("officialEndpoint", ["anthropic", "openai-responses"]),
|
|
250
250
|
"replay-unsigned-thinking": wire("replayUnsignedThinking", ["anthropic"]),
|
|
251
251
|
"requires-thinking-enabled": wire("requiresThinkingEnabled", ["anthropic"]),
|
|
@@ -329,6 +329,8 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
329
329
|
"thinking-suppress-when-off": { key: "suppressWhenOff", set: "thinking", shape: "scalar" },
|
|
330
330
|
"thinking-supports-display": { key: "supportsDisplay", set: "thinking", shape: "scalar" },
|
|
331
331
|
"thinking-upgrade-neutral": { key: "upgradeNeutral", set: "thinking", shape: "scalar" },
|
|
332
|
+
/** Unclassified neutral ids here are capability-unknown: offer the ladder as opt-in. */
|
|
333
|
+
"thinking-unverified": { key: "unverified", set: "thinking", shape: "scalar" },
|
|
332
334
|
|
|
333
335
|
// ── catalog metadata ──
|
|
334
336
|
"apply-patch-tool-type": {
|
|
@@ -389,6 +391,18 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
389
391
|
shape: "scalar",
|
|
390
392
|
values: [true, false],
|
|
391
393
|
},
|
|
394
|
+
/**
|
|
395
|
+
* How the model line bills a replayed signed thinking block as input:
|
|
396
|
+
* `output` bills the reasoning its signature carries, at the size the turn
|
|
397
|
+
* billed as output, and not the signature's bytes. Unassigned: estimated
|
|
398
|
+
* from the signature's bytes. Read through `./thinking-replay`.
|
|
399
|
+
*/
|
|
400
|
+
"thinking-replay-tokens": {
|
|
401
|
+
key: "thinkingReplayTokens",
|
|
402
|
+
set: "catalog",
|
|
403
|
+
shape: "scalar",
|
|
404
|
+
values: ["output", "signature"],
|
|
405
|
+
},
|
|
392
406
|
"delegation-bias": { key: "delegationBias", set: "catalog", shape: "scalar", values: DELEGATION_BIASES },
|
|
393
407
|
"discovery-api": { key: "discoveryApi", set: "catalog", shape: "scalar" },
|
|
394
408
|
"edit-prompt-variant": { key: "editPromptVariant", set: "catalog", shape: "scalar", values: ["full", "compact"] },
|
|
@@ -405,7 +419,28 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
405
419
|
"hosted-image": { key: "hostedImage", set: "catalog", shape: "scalar", values: [true, false] },
|
|
406
420
|
/** How the model line bills an input image; shape and formulas in `./image-tokenization`. */
|
|
407
421
|
"image-tokenization": { key: "imageTokenization", set: "catalog", shape: "object" },
|
|
422
|
+
/**
|
|
423
|
+
* Most images one request may carry, documents included where the host
|
|
424
|
+
* counts them. Unassigned: unknown host, held to a conservative floor.
|
|
425
|
+
*/
|
|
426
|
+
"image-count-budget": { key: "imageCountBudget", set: "catalog", shape: "scalar" },
|
|
427
|
+
/**
|
|
428
|
+
* Replaces `image-count-budget` while the request reaches the official
|
|
429
|
+
* endpoint (pi-ai `reachesOfficialEndpoint`): a limit measured or documented
|
|
430
|
+
* for the host's own endpoint, which a custom `baseUrl` or an
|
|
431
|
+
* `ANTHROPIC_BASE_URL`/`FOUNDRY_BASE_URL` reroute (proxy, gateway) need not share.
|
|
432
|
+
*/
|
|
433
|
+
"official-image-count-budget": { key: "officialImageCountBudget", set: "catalog", shape: "scalar" },
|
|
434
|
+
/**
|
|
435
|
+
* Opt-in: the reader may get a fresh `read` result as snapcompact frames
|
|
436
|
+
* (`snapcompact.toolResults`), because it edits as exactly from frames as
|
|
437
|
+
* from text. Unassigned: `read` results stay text, since a reader that
|
|
438
|
+
* misreads indentation in frames makes inexact edits.
|
|
439
|
+
*/
|
|
440
|
+
"snapcompact-image-reads": { key: "snapcompactImageReads", set: "catalog", shape: "scalar", values: [true, false] },
|
|
408
441
|
"image-model": { key: "imageModel", set: "catalog", shape: "scalar" },
|
|
442
|
+
/** Longest image edge the host passes to the model: it downscales larger images to fit first. */
|
|
443
|
+
"image-max-edge": { key: "imageMaxEdge", set: "catalog", shape: "scalar" },
|
|
409
444
|
"inline-image-byte-budget": { key: "inlineImageByteBudget", set: "catalog", shape: "scalar" },
|
|
410
445
|
/**
|
|
411
446
|
* Most questions one System One judgment request may carry; the judge
|
|
@@ -415,6 +450,12 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
|
|
|
415
450
|
"limits-patch": { key: "limitsPatch", set: "catalog", shape: "object" },
|
|
416
451
|
"long-context-cost": { key: "longContext", set: "catalog", shape: "object" },
|
|
417
452
|
"prompt-cache": { key: "promptCache", set: "catalog", shape: "object" },
|
|
453
|
+
"task-sibling-prompt-cache": {
|
|
454
|
+
key: "taskSiblingPromptCache",
|
|
455
|
+
set: "catalog",
|
|
456
|
+
shape: "scalar",
|
|
457
|
+
values: [true, false],
|
|
458
|
+
},
|
|
418
459
|
/**
|
|
419
460
|
* Prompt-cache lookback in block positions: how far back from a cache
|
|
420
461
|
* breakpoint the provider looks for an earlier request's cache entry.
|
package/src/compat/cascade.ts
CHANGED
|
@@ -409,13 +409,16 @@ function resolveOverIndex(index: RuleIndex, target: ResolveTarget): ResolvedAxes
|
|
|
409
409
|
reasoning = true;
|
|
410
410
|
}
|
|
411
411
|
}
|
|
412
|
+
// Unclassified targets get their provider's ladder too: it is offered as
|
|
413
|
+
// unverified thinking (`resolveThinkingPolicy`), never as reasoning capability.
|
|
414
|
+
const offersThinking = reasoning || target.class === "unknown";
|
|
412
415
|
const wire: WinnerTable = {};
|
|
413
416
|
const thinking: WinnerTable = {};
|
|
414
417
|
const catalog: WinnerTable = {};
|
|
415
418
|
for (const { rule, rank } of ranked) {
|
|
416
419
|
contest(wire, rule.compiled.wire, rank, rule, target);
|
|
417
420
|
contest(catalog, rule.compiled.catalog, rank, rule, target);
|
|
418
|
-
if (
|
|
421
|
+
if (offersThinking) contest(thinking, rule.compiled.thinking, rank, rule, target);
|
|
419
422
|
}
|
|
420
423
|
return {
|
|
421
424
|
wire: collect(wire, rule => rule.wire),
|
|
@@ -37,12 +37,16 @@ export interface OpenAiPatchTokenization {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/** One model line's image billing rule: the vendor formula and its per-model numbers. */
|
|
40
|
-
export type ImageTokenization =
|
|
40
|
+
export type ImageTokenization = (
|
|
41
41
|
| OpenAiPatchTokenization
|
|
42
42
|
/** Anthropic 28px patches, resized to fit a padded-edge limit and a visual-token budget. */
|
|
43
43
|
| { regime: "anthropic-patch"; maxEdge: number; maxTokens: number }
|
|
44
44
|
/** A fixed per-image budget regardless of pixels (Gemini 3 `media_resolution`). */
|
|
45
|
-
| { regime: "fixed"; tokens: number }
|
|
45
|
+
| { regime: "fixed"; tokens: number }
|
|
46
|
+
) & {
|
|
47
|
+
/** Provider billing was measured to match this rule; unset for docs-only rules and wire fallbacks. */
|
|
48
|
+
verified?: boolean;
|
|
49
|
+
};
|
|
46
50
|
|
|
47
51
|
/** A `class: "unknown"` identity for targets resolved by wire API alone. */
|
|
48
52
|
const UNCLASSIFIED: Pick<ModelIdentity, "class"> = { class: "unknown" };
|
|
@@ -62,17 +66,13 @@ export interface ImageTokenizationTarget {
|
|
|
62
66
|
identity?: Pick<ModelIdentity, "class" | "family" | "revision">;
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
* fallback; undefined when neither covers it (an unclassified model on an
|
|
68
|
-
* unregistered API).
|
|
69
|
-
*/
|
|
70
|
-
export function resolveImageTokenization(target: ImageTokenizationTarget): ImageTokenization | undefined {
|
|
69
|
+
/** Catalog axes for `target`'s identity on its host and wire API. */
|
|
70
|
+
function resolveImageAxes(target: ImageTokenizationTarget): Record<string, unknown> {
|
|
71
71
|
const provider = target.provider ?? "";
|
|
72
72
|
const model = target.id ?? "";
|
|
73
73
|
const identity: Pick<ModelIdentity, "class" | "family" | "revision"> =
|
|
74
74
|
target.identity ?? (model ? classifyModel(provider, model, { lenient: true }) : UNCLASSIFIED);
|
|
75
|
-
|
|
75
|
+
return resolveCascade({
|
|
76
76
|
provider,
|
|
77
77
|
api: target.api ?? "",
|
|
78
78
|
class: identity.class,
|
|
@@ -80,10 +80,28 @@ export function resolveImageTokenization(target: ImageTokenizationTarget): Image
|
|
|
80
80
|
reasoning: false,
|
|
81
81
|
...(identity.family !== undefined && { family: identity.family }),
|
|
82
82
|
...(identity.revision !== undefined && { revision: identity.revision }),
|
|
83
|
-
}).catalog
|
|
83
|
+
}).catalog;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The reader's image billing rule: its lineage rule, else its wire API's
|
|
88
|
+
* fallback; undefined when neither covers it (an unclassified model on an
|
|
89
|
+
* unregistered API).
|
|
90
|
+
*/
|
|
91
|
+
export function resolveImageTokenization(target: ImageTokenizationTarget): ImageTokenization | undefined {
|
|
92
|
+
const rule = resolveImageAxes(target).imageTokenization;
|
|
84
93
|
return isImageTokenization(rule) ? rule : undefined;
|
|
85
94
|
}
|
|
86
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Longest image edge the reader's host passes to the model (`image-max-edge`),
|
|
98
|
+
* or undefined when the host keeps the image's size.
|
|
99
|
+
*/
|
|
100
|
+
export function resolveImageMaxEdge(target: ImageTokenizationTarget): number | undefined {
|
|
101
|
+
const edge = resolveImageAxes(target).imageMaxEdge;
|
|
102
|
+
return typeof edge === "number" && edge > 0 ? edge : undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
87
105
|
/** Scale `size` down (never up) so its longest side is at most `maxEdge`. */
|
|
88
106
|
function fitLongEdge(size: ImageSize, maxEdge: number): ImageSize {
|
|
89
107
|
const longest = Math.max(size.width, size.height);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { toModelSpec } from "../provider-models/bundled-references";
|
|
2
2
|
import type { Model } from "../types";
|
|
3
|
+
import { resolveCatalogPolicy } from "./catalog-policy";
|
|
3
4
|
import { resolveCatalogAxes } from "./resolve";
|
|
4
5
|
|
|
5
6
|
const imageBudgetCache = new Map<string, number | null>();
|
|
@@ -7,12 +8,12 @@ const IMAGE_BUDGET_CACHE_MAX = 8192;
|
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Maximum encoded inline-image bytes retained per request for a deployment with a documented body-size limit.
|
|
10
|
-
* The axis is a host contract: a rerouted
|
|
11
|
-
*
|
|
11
|
+
* The axis is a host contract: a rerouted endpoint (proxy, gateway) imposes its own limit, so the budget applies
|
|
12
|
+
* only when `officialEndpoint` says the request reaches the model's official endpoint. `compat.officialEndpoint`
|
|
13
|
+
* classifies only the configured `baseUrl`; pi-ai's `reachesOfficialEndpoint` also follows request-time reroutes.
|
|
12
14
|
*/
|
|
13
|
-
export function resolveInlineImageByteBudget(model: Model): number | undefined {
|
|
14
|
-
|
|
15
|
-
if (!compat || !("officialEndpoint" in compat) || !compat.officialEndpoint) return undefined;
|
|
15
|
+
export function resolveInlineImageByteBudget(model: Model, officialEndpoint: boolean): number | undefined {
|
|
16
|
+
if (!officialEndpoint) return undefined;
|
|
16
17
|
const key = `${model.provider} ${model.id} ${model.api}`;
|
|
17
18
|
let budget = imageBudgetCache.get(key);
|
|
18
19
|
if (budget === undefined) {
|
|
@@ -23,3 +24,15 @@ export function resolveInlineImageByteBudget(model: Model): number | undefined {
|
|
|
23
24
|
}
|
|
24
25
|
return budget ?? undefined;
|
|
25
26
|
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Most images one request to this deployment may carry, or `undefined` when no rule names the host. The
|
|
30
|
+
* `official-image-count-budget` axis replaces `image-count-budget` only when `officialEndpoint` says the request
|
|
31
|
+
* reaches the model's official endpoint (see {@link resolveInlineImageByteBudget}); a rerouted endpoint keeps the
|
|
32
|
+
* provider's generic budget.
|
|
33
|
+
*/
|
|
34
|
+
export function resolveImageCountBudget(model: Model, officialEndpoint: boolean): number | undefined {
|
|
35
|
+
const policy = resolveCatalogPolicy(model);
|
|
36
|
+
const value = (officialEndpoint && policy.officialImageCountBudget) || policy.imageCountBudget;
|
|
37
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
|
|
38
|
+
}
|
package/src/compat/resolve.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { Effort, THINKING_EFFORTS } from "../effort";
|
|
19
19
|
import { hostMatchesUrl, modelMatchesHost } from "../hosts";
|
|
20
|
+
import { withoutUnverifiedThinking } from "../model-thinking";
|
|
20
21
|
import type {
|
|
21
22
|
Api,
|
|
22
23
|
CompatOf,
|
|
@@ -31,6 +32,7 @@ import type {
|
|
|
31
32
|
ResolvedOpenAISharedCompat,
|
|
32
33
|
ThinkingConfig,
|
|
33
34
|
} from "../types";
|
|
35
|
+
import { isOfficialCodexApiUrl } from "../wire/codex";
|
|
34
36
|
import {
|
|
35
37
|
isAnthropicSigningProxyUrl,
|
|
36
38
|
isAzureAnthropicRoute,
|
|
@@ -796,7 +798,11 @@ function resolveOpenAIResponsesPolicy(
|
|
|
796
798
|
supportsObfuscationOptOut: isOpenAIUrl || provider === "openai",
|
|
797
799
|
storeResponses: false,
|
|
798
800
|
connectionBoundNativeHistory: false,
|
|
799
|
-
|
|
801
|
+
// The Codex backend is official at the URL its requests default to when `baseUrl` is unset, and at paths
|
|
802
|
+
// under it such as `/codex` and `/codex/responses`, which its requests resolve to the same endpoint.
|
|
803
|
+
officialEndpoint:
|
|
804
|
+
isOfficialOpenAIEndpoint(provider, baseUrl) ||
|
|
805
|
+
(api === "openai-codex-responses" && isOfficialCodexApiUrl(baseUrl)),
|
|
800
806
|
harmonyLeakMitigation: false,
|
|
801
807
|
rejectRootObjectUnion: false,
|
|
802
808
|
retryWithoutStrictOnGrammarError: false,
|
|
@@ -980,6 +986,10 @@ function resolveGooglePolicy(
|
|
|
980
986
|
claudeThinkingBetaHeader: false,
|
|
981
987
|
antigravityClaudeToolMode: false,
|
|
982
988
|
stripImageInput: false,
|
|
989
|
+
// Unset means "no image contract": images go out as sent. Keys exist so
|
|
990
|
+
// a KDL rule or a user `compat` block can declare one.
|
|
991
|
+
maxImageDimension: undefined,
|
|
992
|
+
maxImagePayloadBytes: undefined,
|
|
983
993
|
};
|
|
984
994
|
applyWireAxes(compat, axes.wire, spec.api);
|
|
985
995
|
applyCompatOverrides(compat, spec.compat);
|
|
@@ -1089,6 +1099,7 @@ interface RuleThinking {
|
|
|
1089
1099
|
supportsDisplay?: boolean;
|
|
1090
1100
|
prefixBinding?: boolean;
|
|
1091
1101
|
upgradeNeutral?: boolean;
|
|
1102
|
+
unverified?: boolean;
|
|
1092
1103
|
}
|
|
1093
1104
|
|
|
1094
1105
|
function readRuleThinking(axes: ResolvedAxes): RuleThinking {
|
|
@@ -1109,6 +1120,7 @@ function readRuleThinking(axes: ResolvedAxes): RuleThinking {
|
|
|
1109
1120
|
if (typeof raw.supportsDisplay === "boolean") out.supportsDisplay = raw.supportsDisplay;
|
|
1110
1121
|
if (typeof raw.prefixBinding === "boolean") out.prefixBinding = raw.prefixBinding;
|
|
1111
1122
|
if (typeof raw.upgradeNeutral === "boolean") out.upgradeNeutral = raw.upgradeNeutral;
|
|
1123
|
+
if (typeof raw.unverified === "boolean") out.unverified = raw.unverified;
|
|
1112
1124
|
return out;
|
|
1113
1125
|
}
|
|
1114
1126
|
|
|
@@ -1156,8 +1168,16 @@ function resolveThinkingPolicy<TApi extends Api>(
|
|
|
1156
1168
|
// not re-expand it into an unadvertised ladder. Absent metadata is
|
|
1157
1169
|
// repaired only where KDL opts in with `thinking-upgrade-neutral`
|
|
1158
1170
|
// alongside a reviewed `thinking-efforts` ladder (the cascade upgrade for
|
|
1159
|
-
// stale source capability data)
|
|
1160
|
-
|
|
1171
|
+
// stale source capability data). On deployments whose discovery reports no
|
|
1172
|
+
// capabilities (`thinking-unverified`), an unclassified neutral spec — nothing
|
|
1173
|
+
// says whether it reasons — gets an unverified ladder the user may opt into.
|
|
1174
|
+
const unverified = !compatReasoning(spec, axes);
|
|
1175
|
+
if (
|
|
1176
|
+
unverified &&
|
|
1177
|
+
(explicitThinking !== undefined || facts.identity.class !== "unknown" || rule.unverified !== true)
|
|
1178
|
+
) {
|
|
1179
|
+
return undefined;
|
|
1180
|
+
}
|
|
1161
1181
|
if (
|
|
1162
1182
|
spec.provider === "cline-pass" &&
|
|
1163
1183
|
compat !== undefined &&
|
|
@@ -1181,9 +1201,14 @@ function resolveThinkingPolicy<TApi extends Api>(
|
|
|
1181
1201
|
if (config.efforts.length === 0) {
|
|
1182
1202
|
throw new Error(`Model ${spec.provider}/${spec.id} resolved to an empty thinking range`);
|
|
1183
1203
|
}
|
|
1184
|
-
if (rule.defaultLevel !== undefined) config.defaultLevel = rule.defaultLevel;
|
|
1185
1204
|
const effortMap = mergeEffortMap(spec, rule.effortMap, compat, config.efforts);
|
|
1186
1205
|
if (effortMap !== undefined) config.effortMap = effortMap;
|
|
1206
|
+
if (unverified) {
|
|
1207
|
+
// Off-by-default opt-in: no default level, mandatory effort, or wire off-switch.
|
|
1208
|
+
config.unverified = true;
|
|
1209
|
+
return config;
|
|
1210
|
+
}
|
|
1211
|
+
if (rule.defaultLevel !== undefined) config.defaultLevel = rule.defaultLevel;
|
|
1187
1212
|
if (rule.effortBudgets !== undefined) config.effortBudgets = rule.effortBudgets;
|
|
1188
1213
|
const supportsDisplay = rule.supportsDisplay ?? defaultSupportsDisplay(spec, facts);
|
|
1189
1214
|
if (supportsDisplay) config.supportsDisplay = true;
|
|
@@ -1316,7 +1341,8 @@ export function resolveModelPolicy<TApi extends Api>(
|
|
|
1316
1341
|
spec: ModelSpec<TApi>,
|
|
1317
1342
|
route?: ResolveRoute,
|
|
1318
1343
|
): ResolvedModelPolicy<TApi>;
|
|
1319
|
-
export function resolveModelPolicy(
|
|
1344
|
+
export function resolveModelPolicy(input: ModelSpec<Api>, route?: ResolveRoute): ResolvedModelPolicy<Api> {
|
|
1345
|
+
const spec = withoutUnverifiedThinking(input);
|
|
1320
1346
|
const identity = resolveIdentity(spec);
|
|
1321
1347
|
const facts = new IdentityFacts(identity);
|
|
1322
1348
|
const target = buildResolveTarget(spec, identity);
|
|
@@ -217,12 +217,20 @@ One object axis carries a computed form: `long-context-cost` accepts either the
|
|
|
217
217
|
|
|
218
218
|
`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).
|
|
219
219
|
|
|
220
|
-
`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. The budget applies only while `compat.officialEndpoint`
|
|
220
|
+
`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.
|
|
221
|
+
|
|
222
|
+
`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).
|
|
221
223
|
|
|
222
224
|
`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.
|
|
223
225
|
|
|
224
226
|
`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.
|
|
225
227
|
|
|
228
|
+
`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.
|
|
229
|
+
|
|
230
|
+
`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.
|
|
231
|
+
|
|
232
|
+
`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.
|
|
233
|
+
|
|
226
234
|
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`.
|
|
227
235
|
|
|
228
236
|
### Time-based pricing
|
|
@@ -280,7 +288,7 @@ The highest-ranked matching assignment wins for that axis. Two distinct rules th
|
|
|
280
288
|
|
|
281
289
|
### Capability gating
|
|
282
290
|
|
|
283
|
-
Wire axes are considered for every matching target. Thinking axes require `reasoning`, except that an exact model selector declaring `thinking-efforts` opens the gate for reviewed corrections. A winning `thinking-upgrade-neutral #true` scoped to a recognized class, family, revision, or model selector also opens it when a matching effort ladder exists; provider-wide opt-in alone does not. Materializing a neutral spec as reasoning-capable requires that opt-in and no explicit thinking vocabulary. Family and revision selectors never match targets missing that rank. An unmatched target resolves to empty maps; the cascade does not infer negative capabilities from absence.
|
|
291
|
+
Wire axes are considered for every matching target. Thinking axes require `reasoning`, except that an exact model selector declaring `thinking-efforts` opens the gate for reviewed corrections. A winning `thinking-upgrade-neutral #true` scoped to a recognized class, family, revision, or model selector also opens it when a matching effort ladder exists; provider-wide opt-in alone does not. Materializing a neutral spec as reasoning-capable requires that opt-in and no explicit thinking vocabulary. A winning `thinking-unverified #true` (scoped `class "unknown"` on hosts whose model list carries no capability metadata) instead offers an unclassified neutral spec its ladder as `ThinkingConfig.unverified`: off until the user picks an effort, and sent as non-reasoning without one; it never changes compat. Family and revision selectors never match targets missing that rank. An unmatched target resolves to empty maps; the cascade does not infer negative capabilities from absence.
|
|
284
292
|
|
|
285
293
|
## Runtime behavior grammar
|
|
286
294
|
|