modelrig 0.2.0 → 0.4.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 (81) hide show
  1. package/CHANGELOG.md +204 -0
  2. package/README.md +68 -4
  3. package/dist/adapters/deepseek.d.ts.map +1 -1
  4. package/dist/adapters/deepseek.js +8 -1
  5. package/dist/adapters/gemini.d.ts +16 -0
  6. package/dist/adapters/gemini.d.ts.map +1 -1
  7. package/dist/adapters/gemini.js +44 -3
  8. package/dist/adapters/grok.d.ts +7 -0
  9. package/dist/adapters/grok.d.ts.map +1 -1
  10. package/dist/adapters/grok.js +23 -1
  11. package/dist/adapters/openai-compat.d.ts +7 -0
  12. package/dist/adapters/openai-compat.d.ts.map +1 -1
  13. package/dist/adapters/openai-compat.js +24 -1
  14. package/dist/adapters/openai.d.ts +7 -0
  15. package/dist/adapters/openai.d.ts.map +1 -1
  16. package/dist/adapters/openai.js +23 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +5 -4
  19. package/dist/config.d.ts +7 -1
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/index.d.ts +6 -6
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +28 -25
  24. package/dist/init/init-cli.d.ts.map +1 -1
  25. package/dist/init/init-cli.js +2 -1
  26. package/dist/init/observe-cli.d.ts.map +1 -1
  27. package/dist/init/observe-cli.js +2 -1
  28. package/dist/routes/loader.d.ts +11 -0
  29. package/dist/routes/loader.d.ts.map +1 -1
  30. package/dist/routes/loader.js +29 -0
  31. package/dist/routes/sampling-config.d.ts.map +1 -1
  32. package/dist/routes/sampling-config.js +11 -0
  33. package/dist/run/dispatch.d.ts +14 -2
  34. package/dist/run/dispatch.d.ts.map +1 -1
  35. package/dist/run/dispatch.js +31 -2
  36. package/dist/run/ladder-key.d.ts +40 -0
  37. package/dist/run/ladder-key.d.ts.map +1 -0
  38. package/dist/run/ladder-key.js +39 -0
  39. package/dist/run/raw-support.d.ts +123 -0
  40. package/dist/run/raw-support.d.ts.map +1 -0
  41. package/dist/run/raw-support.js +150 -0
  42. package/dist/run/raw.d.ts +73 -1
  43. package/dist/run/raw.d.ts.map +1 -1
  44. package/dist/run/raw.js +129 -8
  45. package/dist/run/rawonly.d.ts +60 -0
  46. package/dist/run/rawonly.d.ts.map +1 -0
  47. package/dist/run/rawonly.js +120 -0
  48. package/dist/run/record-attempt.d.ts +14 -1
  49. package/dist/run/record-attempt.d.ts.map +1 -1
  50. package/dist/run/record-attempt.js +43 -2
  51. package/dist/run/resolve.d.ts +4 -0
  52. package/dist/run/resolve.d.ts.map +1 -1
  53. package/dist/run/resolve.js +9 -2
  54. package/dist/run/routing-docs.d.ts +73 -0
  55. package/dist/run/routing-docs.d.ts.map +1 -0
  56. package/dist/run/routing-docs.js +222 -0
  57. package/dist/run/run.d.ts.map +1 -1
  58. package/dist/run/run.js +24 -22
  59. package/dist/telemetry/exporter.d.ts.map +1 -1
  60. package/dist/telemetry/exporter.js +6 -0
  61. package/dist/telemetry/ingest-protocol.d.ts +7 -0
  62. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  63. package/dist/telemetry/load-error-report.d.ts.map +1 -1
  64. package/dist/telemetry/load-error-report.js +4 -1
  65. package/dist/telemetry/otel-map.d.ts.map +1 -1
  66. package/dist/telemetry/otel-map.js +8 -0
  67. package/dist/telemetry/store-migrations.d.ts +15 -0
  68. package/dist/telemetry/store-migrations.d.ts.map +1 -0
  69. package/dist/telemetry/store-migrations.js +119 -0
  70. package/dist/telemetry/store.d.ts.map +1 -1
  71. package/dist/telemetry/store.js +26 -93
  72. package/dist/types/failures-teaching.js +1 -1
  73. package/dist/types/provider.d.ts +38 -0
  74. package/dist/types/provider.d.ts.map +1 -1
  75. package/dist/types/route.d.ts +6 -1
  76. package/dist/types/route.d.ts.map +1 -1
  77. package/dist/types/telemetry.d.ts +31 -0
  78. package/dist/types/telemetry.d.ts.map +1 -1
  79. package/dist/validate-cli.d.ts.map +1 -1
  80. package/dist/validate-cli.js +17 -4
  81. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -10,6 +10,210 @@ published package diverge.
10
10
  This project adheres to [Semantic Versioning](https://semver.org/). Dates are
11
11
  UTC.
12
12
 
13
+ ## [0.4.0] — 2026-08-22
14
+
15
+ Released with the docs/registry mirror in one R-A ceremony. NOT in this tag:
16
+ the R3 declare-first `trial:` candidate flag (#239) and raw-lane artifact
17
+ steps (G1) — both land in 0.5.0.
18
+
19
+ CE-9 raw-lane multi-provider parity — the `reasoning` and native-grounding knobs
20
+ generalize beyond gemini, a `responseFormat` knob joins the raw lane, and the
21
+ provider×knob support set becomes ONE table (`RAW_KNOB_SUPPORT`) that the runtime
22
+ guard AND the published docs matrix both read, so they can never disagree. Every
23
+ knob stays ADDITIVE: absent ⇒ byte-identical to the 0.3.0 request shape.
24
+
25
+ ### Added — docs
26
+
27
+ - **Routing & reliability page** (`https://modelrig.dev/routing-reliability.html`) —
28
+ the runtime model in one concept page: resolve -> candidate ladder -> validate ->
29
+ repair -> fall-through -> exhaust, what is stored per attempt, and a **code-anchored
30
+ boundary** (`ROUTING_BOUNDARY`, one tripwire test per entry) that lists what does not
31
+ trigger fall-through and what is not built — the build fails if a listed feature ships
32
+ without the entry being updated.
33
+
34
+ ### Fixed
35
+
36
+ - **Failure-teaching links resolve.** `RigFailureError.docsUrl` (and the CLI/console
37
+ error render) now point at `routing-reliability.html#<class>` — one stable heading per
38
+ failure class — instead of the never-built `/docs/failures` path, which 404'd on
39
+ every typed failure.
40
+
41
+ ### Added — `rig.runRaw` / `rig.runRawStream`
42
+
43
+ - **`reasoning` generalizes beyond gemini WITHOUT a per-model allowlist (R1).**
44
+ openai / deepinfra / fireworks forward `level` → `reasoning_effort` verbatim
45
+ and let the provider enforce — a model that does not accept it surfaces a
46
+ CLASSIFIED, teaching failure ("this model does not accept reasoning_effort —
47
+ remove reasoning or pick a reasoning model"), never a cryptic 400 or a silent
48
+ drop. grok forwards a DISCLOSED coarsening (`minimal,low → "low"`;
49
+ `medium,high → "high"`; xAI accepts `low|high` only). gemini stays
50
+ model-gated (thinking-capable models only). deepseek + anthropic FAIL CLOSED
51
+ pre-dispatch with a provider-specific fixHint (deepseek → select the
52
+ `deepseek-reasoner` model; anthropic → extended thinking unwired).
53
+ - **Grok native grounding (R2) — WITHDRAWN before release.** The CE-9 live gate hit xAI's `410 Live search is deprecated`; the replacement `web_search` tool is Responses-API-only, which the grok adapter does not speak yet. grok grounding FAILS CLOSED (matrix `supported: false`, teaching note) until CE-9b lands that path. Original entry, kept for the record: `grounding: { mode: "native" }` on grok →
54
+ xAI Live Search (`search_parameters: { mode: "on" }`). Citations are not
55
+ surfaced on `meta` in v1 (same documented limitation as gemini).
56
+ - **`responseFormat?: "json_object"` (R4).** MIME-shaping on the OpenAI-dialect
57
+ providers (openai/deepseek/grok/deepinfra/fireworks → `response_format: { type:
58
+ "json_object" }`) and gemini (`responseMimeType: "application/json"`, no
59
+ schema). This is syntactic-JSON forcing, NOT schema enforcement — F4 stands:
60
+ no validation, no repair, `meta.validated` stays false. anthropic has no
61
+ equivalent and FAILS CLOSED. DeepSeek's word-"JSON" prompt wrinkle is the
62
+ caller's responsibility on the raw lane (documented, never injected).
63
+ - **DeepInfra/Fireworks prompt-cache hint (R3).** `cache.key` now maps to
64
+ `prompt_cache_key` on the openai-compatible adapter, closing the DeepInfra cost
65
+ gap. (openai and grok already mapped `cache.key → prompt_cache_key` on 0.3.0 —
66
+ that was a docs gap, not a code gap; it is now documented as plain fact.)
67
+
68
+ ### Added — exports (R5/R6)
69
+
70
+ - **`RAW_KNOB_SUPPORT`** (the provider×knob matrix) + **`rawKnobSupport(provider)`**
71
+ (a pure lookup) — a multi-provider dispatcher ASKS which knobs a provider
72
+ honors instead of duplicating our lists. The guard (`assertRawKnobsSupported`)
73
+ and the docs generator both import this table.
74
+ - **`GEMINI_THINKING_CAPABLE_MODELS`** — the thinking-capable set (previously
75
+ un-exported), so customers stop duplicating it (the §3.1 drift complaint).
76
+ - **`coarsenGrokReasoningEffort`** + **`renderRawKnobMatrix`** — the disclosed
77
+ grok coarsening and the docs matrix renderer.
78
+
79
+ ### Docs
80
+
81
+ - A generated provider×knob matrix on the modelrig.dev route-bundles page and in
82
+ `llms.txt`, rendered FROM `RAW_KNOB_SUPPORT` (never a hand-written twin).
83
+ - `caching-lifecycle.md`: the cache directive is provider-neutral — a resource
84
+ HANDLE on gemini, a routing HINT (`prompt_cache_key`, retention on openai) on
85
+ the OpenAI-dialect providers.
86
+
87
+ CE-8 — first-class **runRaw-only construction**. A Lane-B customer who only calls
88
+ `rig.runRaw`/`runRawStream` can now build a rig with no routes at all, so a stray
89
+ route file it never serves cannot down construction — retiring the empty-temp-dir
90
+ workaround.
91
+
92
+ ### Added
93
+
94
+ - **`RigConfig.routesDir` now accepts `null`** (was `string`). With `null`,
95
+ `createRig` SKIPS route loading, the `rig.yaml` manifest, and the
96
+ serveable-candidate gate; `runRaw`/`runRawStream` work exactly as before. The
97
+ `string` path is byte-identical to 0.3.0 (the existing serveability hard-error
98
+ on a zero-serveable route is unchanged and regression-pinned).
99
+ - **`requireRoutesDir(config, surface)`** exported — narrows `routesDir` to a
100
+ string for route-required contexts, throwing the teaching failure on `null`.
101
+ Used by `modelrig-server` and `modelrig-oracle` (both always serve routes).
102
+
103
+ ### Behaviour
104
+
105
+ - On a `routesDir: null` rig, `run`/`runStream`/`runBundle`/`runBundleStream`/
106
+ `replay`/`bakeoff`/`bakeoffFromEvalCases`/`verifySwap` throw `RigFailureError`
107
+ class `invariant_violation` (no new failure class) with the fixHint
108
+ *"this rig was constructed runRaw-only (routesDir: null); give createRig a
109
+ routesDir to serve routes"*. `rig.serveability` is `[]`.
110
+ - `loadConfigFromEnv` stays string-only (no env var disables routes, by design);
111
+ opt out in code: `createRig({ ...loadConfigFromEnv(), routesDir: null })`.
112
+
113
+ ### Docs
114
+
115
+ - README, `docs/migration-playbook.md` (Lane-B seam + C4 checkpoint), and the
116
+ generated `llms.txt` gain the runRaw-only construction snippet, the `apiKey`
117
+ BYOK line on every `runRaw` example, the combined-prompt → `userPrompt` note,
118
+ and the note that the cache-ratio-vs-baseline metric is a REAL-run measurement
119
+ (deferred-to-first-run at the checkpoint), not a probe.
120
+
121
+ R0 — the **per-call telemetry ladder key** (routing-console spec §2.1). Every
122
+ inference row now records which call it belongs to and its position in the
123
+ routing ladder, so a dashboard can reconstruct the exact sequence one `rig.run`
124
+ walked and compute fallback / repair rates.
125
+
126
+ ### Added — telemetry ladder key
127
+
128
+ - **`RunMeta.callId`** — the `call_id` shared by every telemetry row a single
129
+ `run` / `runStream` / `runBundle` / `runRaw` invocation writes (primary, retry,
130
+ repair, grounding-search, tool-call), so a caller can correlate a `RunResult`
131
+ with its ladder in the console. Additive & OPTIONAL — a pre-0.4.0 caller's meta
132
+ omits it.
133
+ - Telemetry rows gain **`call_id`**, **`attempt_seq`** (1-based, monotonic across
134
+ every row of the call), and **`candidate_index`** (position of the dispatched
135
+ candidate in the call's EFFECTIVE declared list — after variant narrowing and
136
+ per-run filters; a repair row carries the repaired candidate's index; the raw
137
+ lane is seq 1 / index 0). All three are OPTIONAL on the wire and nullable in
138
+ storage: an older SDK OMITS them and the payload is byte-identical to before
139
+ R0; a reader treats null as *"recorded by an SDK older than 0.4.0."*
140
+ - OTLP spans gain `modelrig.call_id` / `modelrig.attempt_seq` /
141
+ `modelrig.candidate_index`, emitted only when present.
142
+ - **`CandidateIterator.index`** (read-only getter, public type) — the index of the
143
+ candidate the last `next()` returned (−1 before the first call). Additive for
144
+ callers; an external IMPLEMENTER of the interface must add the member.
145
+ - **`tags.rung`** on every telemetry row (R1 review, F1) — one of `primary` |
146
+ `retry` | `repair` | `search` | `tool_call` | `predispatch_fail`, so the routing
147
+ dashboard and ladder view can tell a real candidate dispatch from a tool-call
148
+ turn or a pre-dispatch failure without column heuristics. Additive (rides
149
+ `tags_json`, no schema change); a pre-0.4.0 row omits it and the rollup falls
150
+ back to `repaired_by` / `provider = "search"`. Public type `TelemetryRung` is
151
+ exported.
152
+
153
+ ## [0.3.0] — 2026-08-21
154
+
155
+ The `modelrig-oracle` MCP server ships to npm alongside this release (its own
156
+ package, 0.1.0) — `npx modelrig-oracle` now works as the docs describe.
157
+
158
+ CE-2 raw-lane parity — three new provider knobs on `rig.runRaw` /
159
+ `rig.runRawStream` (and the OpenAI-compatible `/v1/chat/completions` raw lane),
160
+ so the Lane-B report-pipeline seam (`callProvider` → `rig.runRaw`) can swap
161
+ behaviour-identically. Every knob is ADDITIVE: absent ⇒ byte-identical to the
162
+ 0.2.0 request shape.
163
+
164
+ ### Added — `RawRunInput` (all additive, absent ⇒ byte-identical)
165
+
166
+ - **`grounding?: { mode: "native" }`** — native provider web grounding on the
167
+ raw lane (F1). Maps to the adapter's native-grounding directive (gemini →
168
+ the `googleSearch` tool). GEMINI-ONLY in v1: declared on any other provider
169
+ throws `RigFailureError` with class `invariant_violation` PRE-DISPATCH (no
170
+ meter, no provider call, no telemetry row). Interplay with `cache.key` needs
171
+ no code — the gemini `cachedContent` path excludes tools by design (the
172
+ `googleSearch` tool is expected to be baked into the customer's cache resource
173
+ at creation).
174
+ - **`serviceTier?: "standard" | "flex" | "priority"`** — service tier on the raw
175
+ lane (F2). Forwarded to the adapter's tier path; `RunMeta.requestedTier` now
176
+ reports the DECLARED tier (previously hardcoded `"standard"` — an honesty fix).
177
+ ModelRig does NOT degrade flex→standard on the raw lane; a capacity rejection
178
+ surfaces classified (`capacity_shed`) for the customer's own retry ladder.
179
+ - **`reasoning?: { level: "minimal" | "low" | "medium" | "high" }`** —
180
+ provider-neutral reasoning/thinking effort (F3). Gemini maps `level` to
181
+ `thinkingConfig.thinkingLevel` (replacing the hardcoded `"high"`). GEMINI-ONLY
182
+ in v1: declared on another provider throws `invariant_violation` pre-dispatch;
183
+ declared on a non-thinking-capable gemini model is rejected classified by the
184
+ adapter (never a silent drop, never an unguarded provider 400).
185
+
186
+ ### Added — `AdapterRequest`
187
+
188
+ - **`reasoning?: ReasoningParams`** (+ exported `ReasoningParams` /
189
+ `ReasoningLevel` types) — the provider-neutral reasoning shape adapters map to
190
+ their native field.
191
+
192
+ ### Added — HTTP gateway (`/v1/chat/completions`, raw lane only)
193
+
194
+ - Top-level OpenAI-standard `temperature` / `top_p` / `max_tokens` /
195
+ `service_tier` are now forwarded onto the raw lane (the open CE-6 co-item);
196
+ `modelrig.grounding` / `modelrig.reasoning` ride in the `modelrig` body object.
197
+ A malformed `grounding.mode` / `reasoning.level` is a teaching 4xx. The SLUG
198
+ lane still ignores all generation params (the verified route owns them).
199
+
200
+ ### Added — route loader
201
+
202
+ - Load-time WARN when a route's output schema has an ARRAY root — OpenAI strict
203
+ structured output requires a top-level object, so an array-root schema cannot
204
+ use strict enforcement on OpenAI candidates (the A5 co-item). The route still
205
+ loads and serves as declared.
206
+
207
+ ### Confirmed (already true on 0.2.0, now pinned by regression tests)
208
+
209
+ - **F5** — an invalid/evicted customer `cache.key` on the raw lane surfaces as a
210
+ thrown `RigFailureError` with `.failure.class === "cache_invalid"`; the raw
211
+ lane is single-dispatch, so a silent un-cached retry is structurally
212
+ impossible.
213
+ - **F6** — success `RunMeta` carries `servedTier` / `tokensCached` / `latencyMs`
214
+ (and provider/model/cost); a per-attempt failure's class + provider/model
215
+ travel on the thrown error's `.failure`.
216
+
13
217
  ## [0.2.0] — unreleased (staged for the R-A ceremony)
14
218
 
15
219
  - Ships the probed capability registry and leaderboard (`registry/registry.json`, `registry/leaderboard.json`) so a clean install routes on the probed profile instead of the static fallback (R-1 F2 ruling). The `modelrig-oracle` MCP server remains unreleased and is flagged as such in the docs.
package/README.md CHANGED
@@ -99,15 +99,75 @@ over the route's `cache: auto` stamp, and a non-matching fallback dispatches
99
99
  without it. ModelRig carries the handle and prices the hits — it never creates,
100
100
  refreshes, or deletes the resource, so a long job needs your own TTL heartbeat
101
101
  (see the [caching lifecycle](https://modelrig.dev/caching-lifecycle.html) doc).
102
- The Lane-B `rig.runRaw({ provider, model, …, cache: { key } })` seam takes the
103
- same field.
102
+ The Lane-B `rig.runRaw({ provider, model, apiKey, …, cache: { key } })` seam takes
103
+ the same field.
104
+
105
+ `rig.runRaw` also accepts
106
+ provider knobs, each additive — absent ⇒ byte-identical dispatch:
107
+ `grounding: { mode: "native" }` (native provider web grounding —
108
+ gemini; grok fail-closed until CE-9b), `serviceTier: "standard" | "flex" | "priority"`,
109
+ `reasoning: { level: "minimal" | "low" | "medium" | "high" }` (reasoning effort¹),
110
+ and `responseFormat: "json_object"` (syntactic-JSON forcing¹ — MIME-shaping, NOT
111
+ schema enforcement, so `meta.validated` stays `false`). A declared directive a
112
+ provider cannot honor FAILS CLOSED — a thrown `RigFailureError` with class
113
+ `invariant_violation`, pre-dispatch (no meter, no provider call) — never a silent
114
+ drop. **Which provider honors which knob is one table** — the runtime guard and
115
+ the published
116
+ [provider × knob matrix](https://modelrig.dev/route-bundles.html#raw-lane-provider-knob-support-rigrunraw)
117
+ are generated from the same `RAW_KNOB_SUPPORT`; ask it in code with
118
+ `rawKnobSupport(provider)` (and `GEMINI_THINKING_CAPABLE_MODELS` is exported so
119
+ you don't duplicate the thinking-model set). A grounded step that also passes a
120
+ `cache.key` keeps its cached-token ratio: bake the `googleSearch` tool into the
121
+ cache resource at creation (ModelRig sends `cachedContent` without tools, which
122
+ the provider requires).
123
+
124
+ _¹ Multi-provider `reasoning` and `responseFormat` ship in
125
+ 0.4.0; on the published package `reasoning`/`grounding` are gemini-only._
126
+
127
+ **runRaw — a BYOK passthrough (Lane B).** When you don't need a route — no
128
+ schema, no validation, no retries — call a `provider/model` directly with your
129
+ own provider key:
130
+
131
+ ```ts
132
+ const rig = createRig(loadConfigFromEnv());
133
+ const result = await rig.runRaw({
134
+ provider: "gemini",
135
+ model: "gemini-3.1-pro",
136
+ apiKey: process.env.GEMINI_API_KEY!, // runRaw is BYOK — pass your provider key
137
+ systemPrompt: "", // one combined prompt? put it ALL in userPrompt, leave systemPrompt empty
138
+ userPrompt: buildPrompt(input),
139
+ });
140
+ ```
141
+
142
+ `runRaw` is BYOK — pass your provider key; ModelRig holds it for the one call and
143
+ never stores it. It records one metered `lane=raw` telemetry row at the
144
+ provider's list price (zero ModelRig margin) and throws a typed `RigFailureError`
145
+ on failure, exactly like `rig.run`.
146
+
147
+ **runRaw-only construction (`routesDir: null`) — 0.4.0+.** A
148
+ Lane-B deployment that ONLY calls `runRaw`/`runRawStream` has no routes. Build it
149
+ with `routesDir: null` and `createRig` skips route loading, the `rig.yaml`
150
+ manifest, and the serveability gate entirely — so a stray route file it never
151
+ calls (or a route whose provider key is absent in this deployment) can no longer
152
+ down construction:
153
+
154
+ ```ts
155
+ const rig = createRig({ ...loadConfigFromEnv(), routesDir: null });
156
+ await rig.runRaw({ provider, model, apiKey, systemPrompt, userPrompt });
157
+ ```
158
+
159
+ `loadConfigFromEnv()` always yields a string `routesDir`, so the opt-out is a
160
+ deliberate code-level choice — override it as above; there is no env var that
161
+ disables routes, by design. On such a rig, `rig.run` and every other
162
+ route-dependent surface (`runStream`, `runBundle`, bake-offs, watch, replay,
163
+ `verifySwap`) throws a terminal `invariant_violation` naming the fix.
104
164
 
105
165
  Every attempt is recorded in a local SQLite file (`.modelrig/telemetry.db`)
106
166
  tagged with your `tags`. Failures are typed —
107
167
  `RigFailureError.failure.class` is one of `content_invalid`, `capacity_shed`,
108
- `network`, `refusal`, `cache_invalid`, `timeout`, `budget_exhausted`,
168
+ `network`, `refusal`, `cache_invalid`, `timeout`, `config_auth`, `budget_exhausted`,
109
169
  `invariant_violation` — with a separate retry budget per class, so a flaky
110
- network never spends your content-validation retries.
170
+ network never spends your content-validation retries. What each class means, whether it retries, and how it backs off is on [Routing & reliability](https://modelrig.dev/routing-reliability.html#5-failure-classes).
111
171
 
112
172
  ## CLI
113
173
 
@@ -143,6 +203,10 @@ on the quickstart:
143
203
  stable enough to build on, not frozen.
144
204
  - **`modelrig serve` needs the `modelrig-server` package** — it is not bundled
145
205
  here.
206
+ - **What the ladder does and does not do.** Fall-through is structural and
207
+ infrastructural, never a quality judgment; the full boundary (quality gate,
208
+ raw-lane structure, latency ordering…) is generated and tripwire-tested on
209
+ [Routing & reliability](https://modelrig.dev/routing-reliability.html#10-not-yet-implemented-the-boundary).
146
210
 
147
211
  ## License
148
212
 
@@ -1 +1 @@
1
- {"version":3,"file":"deepseek.d.ts","sourceRoot":"","sources":["../../src/adapters/deepseek.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAIV,cAAc,EACd,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAMhE,OAAO,KAAK,EAAuB,cAAc,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAMhG,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAKD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAMhF;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAiB1E;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE;QAAE,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAA;KAAE,CAAC;IAC1D,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,eAAe,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;KAAE,CAAC;IAC5D,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAOD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,oBAAoB,CA2B1F;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,eAAe,CA8JpF"}
1
+ {"version":3,"file":"deepseek.d.ts","sourceRoot":"","sources":["../../src/adapters/deepseek.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAIV,cAAc,EACd,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAMhE,OAAO,KAAK,EAAuB,cAAc,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAMhG,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAKD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAMhF;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAiB1E;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE;QAAE,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAA;KAAE,CAAC;IAC1D,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,eAAe,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;KAAE,CAAC;IAC5D,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAOD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,oBAAoB,CAkC1F;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,eAAe,CA8JpF"}
@@ -62,6 +62,13 @@ function classifyDeepSeekFailure(message) {
62
62
  const JSON_GUARD = "Respond with a single valid JSON object.";
63
63
  function buildDeepSeekParams(model, req) {
64
64
  const wantJson = req.outputSchema !== null;
65
+ // CE-9 R4: the raw lane's `responseFormat: "json_object"` also drives DeepSeek's
66
+ // json_object mode (the flag previously fired ONLY on the routed outputSchema
67
+ // path, which is always null on the raw lane). Absent both ⇒ no key
68
+ // (byte-identical). The JSON_GUARD below stays keyed on the ROUTED path only:
69
+ // on the raw lane the word-"JSON" wrinkle is the caller's prompt responsibility
70
+ // (spec R4), not silently intercepted here.
71
+ const jsonObject = wantJson || req.responseFormat === "json_object";
65
72
  let systemPrompt = req.systemPrompt;
66
73
  if (wantJson && !req.systemPrompt.includes("JSON") && !req.userPrompt.includes("JSON")) {
67
74
  systemPrompt = systemPrompt === "" ? JSON_GUARD : `${systemPrompt}\n\n${JSON_GUARD}`;
@@ -77,7 +84,7 @@ function buildDeepSeekParams(model, req) {
77
84
  stream: true,
78
85
  stream_options: { include_usage: true },
79
86
  ...(0, sampling_1.openAISamplingFields)(req),
80
- ...(wantJson ? { response_format: { type: "json_object" } } : {}),
87
+ ...(jsonObject ? { response_format: { type: "json_object" } } : {}),
81
88
  ...(req.tools !== undefined && req.tools.length > 0
82
89
  ? {
83
90
  tools: (0, tool_mapping_1.toOpenAITools)(req.tools),
@@ -18,6 +18,12 @@ import type { FailureClass } from "../types/failures";
18
18
  export interface GeminiAdapterConfig {
19
19
  readonly apiKey: string;
20
20
  }
21
+ /** Models that accept thinkingConfig — passing it to 2.x models 400s.
22
+ * Ported from the production THINKING_CAPABLE_MODELS set. EXPORTED (CE-9 R6) so
23
+ * a multi-provider customer's dispatcher reads THIS set instead of duplicating
24
+ * it (the §3.1 drift complaint) — the same fact the raw-lane guard uses to keep
25
+ * gemini reasoning model-gated. */
26
+ export declare const GEMINI_THINKING_CAPABLE_MODELS: ReadonlySet<string>;
21
27
  export declare function geminiSupports(feature: CapabilityFlag, model: string): boolean;
22
28
  export interface ClassifiedFailure {
23
29
  readonly cls: FailureClass;
@@ -33,6 +39,16 @@ export declare function classifyGeminiFailure(message: string): ClassifiedFailur
33
39
  /** The candidate-independent part of an AdapterRequest (also what the pure
34
40
  * shaping helpers take, so unit tests never need a branded CandidateRef). */
35
41
  export type ShapingInput = Omit<AdapterRequest, "candidate">;
42
+ /**
43
+ * CE-2 F3 fail-closed guard: `reasoning` declared on a Gemini model that is NOT
44
+ * thinking-capable would 400 at the provider (thinkingConfig on a 2.x model is
45
+ * rejected). Pre-empt with a classified TERMINAL failure so the declaration is
46
+ * never silently dropped and never turns into an unguarded provider 400 — the
47
+ * same fail-closed discipline as the raw-lane provider guard (spec §3 F1/F3).
48
+ * Returns the failure class + fixHint, or null when the request is fine (no
49
+ * reasoning declared, or a thinking-capable model). Pure — unit-tested directly.
50
+ */
51
+ export declare function geminiReasoningUnsupported(model: string, req: ShapingInput): ClassifiedFailure | null;
36
52
  export interface GeminiShapedRequest {
37
53
  readonly model: string;
38
54
  readonly contents: string;
@@ -1 +1 @@
1
- {"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/adapters/gemini.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EACV,cAAc,EAGd,cAAc,EACd,eAAe,EAEhB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,mBAAmB,CAAC;AAKlE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAkCD,wBAAgB,cAAc,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAG9E;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAyCxE;AAED;6EAC6E;AAC7E,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;AAE7D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C;AAED,iEAAiE;AACjE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,mBAAmB,CA2CxF;AA0BD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,eAAe,CAmJhF"}
1
+ {"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/adapters/gemini.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EACV,cAAc,EAGd,cAAc,EACd,eAAe,EAEhB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,mBAAmB,CAAC;AAKlE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;mCAImC;AACnC,eAAO,MAAM,8BAA8B,EAAE,WAAW,CAAC,MAAM,CAI7D,CAAC;AA0BH,wBAAgB,cAAc,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAG9E;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAyCxE;AAED;6EAC6E;AAC7E,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;AAE7D;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,iBAAiB,GAAG,IAAI,CAQrG;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C;AAED,iEAAiE;AACjE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,mBAAmB,CAqDxF;AA0BD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,eAAe,CA+JhF"}
@@ -15,16 +15,21 @@
15
15
  * gemini transformation logic is consulted during the port.
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.GEMINI_THINKING_CAPABLE_MODELS = void 0;
18
19
  exports.geminiSupports = geminiSupports;
19
20
  exports.classifyGeminiFailure = classifyGeminiFailure;
21
+ exports.geminiReasoningUnsupported = geminiReasoningUnsupported;
20
22
  exports.buildGeminiRequest = buildGeminiRequest;
21
23
  exports.createGeminiAdapter = createGeminiAdapter;
22
24
  const genai_1 = require("@google/genai");
23
25
  const tool_mapping_1 = require("./tool-mapping");
24
26
  const sampling_1 = require("./sampling");
25
27
  /** Models that accept thinkingConfig — passing it to 2.x models 400s.
26
- * Ported from the production THINKING_CAPABLE_MODELS set. */
27
- const THINKING_CAPABLE_MODELS = new Set([
28
+ * Ported from the production THINKING_CAPABLE_MODELS set. EXPORTED (CE-9 R6) so
29
+ * a multi-provider customer's dispatcher reads THIS set instead of duplicating
30
+ * it (the §3.1 drift complaint) — the same fact the raw-lane guard uses to keep
31
+ * gemini reasoning model-gated. */
32
+ exports.GEMINI_THINKING_CAPABLE_MODELS = new Set([
28
33
  "gemini-3.1-pro-preview",
29
34
  "gemini-3.0-pro-preview",
30
35
  "gemini-3.1-flash-preview",
@@ -99,23 +104,52 @@ function classifyGeminiFailure(message) {
99
104
  }
100
105
  return { cls: "network" };
101
106
  }
107
+ /**
108
+ * CE-2 F3 fail-closed guard: `reasoning` declared on a Gemini model that is NOT
109
+ * thinking-capable would 400 at the provider (thinkingConfig on a 2.x model is
110
+ * rejected). Pre-empt with a classified TERMINAL failure so the declaration is
111
+ * never silently dropped and never turns into an unguarded provider 400 — the
112
+ * same fail-closed discipline as the raw-lane provider guard (spec §3 F1/F3).
113
+ * Returns the failure class + fixHint, or null when the request is fine (no
114
+ * reasoning declared, or a thinking-capable model). Pure — unit-tested directly.
115
+ */
116
+ function geminiReasoningUnsupported(model, req) {
117
+ if (req.reasoning !== undefined && !exports.GEMINI_THINKING_CAPABLE_MODELS.has(model)) {
118
+ return {
119
+ cls: "invariant_violation",
120
+ fixHint: `reasoning control requires a thinking-capable Gemini model; "${model}" is not one — remove reasoning or choose a thinking-capable model`,
121
+ };
122
+ }
123
+ return null;
124
+ }
102
125
  /** Pure request shaping — everything except the abort signal. */
103
126
  function buildGeminiRequest(model, req) {
104
127
  const usingCache = req.cache?.key !== undefined && req.cache.key !== "";
105
128
  // CE-6: a declared sampling field OVERRIDES the ported constant; absent, the
106
129
  // constant stands (byte-identical to pre-CE-6). topK stays internal (gemini-only,
107
130
  // not on the customer surface — spec §1 non-goals).
131
+ // CE-2 F3: a declared reasoning level replaces the hardcoded `"high"` for the
132
+ // thinking-capable models; absent, `"high"` stands (byte-identical to
133
+ // pre-CE-2). A declared level on a NON-capable model is rejected fail-closed in
134
+ // call() (geminiReasoningUnsupported) — it never reaches this shaping.
135
+ const thinkingLevel = req.reasoning?.level ?? "high";
108
136
  const config = {
109
137
  temperature: req.sampling?.temperature ?? GEMINI_TEMPERATURE,
110
138
  maxOutputTokens: (0, sampling_1.effectiveMaxOutputTokens)(req, DEFAULT_MAX_OUTPUT_TOKENS),
111
139
  topP: req.sampling?.topP ?? TOP_P,
112
140
  topK: TOP_K,
113
- ...(THINKING_CAPABLE_MODELS.has(model) ? { thinkingConfig: { thinkingLevel: "high" } } : {}),
141
+ ...(exports.GEMINI_THINKING_CAPABLE_MODELS.has(model) ? { thinkingConfig: { thinkingLevel } } : {}),
114
142
  };
115
143
  if (req.outputSchema !== null) {
116
144
  config.responseMimeType = "application/json";
117
145
  config.responseJsonSchema = req.outputSchema;
118
146
  }
147
+ else if (req.responseFormat === "json_object") {
148
+ // CE-9 R4: mime-shaping ONLY — force a JSON mime type WITHOUT a schema. This
149
+ // is the raw lane, so F4 stands: no responseJsonSchema, no validation, no
150
+ // repair, `validated` stays false. Absent ⇒ no key (byte-identical).
151
+ config.responseMimeType = "application/json";
152
+ }
119
153
  // cachedContent excludes systemInstruction and tools — the API rejects
120
154
  // requests setting both (they are baked into the cache at creation time,
121
155
  // including the googleSearch tool for cached grounded steps).
@@ -179,6 +213,13 @@ function createGeminiAdapter(config) {
179
213
  },
180
214
  ...(usage !== undefined ? { usage } : {}),
181
215
  });
216
+ // CE-2 F3: reject reasoning on a non-thinking model BEFORE any dispatch —
217
+ // classified, terminal, never a silent drop (spec §3 F3).
218
+ const reasoningUnsupported = geminiReasoningUnsupported(model, req);
219
+ if (reasoningUnsupported !== null) {
220
+ clearTimeout(timeoutId);
221
+ return fail(reasoningUnsupported.cls, `reasoning control declared on the non-thinking Gemini model "${model}"`, reasoningUnsupported.fixHint);
222
+ }
182
223
  try {
183
224
  const shaped = buildGeminiRequest(model, req);
184
225
  const dispatchedAt = Date.now();
@@ -36,7 +36,14 @@ export interface GrokShapedParams {
36
36
  /** Prompt-cache routing hint (WS-C) — the stable-prefix fingerprint the run
37
37
  * loop stamps; xAI keeps a warm cache keyed to it (writes free today). */
38
38
  readonly prompt_cache_key?: string;
39
+ /** CE-9 R1 — xAI accepts `reasoning_effort: "low" | "high"` ONLY, so the
40
+ * neutral four-level `level` is coarsened by `coarsenGrokReasoningEffort`
41
+ * (a DISCLOSED, lossy mapping: `minimal,low → "low"`; `medium,high → "high"`).
42
+ * Present only when the caller declared `reasoning`. */
43
+ readonly reasoning_effort?: "low" | "high";
39
44
  readonly response_format?: {
45
+ readonly type: "json_object";
46
+ } | {
40
47
  readonly type: "json_schema";
41
48
  readonly json_schema: {
42
49
  readonly name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"grok.d.ts","sourceRoot":"","sources":["../../src/adapters/grok.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAIV,cAAc,EACd,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAMhE,OAAO,KAAK,EAAuB,cAAc,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAMhG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAKD,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAc7E;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAsBtE;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE;QAAE,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAA;KAAE,CAAC;IAC1D,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;8EAC0E;IAC1E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,eAAe,CAAC,EAAE;QACzB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;QAC7B,QAAQ,CAAC,WAAW,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;SAAE,CAAC;KACjG,CAAC;IACF,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,gBAAgB,CAsClF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,eAAe,CAgK5E"}
1
+ {"version":3,"file":"grok.d.ts","sourceRoot":"","sources":["../../src/adapters/grok.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAIV,cAAc,EACd,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAMhE,OAAO,KAAK,EAAuB,cAAc,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAOhG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAKD,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAc7E;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CA+BtE;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE;QAAE,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAA;KAAE,CAAC;IAC1D,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;8EAC0E;IAC1E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;4DAGwD;IACxD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC3C,QAAQ,CAAC,eAAe,CAAC,EACrB;QAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;KAAE,GAChC;QACE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;QAC7B,QAAQ,CAAC,WAAW,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;SAAE,CAAC;KACjG,CAAC;IACN,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,gBAAgB,CAmDlF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,eAAe,CAgK5E"}
@@ -23,6 +23,7 @@ const openai_1 = __importDefault(require("openai"));
23
23
  const tool_mapping_1 = require("./tool-mapping");
24
24
  const openai_stream_1 = require("./openai-stream");
25
25
  const sampling_1 = require("./sampling");
26
+ const raw_support_1 = require("../run/raw-support");
26
27
  const GROK_BASE_URL = "https://api.x.ai/v1";
27
28
  const DEFAULT_MAX_TOKENS = 32000;
28
29
  function grokSupports(feature, _model) {
@@ -65,6 +66,14 @@ function classifyGrokFailure(message) {
65
66
  fixHint: "the candidate model was not found — fix the model id in the route's candidate list",
66
67
  };
67
68
  }
69
+ // CE-9 R1: a non-reasoning grok variant rejects `reasoning_effort` — teach the
70
+ // fix (pick a reasoning variant) rather than surface a cryptic 400.
71
+ if (/reasoning_effort/.test(msg)) {
72
+ return {
73
+ cls: "network",
74
+ fixHint: "this Grok model does not accept reasoning_effort — remove reasoning or pick a Grok reasoning variant",
75
+ };
76
+ }
68
77
  return { cls: "network" };
69
78
  }
70
79
  function buildGrokParams(model, req) {
@@ -84,6 +93,17 @@ function buildGrokParams(model, req) {
84
93
  ...(req.cache?.key !== undefined && req.cache.key !== ""
85
94
  ? { prompt_cache_key: req.cache.key }
86
95
  : {}),
96
+ // CE-9 R1: coarsen the neutral level to xAI's low|high (disclosed mapping).
97
+ // Absent ⇒ no key. A non-reasoning grok model's rejection is taught below.
98
+ ...(req.reasoning !== undefined
99
+ ? { reasoning_effort: (0, raw_support_1.coarsenGrokReasoningEffort)(req.reasoning.level) }
100
+ : {}),
101
+ // CE-9b: grounding is NOT mapped here. xAI retired Live Search
102
+ // (`search_parameters` → HTTP 410 on the CE-9 live gate); the replacement
103
+ // `web_search` tool is Responses-API-only. The raw-lane guard fails grok
104
+ // grounding closed (matrix: supported=false) until CE-9b adds that path.
105
+ // response_format: a native strict json_schema (routed lane) wins; else the
106
+ // RAW lane's mime-shaping json_object (CE-9 R4). Absent both ⇒ no key.
87
107
  ...(req.outputSchema !== null
88
108
  ? {
89
109
  response_format: {
@@ -95,7 +115,9 @@ function buildGrokParams(model, req) {
95
115
  },
96
116
  },
97
117
  }
98
- : {}),
118
+ : req.responseFormat === "json_object"
119
+ ? { response_format: { type: "json_object" } }
120
+ : {}),
99
121
  ...(req.tools !== undefined && req.tools.length > 0
100
122
  ? {
101
123
  tools: (0, tool_mapping_1.toOpenAITools)(req.tools),
@@ -49,6 +49,13 @@ export interface OpenAICompatShapedParams {
49
49
  /** CE-6 declared sampling — present only when the route/raw asks for them. */
50
50
  readonly temperature?: number;
51
51
  readonly top_p?: number;
52
+ /** CE-9 R1 — reasoning effort forwarded verbatim (1:1). Present only when the
53
+ * caller declared `reasoning`; a host/model that does not accept it rejects at
54
+ * the provider and `classifyCompatFailure` teaches (no per-model allowlist). */
55
+ readonly reasoning_effort?: "minimal" | "low" | "medium" | "high";
56
+ /** CE-9 R3 — the DeepInfra prompt_cache_key gap: `cache.key` → the routing
57
+ * hint. Present only when the caller supplies `cache.key`. */
58
+ readonly prompt_cache_key?: string;
52
59
  readonly response_format?: {
53
60
  readonly type: "json_object";
54
61
  } | {
@@ -1 +1 @@
1
- {"version":3,"file":"openai-compat.d.ts","sourceRoot":"","sources":["../../src/adapters/openai-compat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAIV,cAAc,EACd,eAAe,EACf,UAAU,EACX,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAMhE,OAAO,KAAK,EAAuB,cAAc,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAQhG;;8CAE8C;AAC9C,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACnD;AAED;;;0EAG0E;AAC1E,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,WAAW,GAAG,WAAW,EAAE,gBAAgB,CAAC,CAG7F,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAiBtF;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE;QAAE,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAA;KAAE,CAAC;IAC1D,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,eAAe,CAAC,EACrB;QAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;KAAE,GAChC;QACE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;QAC7B,QAAQ,CAAC,WAAW,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;KACjG,CAAC;IACN,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAOD;;2DAE2D;AAC3D,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,YAAY,EACjB,YAAY,EAAE,WAAW,CAAC,cAAc,CAAC,GACxC,wBAAwB,CAqC1B;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD;+DAC2D;IAC3D,QAAQ,CAAC,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,WAAW,CAAC,cAAc,CAAC,CAAC;CAC5E;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,yBAAyB,GAAG,eAAe,CAwK5F"}
1
+ {"version":3,"file":"openai-compat.d.ts","sourceRoot":"","sources":["../../src/adapters/openai-compat.ts"],"names":[],"mappings":"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"}
@@ -66,6 +66,15 @@ function classifyCompatFailure(message, host) {
66
66
  fixHint: "the candidate model was not found — fix the model id in the route's candidate list",
67
67
  };
68
68
  }
69
+ // CE-9 R1: a model that does not accept `reasoning_effort` rejects the request
70
+ // — teach it rather than surfacing a cryptic generic 400 (the reason we forward
71
+ // verbatim instead of keeping a per-model allowlist).
72
+ if (/reasoning_effort/.test(msg)) {
73
+ return {
74
+ cls: "network",
75
+ fixHint: `this ${host} model does not accept reasoning_effort — remove reasoning or pick a reasoning model`,
76
+ };
77
+ }
69
78
  return { cls: "network" };
70
79
  }
71
80
  /** json_object mode 400s on several compat backends when the literal word
@@ -93,6 +102,18 @@ function buildOpenAICompatParams(model, req, profileFlags) {
93
102
  stream: true,
94
103
  stream_options: { include_usage: true },
95
104
  ...(0, sampling_1.openAISamplingFields)(req),
105
+ // CE-9 R1: forward reasoning effort verbatim (1:1). Absent ⇒ no key.
106
+ ...(req.reasoning !== undefined ? { reasoning_effort: req.reasoning.level } : {}),
107
+ // CE-9 R3: the DeepInfra prompt_cache_key gap — map cache.key → the routing
108
+ // hint (absent ⇒ no key, byte-identical). Additive; unrecognized on a host
109
+ // that doesn't cache, the hint is simply ignored.
110
+ ...(req.cache?.key !== undefined && req.cache.key !== ""
111
+ ? { prompt_cache_key: req.cache.key }
112
+ : {}),
113
+ // response_format: a routed strict json_schema (structured_native profile)
114
+ // wins; else routed json_object; else the RAW lane's mime-shaping json_object
115
+ // (CE-9 R4). The raw path carries NO JSON_GUARD injection — the word-"JSON"
116
+ // wrinkle is the caller's prompt responsibility on the raw lane.
96
117
  ...(wantJson
97
118
  ? strict
98
119
  ? {
@@ -102,7 +123,9 @@ function buildOpenAICompatParams(model, req, profileFlags) {
102
123
  },
103
124
  }
104
125
  : { response_format: { type: "json_object" } }
105
- : {}),
126
+ : req.responseFormat === "json_object"
127
+ ? { response_format: { type: "json_object" } }
128
+ : {}),
106
129
  ...(req.tools !== undefined && req.tools.length > 0
107
130
  ? {
108
131
  tools: (0, tool_mapping_1.toOpenAITools)(req.tools),
@@ -38,7 +38,14 @@ export interface OpenAIShapedParams {
38
38
  readonly service_tier?: "flex" | "priority";
39
39
  readonly prompt_cache_key?: string;
40
40
  readonly prompt_cache_retention?: "24h";
41
+ /** CE-9 R1 — reasoning effort forwarded verbatim (1:1 from the neutral
42
+ * `level`). Present only when the caller declared `reasoning`. A model that
43
+ * does not accept it rejects at the provider; `classifyOpenAIFailure` turns
44
+ * that 400 into a teaching failure (no per-model allowlist). */
45
+ readonly reasoning_effort?: "minimal" | "low" | "medium" | "high";
41
46
  readonly response_format?: {
47
+ readonly type: "json_object";
48
+ } | {
42
49
  readonly type: "json_schema";
43
50
  readonly json_schema: {
44
51
  readonly name: string;