modelrig 0.9.0 → 0.11.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 (231) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/README.md +51 -27
  3. package/data/PROVENANCE.md +14 -0
  4. package/data/pricing-map.json +26 -0
  5. package/dist/adapters/attempt-meta-map.d.ts +8 -1
  6. package/dist/adapters/attempt-meta-map.d.ts.map +1 -1
  7. package/dist/adapters/attempt-meta-map.js +4 -0
  8. package/dist/adapters/build-adapters.js +2 -2
  9. package/dist/adapters/gemini.d.ts +28 -1
  10. package/dist/adapters/gemini.d.ts.map +1 -1
  11. package/dist/adapters/gemini.js +38 -9
  12. package/dist/adapters/grok.d.ts.map +1 -1
  13. package/dist/adapters/grok.js +53 -4
  14. package/dist/adapters/openai-compat.d.ts +8 -2
  15. package/dist/adapters/openai-compat.d.ts.map +1 -1
  16. package/dist/adapters/openai-compat.js +8 -1
  17. package/dist/adapters/openai-stream.d.ts +10 -2
  18. package/dist/adapters/openai-stream.d.ts.map +1 -1
  19. package/dist/adapters/openai-stream.js +5 -1
  20. package/dist/adapters/openai.d.ts +10 -1
  21. package/dist/adapters/openai.d.ts.map +1 -1
  22. package/dist/adapters/openai.js +57 -7
  23. package/dist/adapters/responses-api.d.ts +61 -0
  24. package/dist/adapters/responses-api.d.ts.map +1 -0
  25. package/dist/adapters/responses-api.js +124 -0
  26. package/dist/artifacts/artifact.d.ts +20 -0
  27. package/dist/artifacts/artifact.d.ts.map +1 -1
  28. package/dist/artifacts/artifact.js +207 -171
  29. package/dist/artifacts/index.d.ts +3 -1
  30. package/dist/artifacts/index.d.ts.map +1 -1
  31. package/dist/artifacts/index.js +8 -1
  32. package/dist/bakeoff/groundedness.d.ts +58 -0
  33. package/dist/bakeoff/groundedness.d.ts.map +1 -0
  34. package/dist/bakeoff/groundedness.js +165 -0
  35. package/dist/bakeoff/quality.d.ts +14 -1
  36. package/dist/bakeoff/quality.d.ts.map +1 -1
  37. package/dist/bakeoff/quality.js +10 -0
  38. package/dist/bakeoff/replay.d.ts.map +1 -1
  39. package/dist/bakeoff/replay.js +4 -0
  40. package/dist/cli.d.ts.map +1 -1
  41. package/dist/cli.js +8 -0
  42. package/dist/config.d.ts +31 -0
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/config.js +23 -0
  45. package/dist/doctor-cli.d.ts +7 -0
  46. package/dist/doctor-cli.d.ts.map +1 -0
  47. package/dist/doctor-cli.js +68 -0
  48. package/dist/doctor-preflight.d.ts +64 -0
  49. package/dist/doctor-preflight.d.ts.map +1 -0
  50. package/dist/doctor-preflight.js +167 -0
  51. package/dist/feedback/coverage.d.ts.map +1 -1
  52. package/dist/feedback/coverage.js +9 -2
  53. package/dist/feedback/grade.d.ts +8 -1
  54. package/dist/feedback/grade.d.ts.map +1 -1
  55. package/dist/feedback/grade.js +4 -0
  56. package/dist/gateway/http-protocol.d.ts +19 -0
  57. package/dist/gateway/http-protocol.d.ts.map +1 -1
  58. package/dist/gateway/serve-client.d.ts +99 -0
  59. package/dist/gateway/serve-client.d.ts.map +1 -0
  60. package/dist/gateway/serve-client.js +360 -0
  61. package/dist/index.d.ts +31 -3
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +71 -8
  64. package/dist/prompts/manifest.d.ts +128 -0
  65. package/dist/prompts/manifest.d.ts.map +1 -0
  66. package/dist/prompts/manifest.js +214 -0
  67. package/dist/prompts/prompt-fetch.d.ts +84 -0
  68. package/dist/prompts/prompt-fetch.d.ts.map +1 -0
  69. package/dist/prompts/prompt-fetch.js +120 -0
  70. package/dist/prompts/prompt-resolve.d.ts +76 -0
  71. package/dist/prompts/prompt-resolve.d.ts.map +1 -0
  72. package/dist/prompts/prompt-resolve.js +93 -0
  73. package/dist/prompts/slot-resolver.d.ts +91 -0
  74. package/dist/prompts/slot-resolver.d.ts.map +1 -0
  75. package/dist/prompts/slot-resolver.js +135 -0
  76. package/dist/prompts/tools-register.d.ts +60 -0
  77. package/dist/prompts/tools-register.d.ts.map +1 -0
  78. package/dist/prompts/tools-register.js +48 -0
  79. package/dist/prompts/version-hash.d.ts +29 -0
  80. package/dist/prompts/version-hash.d.ts.map +1 -0
  81. package/dist/prompts/version-hash.js +54 -0
  82. package/dist/prompts-export-cli.d.ts +21 -0
  83. package/dist/prompts-export-cli.d.ts.map +1 -0
  84. package/dist/prompts-export-cli.js +130 -0
  85. package/dist/registry/cache-policy.d.ts.map +1 -1
  86. package/dist/registry/cache-policy.js +22 -0
  87. package/dist/registry/pricing-shape.d.ts +38 -8
  88. package/dist/registry/pricing-shape.d.ts.map +1 -1
  89. package/dist/registry/pricing-shape.js +40 -12
  90. package/dist/registry/pricing.d.ts +6 -5
  91. package/dist/registry/pricing.d.ts.map +1 -1
  92. package/dist/registry/pricing.js +9 -5
  93. package/dist/routes/destination-loader.d.ts +16 -0
  94. package/dist/routes/destination-loader.d.ts.map +1 -0
  95. package/dist/routes/destination-loader.js +29 -0
  96. package/dist/routes/draft.d.ts +7 -0
  97. package/dist/routes/draft.d.ts.map +1 -1
  98. package/dist/routes/draft.js +12 -0
  99. package/dist/routes/loader.d.ts.map +1 -1
  100. package/dist/routes/loader.js +27 -54
  101. package/dist/routes/output-loader.d.ts +42 -0
  102. package/dist/routes/output-loader.d.ts.map +1 -0
  103. package/dist/routes/output-loader.js +112 -0
  104. package/dist/routes/prompt-loader.d.ts +49 -0
  105. package/dist/routes/prompt-loader.d.ts.map +1 -0
  106. package/dist/routes/prompt-loader.js +113 -0
  107. package/dist/routes/resolved-bundle.d.ts +19 -0
  108. package/dist/routes/resolved-bundle.d.ts.map +1 -1
  109. package/dist/routes/resolved-bundle.js +24 -0
  110. package/dist/routes/screen-loader.d.ts +1 -1
  111. package/dist/routes/screen-loader.d.ts.map +1 -1
  112. package/dist/routes/screen-loader.js +8 -1
  113. package/dist/run/accept.d.ts +3 -0
  114. package/dist/run/accept.d.ts.map +1 -1
  115. package/dist/run/accept.js +1 -0
  116. package/dist/run/cost.d.ts +31 -2
  117. package/dist/run/cost.d.ts.map +1 -1
  118. package/dist/run/cost.js +57 -2
  119. package/dist/run/destination.d.ts +39 -0
  120. package/dist/run/destination.d.ts.map +1 -0
  121. package/dist/run/destination.js +45 -0
  122. package/dist/run/dispatch.d.ts +25 -3
  123. package/dist/run/dispatch.d.ts.map +1 -1
  124. package/dist/run/dispatch.js +26 -4
  125. package/dist/run/raw-defaults.d.ts.map +1 -1
  126. package/dist/run/raw-defaults.js +6 -1
  127. package/dist/run/raw-finalize.d.ts +96 -0
  128. package/dist/run/raw-finalize.d.ts.map +1 -0
  129. package/dist/run/raw-finalize.js +283 -0
  130. package/dist/run/raw-knobs.d.ts +36 -0
  131. package/dist/run/raw-knobs.d.ts.map +1 -0
  132. package/dist/run/raw-knobs.js +99 -0
  133. package/dist/run/raw-request.d.ts +24 -0
  134. package/dist/run/raw-request.d.ts.map +1 -0
  135. package/dist/run/raw-request.js +69 -0
  136. package/dist/run/raw-schema.d.ts +76 -0
  137. package/dist/run/raw-schema.d.ts.map +1 -0
  138. package/dist/run/raw-schema.js +166 -0
  139. package/dist/run/raw-search-cost.d.ts +49 -0
  140. package/dist/run/raw-search-cost.d.ts.map +1 -0
  141. package/dist/run/raw-search-cost.js +79 -0
  142. package/dist/run/raw-support.d.ts +64 -6
  143. package/dist/run/raw-support.d.ts.map +1 -1
  144. package/dist/run/raw-support.js +84 -11
  145. package/dist/run/raw.d.ts +25 -26
  146. package/dist/run/raw.d.ts.map +1 -1
  147. package/dist/run/raw.js +30 -351
  148. package/dist/run/record-attempt.d.ts +8 -0
  149. package/dist/run/record-attempt.d.ts.map +1 -1
  150. package/dist/run/record-attempt.js +26 -12
  151. package/dist/run/record-call.d.ts +104 -0
  152. package/dist/run/record-call.d.ts.map +1 -0
  153. package/dist/run/record-call.js +277 -0
  154. package/dist/run/repair-attempt.d.ts +68 -0
  155. package/dist/run/repair-attempt.d.ts.map +1 -0
  156. package/dist/run/repair-attempt.js +180 -0
  157. package/dist/run/resolve-destination.d.ts +49 -0
  158. package/dist/run/resolve-destination.d.ts.map +1 -0
  159. package/dist/run/resolve-destination.js +76 -0
  160. package/dist/run/routing-docs.d.ts.map +1 -1
  161. package/dist/run/routing-docs.js +9 -4
  162. package/dist/run/run.d.ts +30 -1
  163. package/dist/run/run.d.ts.map +1 -1
  164. package/dist/run/run.js +75 -148
  165. package/dist/run/telemetry-row.d.ts +92 -0
  166. package/dist/run/telemetry-row.d.ts.map +1 -0
  167. package/dist/run/telemetry-row.js +81 -0
  168. package/dist/run/validate.d.ts +12 -1
  169. package/dist/run/validate.d.ts.map +1 -1
  170. package/dist/run/validate.js +31 -0
  171. package/dist/run/web-search-pricing.d.ts +43 -0
  172. package/dist/run/web-search-pricing.d.ts.map +1 -0
  173. package/dist/run/web-search-pricing.js +56 -0
  174. package/dist/settings/grading-guide.d.ts +27 -1
  175. package/dist/settings/grading-guide.d.ts.map +1 -1
  176. package/dist/status.d.ts +6 -0
  177. package/dist/status.d.ts.map +1 -1
  178. package/dist/status.js +4 -1
  179. package/dist/telemetry/artifact-store.d.ts.map +1 -1
  180. package/dist/telemetry/artifact-store.js +11 -0
  181. package/dist/telemetry/attempt-meta.d.ts +66 -1
  182. package/dist/telemetry/attempt-meta.d.ts.map +1 -1
  183. package/dist/telemetry/attempt-meta.js +27 -1
  184. package/dist/telemetry/exporter-setup.d.ts +2 -0
  185. package/dist/telemetry/exporter-setup.d.ts.map +1 -1
  186. package/dist/telemetry/exporter-setup.js +6 -1
  187. package/dist/telemetry/grade-mappers.d.ts +5 -2
  188. package/dist/telemetry/grade-mappers.d.ts.map +1 -1
  189. package/dist/telemetry/grade-mappers.js +8 -2
  190. package/dist/telemetry/grades-sql.d.ts +4 -1
  191. package/dist/telemetry/grades-sql.d.ts.map +1 -1
  192. package/dist/telemetry/grades-sql.js +26 -3
  193. package/dist/telemetry/ingest-protocol.d.ts +26 -2
  194. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  195. package/dist/telemetry/ingest-protocol.js +32 -29
  196. package/dist/telemetry/lanes.d.ts +31 -0
  197. package/dist/telemetry/lanes.d.ts.map +1 -0
  198. package/dist/telemetry/lanes.js +41 -0
  199. package/dist/telemetry/org-identity.d.ts +47 -0
  200. package/dist/telemetry/org-identity.d.ts.map +1 -0
  201. package/dist/telemetry/org-identity.js +100 -0
  202. package/dist/telemetry/route-registration.d.ts +48 -0
  203. package/dist/telemetry/route-registration.d.ts.map +1 -0
  204. package/dist/telemetry/route-registration.js +77 -0
  205. package/dist/telemetry/sinks.d.ts +10 -1
  206. package/dist/telemetry/sinks.d.ts.map +1 -1
  207. package/dist/telemetry/sinks.js +52 -6
  208. package/dist/telemetry/store-migrations.d.ts.map +1 -1
  209. package/dist/telemetry/store-migrations.js +13 -0
  210. package/dist/tenancy/api-key-format.d.ts +16 -1
  211. package/dist/tenancy/api-key-format.d.ts.map +1 -1
  212. package/dist/tenancy/api-key-format.js +17 -0
  213. package/dist/types/provider.d.ts +20 -3
  214. package/dist/types/provider.d.ts.map +1 -1
  215. package/dist/types/provider.js +1 -0
  216. package/dist/types/route.d.ts +76 -0
  217. package/dist/types/route.d.ts.map +1 -1
  218. package/dist/types/telemetry.d.ts +30 -2
  219. package/dist/types/telemetry.d.ts.map +1 -1
  220. package/docs/agent-skill/SKILL.md +75 -5
  221. package/docs/instrumentation.md +98 -36
  222. package/docs/llms.txt +96 -16
  223. package/docs/managed-serving.md +150 -0
  224. package/docs/mcp-oracle.md +75 -2
  225. package/docs/migration-playbook.md +5 -4
  226. package/docs/quickstart.md +169 -83
  227. package/package.json +3 -2
  228. package/registry/registry.json +82 -0
  229. package/dist/feedback/feedback.d.ts +0 -35
  230. package/dist/feedback/feedback.d.ts.map +0 -1
  231. package/dist/feedback/feedback.js +0 -39
package/CHANGELOG.md CHANGED
@@ -10,8 +10,235 @@ published package diverge.
10
10
  This project adheres to [Semantic Versioning](https://semver.org/). Dates are
11
11
  UTC.
12
12
 
13
+ Every `[Unreleased]` entry adds its symbols to `release-manifest.json` (rule P17
14
+ in `docs/ModelRig/development-rules.md`; enforced by `release-parity.test.ts`).
15
+
13
16
  ## [Unreleased]
14
17
 
18
+ ## [0.11.0] — 2026-10-20
19
+
20
+ **`rig.recordCall()` — record a provider call ModelRig did not dispatch**
21
+ (OBS-1, acceptance ruling M12). When a call has to stay a direct provider call,
22
+ report it after it returns: `rig.recordCall({ provider, model, usage, servedTier?,
23
+ requestedTier?, latencyMs, ttfbMs?, failureClass?, webSearchCalls?, stepKey?, tags?,
24
+ providerRequestId?, finishReason? })` → `{ inferenceId, callId, costEstimateUsd,
25
+ pricingMissing }`. One row, built by the raw lane's own helpers: priced at list
26
+ for the SERVED tier (flex included), `route = provider/model`, `routeVersion 0`,
27
+ inside `run.scope`/`run.start` a ground-truth step joined by `inference_id` and
28
+ `run_id` stamped; `webSearchCalls` writes the separate `rung:"search"` cost row.
29
+ The row carries `tags.lane = "external"` and `tags.key_ownership = "byok"`
30
+ (SDK-owned, a caller tag cannot override them) and `meta.provenance =
31
+ "caller-reported"` (new optional `AttemptMetaV1` key, exported type
32
+ `AttemptProvenance`; additive, no version bump). Any non-empty provider is
33
+ accepted; an unpriced provider/model records $0 with `pricingMissing`.
34
+ Synchronous and never throws: invalid input warns once per rule with a fix and
35
+ returns `inferenceId: null`. Token counts and `latencyMs`/`ttfbMs` are rounded
36
+ to integers and clamped (tokens ≤ `Number.MAX_SAFE_INTEGER`, ms ≤ 2³¹−1); a
37
+ `provider/model` longer than 200 characters is refused (ingest would drop it). Types `RecordCallInput`, `RecordCallResult`,
38
+ `RecordCallUsage`. Docs: `not-captured.md` → *Calls your code makes itself:
39
+ record them*; `instrumentation.md`.
40
+
41
+ **Raw-lane rows are BYOK by construction.** Every `rig.runRaw` /
42
+ `rig.runRawStream` row (and its native-search cost row) now carries
43
+ `tags.key_ownership = "byok"`, SDK-owned and stamped last beside `lane: "raw"`
44
+ — the raw lane only ever dispatches on the caller's own provider key. Before
45
+ 0.11.0 a raw call made in your own process carried no `key_ownership` tag;
46
+ the control plane now classifies `lane: "raw"` rows as BYOK on the lane. This
47
+ applies to every window settled from this release on; no customer usage window
48
+ had been settled before it.
49
+
50
+ **One lane classifier.** `isRoutedLane(lane)` (false for `"raw"` and
51
+ `"external"`), `callLaneOf(lane)` (`routes` | `raw` | `external`), `RAW_LANE`,
52
+ `EXTERNAL_LANE`, `RECORDED_BADGE`, `RECORDED_TOOLTIP` and the `CallLane` type are
53
+ exported — the same test the server rollups, `/coverage` (`computeCoverage`)
54
+ and the console use, so a recorded or raw call never enters ladder,
55
+ conformance or coverage-rung math. Docs: `not-captured.md`.
56
+
57
+ **`modelrig doctor` edge cases (QA N14).** A value that is not a `rig_sk_` key
58
+ at all (a deployment or provider token) is a usage error — "not a rig_sk_ key —
59
+ doctor checks customer API keys", exit 2 — and a `rig_sk_` key that is
60
+ truncated or padded fails with "malformed key"; neither is ever sent over the
61
+ wire (`PreflightResult.status` gains `"not_rig_key"` and `"malformed_key"`). A 2xx whoami whose body is
62
+ not JSON, or that names no org, is now a failure (exit 1, no ✓, says why) — it
63
+ used to print `(unknown org) ✓`. The first-flush "exporting to org" line no
64
+ longer prints `«?»`/`(?)` placeholders.
65
+
66
+ ## [0.10.0] — 2026-10-19
67
+
68
+ **Behaviour change (raw lane) — migration note.** A `rig.runRaw` call on Gemini
69
+ that passed a non-empty `systemPrompt` alongside `cache.key` used to have the
70
+ system prompt silently DROPPED (the adapter could not place it beside
71
+ `cachedContent`). It is now delivered in the user turn — ahead of `userPrompt`,
72
+ separated by a blank line — and is therefore billed as input tokens on every
73
+ such call. If the cache resource already holds that text, pass
74
+ `systemPrompt: ""` to keep the request byte-identical to before (apps/api's raw
75
+ lane already does). A call with an empty `systemPrompt`, or without a handle, is
76
+ unchanged.
77
+
78
+ **Per-call user turn (`prompt.user`) + the Gemini cache-handle delivery rule**
79
+ (GitLab issue #5, R1). A route may now declare `prompt.user: <path>` — a second
80
+ template rendered with the same `variables`, capability conditionals and
81
+ `x-trust` rules as `prompt.system` — whose rendered text **is** the user turn of
82
+ every primary attempt (replacing the fixed trigger sentence) and rides ahead of
83
+ the directive on a same-model repair. `prompt.system` may be omitted when
84
+ `prompt.user` is declared. Absent ⇒ the fixed trigger ⇒ byte-identical
85
+ behaviour and an unchanged `bundle_sha`; declaring it changes the `bundle_sha`,
86
+ and `userTemplate` rides the resolved (mirror) bundle. **Cache rule:** a Gemini
87
+ dispatch carrying a cache handle (`cachedContent`) may not carry
88
+ `systemInstruction`; before this release the adapter silently DROPPED the
89
+ rendered template on such a dispatch (the `caching-lifecycle.md` "you do not
90
+ need to empty your template" reassurance was a trap). Now **with a handle,
91
+ everything rendered is delivered as the user turn** — the system prompt (if
92
+ any), a blank line, the user turn — on both lanes, and nothing is dropped. Every
93
+ Gemini attempt that reached the provider stamps `inferences.meta.prompt_delivery`
94
+ (`system` | `user` | `user+cache`, exported as `PromptDelivery`) so the control
95
+ plane can prove what was sent; a transport-error row (no response) carries none.
96
+ The loader warns on a `cache:` route with a Gemini candidate, a non-empty
97
+ `prompt.system` and no `prompt.user`. Docs: `caching-lifecycle.md` → *A cache
98
+ handle changes where the template is delivered*; `route-bundles.md` → *Per-call
99
+ user turn*.
100
+
101
+ **Prose on the route lane — `output: text`** (GitLab issue #1; incident
102
+ 2026-10-11). A route can now declare that its output is prose/markdown rather
103
+ than JSON: `output: text` in the bundle YAML (or `output: { kind: text,
104
+ min_chars: N }` for the one declared length check). The response is returned
105
+ **verbatim** as `result.output` (a string): no `JSON.parse`, no schema, no
106
+ json-mode coaching, and the JSON repair rung is never entered — so a prose route
107
+ is never rejected `content_invalid` for not being JSON. The default is unchanged
108
+ (`json`; `output: json` may be written explicitly and is identical to absent):
109
+ every existing route behaves byte-identically and keeps its `bundle_sha`. A
110
+ text route's `bundle_sha` differs from its JSON twin, and `outputKind` rides the
111
+ resolved bundle, so the hosted mirror lane and call-config pins honour the prose
112
+ contract too. The loader refuses `output: text` combined with `schema:`,
113
+ `require: schema_conformant`, or `repair:` with a teaching error. A JSON route
114
+ that receives prose keeps today's `content_invalid` and now carries a
115
+ `fixHint` pointing at `output: text`. Every dispatched route-lane attempt stamps
116
+ `inferences.meta.output_kind` (`json` | `text`, the contract it was held to);
117
+ raw-lane rows carry no such key. `OutputKind` is exported. Docs:
118
+ `route-bundles.md` → *Prose and markdown output*.
119
+
120
+ **`responseSchema` on the raw lane (R2, GitLab #6).** `rig.runRaw` accepts a
121
+ JSON Schema object and forwards it as the provider's *native* structured-output
122
+ directive — gemini `responseJsonSchema` + JSON mime, openai/grok strict
123
+ `json_schema` (a non-object root is warned pre-dispatch), anthropic a forced
124
+ tool call — then validates the returned text client-side with the same Ajv the
125
+ routed lane uses. A miss is a **billed, terminal `content_invalid`**: one
126
+ `lane: "raw"` row with that class and a thrown `RigFailureError` whose `fixHint`
127
+ names the first errors; nothing is repaired, retried, or fallen through, so
128
+ `meta.validated` stays `false` (boundary B2 is narrowed from "no structure" to
129
+ "no repair"). Providers without a native schema path on this lane — deepseek and
130
+ the OpenAI-compat hosts (deepinfra / fireworks / zai) — fail closed
131
+ pre-dispatch, as does a schema that is not a plain object, does not compile, or
132
+ is declared alongside `responseFormat`. The row's `meta.schema_sha` is
133
+ `sha256:` of the canonical (sorted-key) schema JSON, so declared-vs-served drift
134
+ is queryable; the schema joins `input_hash` exactly as a route schema does. The
135
+ knob is one more cell of `RAW_KNOB_SUPPORT` — the guard, the published matrix,
136
+ `RAW_DEFAULTS`, and `llms.txt` all derive from it. Zero DDL (`meta` is jsonb).
137
+
138
+ **The generated tables now ship in the committed docs.** `route-bundles.md`
139
+ (raw-lane defaults, provider × knob matrix) and `routing-reliability.md`
140
+ (failure classes, the boundary) — and the nine use-case template pages — used
141
+ to carry a bare HTML-comment marker that only the modelrig.dev build filled in,
142
+ so GitLab, the OSS mirror and any offline agent reading the repo saw an empty
143
+ line where the table belonged. Every marker is now a fence around its current
144
+ render; the docs build refreshes the fenced body from the same renderer on every
145
+ run (the `docs/llms.txt` rule applied to the docs) and fails on a bare
146
+ placeholder, and a tripwire test asserts each committed body is byte-identical
147
+ to its renderer. Regenerate with `tsx scripts/build-docs-site.ts` and commit.
148
+
149
+ **Cost rows are priced at the SERVED tier, and Gemini has a `flex` tier
150
+ (GitLab #2).** `attemptCost` / `costComponents` now read `usage.servedTier`
151
+ (both lanes, every attempt — serving, failed, repair) and price `flex` /
152
+ `priority` from the model's published `pricing_v2` tiers instead of always
153
+ billing the standard base. `ServingTiers` gains `flex`, and a tier multiplier
154
+ may now be **per component** (`{ input: 0.5, output: 0.5, cache_read: 1.0 }`
155
+ beside the scalar form; an absent component is exempt, i.e. the standard
156
+ rate) — Google's flex/batch halve input + output but leave cache read "Same as
157
+ Standard", which one scalar cannot say. `gemini/gemini-3.1-pro-preview` (and
158
+ `-customtools`) carry `tiers.flex`, `tiers.priority: 1.8` and the >200k
159
+ context band, verified against the Gemini pricing page 2026-10-11. On the
160
+ InferWealth flex batch this recorded $1.11 where Google's list is ≈$0.68
161
+ (1,357,697 in / 1,289,855 cached / 59,625 out: in $0.068 + cache $0.258 + out
162
+ $0.358). A model that publishes no rate for a served tier still bills at base
163
+ (fail-closed, never a guessed multiplier); `standard` rows are byte-identical.
164
+ Known gap, stated: the >200k context band is **listed, not applied** — rows
165
+ whose prompt exceeds 200k tokens still price at the ≤200k rates and under-state
166
+ Google's list (follow-up issue). `meta.served_tier_source` (`provider` |
167
+ `requested_fallback`) now says whether the served tier came from the provider's
168
+ response or fell back to the requested tier, so a fallback-priced row is
169
+ identifiable. **Scope of the claim:** attempt cost rows (`cost_estimate_usd`)
170
+ and `meta.cost` price at the served tier; the server's managed re-pricing
171
+ (`modelrig-server` `compute-fee.ts` `listPriceCents`, the `/costs` rollup and
172
+ settlement draw) still prices at base — that money path is untouched here and
173
+ tracked for the next money-path MR. Zero DDL.
174
+
175
+ **Managed serving is released at the SDK/capability layer** (`destination:
176
+ modelrig-served`). The serving cells of the capability table and the published
177
+ `llms.txt` matrix drop their UNRELEASED tags; `config.managedServing` is
178
+ unchanged (set in code, default `false`, so an absent/`direct` route stays
179
+ byte-identical). New customer page `docs/managed-serving.md` — declare the
180
+ destination, enable it in code, mint a `serve`-scoped key, opt the organization
181
+ in (Account settings → Managed serving, or `PATCH /v1/orgs/:org/settings
182
+ {managed_serving_enabled}`), fund the prepaid balance, and what each 401/402/
183
+ 403/502/503 refusal teaches; `route-bundles.md` documents the `destination`
184
+ field. The SDK side of the hosted keyless transport (K1/K1b) is in this package
185
+ (it is not in 0.9.0, where a served run uses the local seam). Hosted serving
186
+ also needs the org opt-in, a `serve`-scoped key and a funded prepaid balance.
187
+
188
+ Routes are visible — and pinnable — before their first call (RV-1). A rig
189
+ created with a `routesDir` and a control-plane export now registers each route
190
+ the moment `createRig()` runs: its metadata (route, version, candidates,
191
+ capture flag) plus its declared resolved bundle and `bundle_sha`, through the
192
+ same `/v1/ingest` call telemetry uses (the `ingest` scope is enough). The
193
+ hosted ingest now stores that bundle (it used to drop it in api mode), so a
194
+ call-config pin passes the eligibility gate instead of failing with "no
195
+ servable bundle". The bundle is route configuration (the authored prompt
196
+ template, never per-call values); it is stored even for a posture-`off` org.
197
+ A prompt version promoted in ModelRig keeps precedence over the YAML template.
198
+ Registration is fire-and-forget and fail-open: never awaited, never throws,
199
+ warns once on failure, and never changes what this process serves. The
200
+ exporter's first-flush routes mirror (which now also lands its bundle in api
201
+ mode) remains the retry path. Pair it with `GET /v1/routes/:route/call-config?include=effective`
202
+ (or MCP `get_call_config`) to see what a route will serve and why — pin vs your
203
+ YAML default — and whether recent calls drifted from a pin. A bundle that
204
+ arrives over `/v1/ingest` is stored as route configuration only: pins, the
205
+ effective view and bake-offs use it, but the hosted gateway keeps serving only
206
+ bundles from a trusted sync, so an `ingest` key cannot change what the gateway
207
+ serves. `stampBatch` now writes routes with and without a bundle as two
208
+ separate writes (a bulk insert needs every row to carry the same columns).
209
+
210
+ Silent telemetry drops are now loud — **no configuration required**. When an
211
+ ingest POST is rejected with `401`/`403` (a wrong, revoked, or wrong-org key),
212
+ the api sink emits ONE loud, legible warning — `[modelrig] AUTH FAILED —
213
+ telemetry is being DROPPED (401 at <host>, key …5WMM) …` — naming the host and
214
+ the redacted key and pointing at `app.modelrig.ai` and `modelrig doctor`. It
215
+ fires once per failure streak (a later successful send re-arms it), rides the
216
+ POST that is already failing (no extra request), and never prints the key or the
217
+ response body. This is the automatic safety net — the one key a customer already
218
+ sets is enough; there is no boot-preflight env var.
219
+
220
+ `modelrig doctor` — a new CLI command that confirms, in one line, which org your
221
+ `MODELRIG_API_KEY` resolves to (via the server's `GET /v1/whoami`): prints
222
+ `key …5WMM → org «YourOrg» ✓` on success, a loud `AUTH FAILED (401)` line on a
223
+ rejected key, and exits non-zero when the key is bad — so CI / a deploy step /
224
+ setup can gate on it before any traffic runs. Never prints the key (last-four
225
+ only).
226
+
227
+ **Which org am I exporting to? — the wrong-org signal (REL-1, M5).** A valid key
228
+ minted in the wrong org used to fail silently: every ingest POST is a 200 and the
229
+ runs land in another org's console. Now, on the first successful flush in each
230
+ process, the api sink calls `GET /v1/whoami` once (the same helper `modelrig
231
+ doctor` uses) and logs `[modelrig] exporting to org «Name» (slug) as key «name»
232
+ rig_sk_…last4`. It runs in the background, never blocks or drops a batch, and is
233
+ silent if whoami fails (a rejected key already has its own `AUTH FAILED` line).
234
+ New code option `createRig({ expectOrg: "<slug>" })`: when the key resolves to a
235
+ different org it logs `[modelrig] ORG MISMATCH: key resolves to «a», expectOrg is
236
+ «b» — telemetry is landing in «a»` loudly on every flush. The whoami call is
237
+ aborted after 5 s, so it never holds a process open. It never throws and never drops telemetry. It is a code option, not an
238
+ env var (P15). `modelrig doctor --expect-org <slug>` exits 1 and prints both
239
+ slugs on a mismatch, so a deploy or CI step can gate on it. Docs:
240
+ `instrumentation.md` → *Which org am I exporting to?*
241
+
15
242
  ## [0.9.0] — 2026-09-01
16
243
 
17
244
  **Breaking** (the reason this is 0.9.0, not 0.8.2): the temporary `rig.feedback` /
package/README.md CHANGED
@@ -89,10 +89,11 @@ import { createRig, loadConfigFromEnv } from "modelrig";
89
89
  const rig = createRig(loadConfigFromEnv()); // keys from GEMINI_API_KEY etc.
90
90
  const result = await rig.run("example.support_summarize", {
91
91
  input: { ticket, product, priorContext }, // template variables
92
- tags: { run_id, step: "summarize" },
93
- budget: { envelope: run_id }, // hard-stop cost envelope
92
+ tags: { subject: customer, feature: "summarize" },
93
+ budget: { envelope: ticketId }, // hard-stop cost envelope
94
94
  });
95
- // result.output — schema-validated JSON (ajv), regardless of serving model
95
+ // result.output — schema-validated JSON (ajv), regardless of serving model;
96
+ // the verbatim string on an `output: text` (prose) route
96
97
  // result.meta — servedCandidate, tokens (in/out/cached), cost, tiers,
97
98
  // validation status, attempts by failure class
98
99
  rig.close();
@@ -123,15 +124,23 @@ without it. ModelRig carries the handle and prices the hits — it never creates
123
124
  refreshes, or deletes the resource, so a long job needs your own TTL heartbeat
124
125
  (see the [caching lifecycle](https://modelrig.dev/caching-lifecycle.html) doc).
125
126
  The Lane-B `rig.runRaw({ provider, model, apiKey, …, cache: { key } })` seam takes
126
- the same field.
127
+ the same field. A Gemini handle forbids a system instruction on the request, so
128
+ **with a handle everything the route rendered is delivered as the user turn**
129
+ (never dropped); declare the per-call delta in a `prompt.user` template and let
130
+ the cache hold the shared prefix — every attempt stamps `meta.prompt_delivery`
131
+ (`system` | `user` | `user+cache`) so you can prove what was sent.
127
132
 
128
133
  `rig.runRaw` also accepts
129
134
  provider knobs, each additive — absent ⇒ byte-identical dispatch:
130
135
  `grounding: { mode: "native" }` (native provider web grounding —
131
136
  gemini; grok fail-closed until CE-9b), `serviceTier: "standard" | "flex" | "priority"`,
132
137
  `reasoning: { level: "minimal" | "low" | "medium" | "high" }` (reasoning effort¹),
133
- and `responseFormat: "json_object"` (syntactic-JSON forcing¹ — MIME-shaping, NOT
134
- schema enforcement, so `meta.validated` stays `false`). A declared directive a
138
+ `responseFormat: "json_object"` (syntactic-JSON forcing¹ — MIME-shaping, NOT
139
+ schema enforcement, so `meta.validated` stays `false`), and `responseSchema`
140
+ (a JSON Schema object² — forwarded as the provider's native structured-output
141
+ directive AND Ajv-checked client-side; a miss is a billed, terminal
142
+ `content_invalid` — never repaired or retried — and the row carries
143
+ `meta.schema_sha`). A declared directive a
135
144
  provider cannot honor FAILS CLOSED — a thrown `RigFailureError` with class
136
145
  `invariant_violation`, pre-dispatch (no meter, no provider call) — never a silent
137
146
  drop. **Which provider honors which knob is one table** — the runtime guard and
@@ -144,8 +153,10 @@ you don't duplicate the thinking-model set). A grounded step that also passes a
144
153
  cache resource at creation (ModelRig sends `cachedContent` without tools, which
145
154
  the provider requires).
146
155
 
147
- _¹ Multi-provider `reasoning` and `responseFormat` ship in
148
- 0.4.0; on the published package `reasoning`/`grounding` are gemini-only._
156
+ _¹ Which provider honors `reasoning`, `grounding` and `responseFormat` is the
157
+ knob matrix — generated from the same table the runtime guard reads._
158
+ _² `responseSchema` needs 0.10.0 or newer (gemini / openai / grok / anthropic;
159
+ deepseek and the compat hosts fail closed) — see the knob matrix._
149
160
 
150
161
  **runRaw — a BYOK passthrough (Lane B).** When you don't need a route — no
151
162
  schema, no validation, no retries — call a `provider/model` directly with your
@@ -248,6 +259,10 @@ lines up with the console's built-ins:
248
259
  | `session` | the conversation/session the call belongs to |
249
260
  | `cost_center` | the team/budget to attribute spend to |
250
261
 
262
+ These five are the keys **you** set. `run_id` and `step` are stamped by the SDK
263
+ inside a run (don't set `run_id` yourself), and ModelRig owns a few more; the one
264
+ complete list is in the [instrumentation guide](./docs/instrumentation.md#reserved-tag-keys-the-one-list).
265
+
251
266
  `modelrig validate` warns on near-misses — a declared `rig.yaml` dimension named
252
267
  `customer`, `tenant`, `user_id`, `team`, … is nudged toward the reserved key it
253
268
  resembles. They stay ordinary tags; nothing is required.
@@ -260,33 +275,40 @@ one **step** per model-call family, and **save the work products** as artifacts;
260
275
  the console's **Runs** tab then shows the run → step → artifact chain you click
261
276
  through, and `/projects` groups it by your tags.
262
277
 
278
+ <!-- BLOCK:RUN_WRAP -->
279
+ **Wrap one pipeline execution in a run.** Every pipeline execution is a run. Wrap each execution in `run.scope` (or `run.start` / `run.end`), name one step per model call with `stepKey`, `artifact.save` the work products, and pass `environment` so the Env column on /runs is filled. Inside the run the SDK stamps `run_id` and `step` on every call. `run_id` is reserved: don't set it yourself.
280
+
263
281
  ```ts
264
282
  import { createRig, loadConfigFromEnv } from "modelrig";
265
283
 
266
284
  const rig = createRig(loadConfigFromEnv());
267
285
 
268
- // One run per pipeline execution. run.scope isolates concurrent runs; use
269
- // run.start()/run.end() for one run at a time on a request path.
286
+ // Every pipeline execution is a run. Inside the scope the SDK records each
287
+ // rig.run / rig.runRaw call as a step and stamps run_id and step on every row.
288
+ // run_id is reserved: don't set it yourself (put your own attempt id in a tag of
289
+ // your own, such as pipeline_attempt_id).
270
290
  await rig.artifacts.run.scope(
271
- { pipeline: "support", episodeKey: ticketId },
291
+ { pipeline: "support", episodeKey: ticketId, environment: process.env.NODE_ENV ?? "development" },
272
292
  async () => {
273
293
  const result = await rig.run("example.support_summarize", {
274
294
  input: { ticket, product, priorContext },
275
- tags: { subject: customer, feature: "summarize" }, // run_id auto-stamped
276
- stepKey: "summarize", // names this step on /runs
295
+ tags: { subject: customer, feature: "summarize" },
296
+ stepKey: "summarize", // names this step on /runs
277
297
  });
278
- // Save the work product — prompt / raw / parsed all save the same way.
279
- rig.artifacts.artifact.save(result.output, { name: "summary", type: "step_output" });
298
+ rig.artifacts.artifact.save(result.output, { name: "summary", type: "step_output", stepKey: "summarize" });
280
299
  },
281
300
  );
282
- rig.close();
301
+
302
+ await rig.close(); // flushes the exporter — call once at shutdown
283
303
  ```
304
+ <!-- /BLOCK:RUN_WRAP -->
284
305
 
285
306
  - **Tags:** carry `subject` (what the run is *about* — an opaque id) and
286
307
  `feature`. Inside a run context the SDK stamps `tags.run_id` (the run's
287
308
  `episodeKey`, else its id) and `tags.step` (the current step key) on every
288
- attempt row of both lanes — a value you pass always wins, and outside a run
289
- context nothing is stamped, so the row is byte-identical.
309
+ attempt row of both lanes; outside a run context nothing is stamped, so the row
310
+ is byte-identical. `run_id` is reserved: don't set it yourself — the full
311
+ reserved-key list is in the [instrumentation guide](./docs/instrumentation.md#reserved-tag-keys-the-one-list).
290
312
  - **Raw lane too:** a `rig.runRaw` call inside a run context records a
291
313
  ground-truth step (since 0.5.0), so grounded/cached BYOK pipelines fill the
292
314
  same graph.
@@ -297,8 +319,7 @@ rig.close();
297
319
 
298
320
  **On by default under a control plane.** The run/artifact namespace is **on by
299
321
  default once a control plane is configured** — set `MODELRIG_API_KEY=rig_sk_…`
300
- (or the self-host Supabase pair) and runs record automatically, metadata and
301
- hashes only. A **pure local-only rig** (no sink to ship to) stays off; force it
322
+ (or the self-host Supabase pair) and runs record automatically. A **pure local-only rig** (no sink to ship to) stays off; force it
302
323
  on there with `MODELRIG_ARTIFACTS=1`. To **opt out of capture**, set posture
303
324
  `off` in your account settings (the console or the `set_org_settings` MCP tool) —
304
325
  the account posture is the customer control, not a deployment env var. With the
@@ -306,11 +327,12 @@ namespace off (a local-only rig) it is inert — `run.start` is a
306
327
  no-op, `artifact.save` returns null, zero rows are written, and your pipeline
307
328
  behaves byte-for-byte as before. `modelrig status` prints the current posture.
308
329
 
309
- > **Metadata and hashes only, this release.** Content custody (holding the
310
- > serialized bytes) is early access; today the value you save is hashed (sha256)
311
- > and the bytes are discarded — the metadata row and its integrity hash are what
312
- > persist. A `zeroRetention` run refuses every artifact fail-closed at the SDK
313
- > gate.
330
+ > **What is kept.** Every saved artifact records a metadata row with a sha256
331
+ > integrity hash. Whether the serialized bytes are kept as well is your
332
+ > organization's [content custody](https://modelrig.dev/content-custody.html)
333
+ > posture: new organizations are created `managed` (bytes kept in your managed
334
+ > store); `metadata` keeps hashes only and `off` captures nothing. A
335
+ > `zeroRetention` run refuses every artifact fail-closed at the SDK gate.
314
336
 
315
337
  The full artifact API — lineage (`link`), evaluations (`evaluate`), the reusable
316
338
  per-step seam, and `run.scope` vs `run.start` — is the
@@ -345,8 +367,10 @@ on the quickstart:
345
367
 
346
368
  ## Honest limits
347
369
 
348
- - **Prepaid balances and managed billing are not built yet.** This release is
349
- the gateway and the self-host lane; the hosted control plane has one customer.
370
+ - **Managed serving is opt-in and prepaid.** It draws from a prepaid balance your
371
+ organization funds in the console ([Managed serving](https://modelrig.dev/managed-serving.html));
372
+ with no balance, every call runs on your own provider keys (`destination: direct`,
373
+ the default).
350
374
  - **`0.x` means the shape may still move.** Route and config contracts are
351
375
  stable enough to build on, not frozen.
352
376
  - **`modelrig serve` needs the `modelrig-server` package** — it is not bundled
@@ -13,6 +13,20 @@ this snapshot was copied by hand per implementation-plan §9. Replace this manua
13
13
  copy with the sync script's output when Phase 0 tooling lands; until then, any
14
14
  pin bump must re-copy this file and update this note.
15
15
 
16
+ HAND-ADDED `pricing_v2` BLOCKS (must survive a pin bump — re-apply after any
17
+ re-copy): `gemini/gemini-3.1-pro-preview` and `…-customtools` carry an additive
18
+ `pricing_v2` (GitLab issue #2, 2026-10-11, verified against
19
+ https://ai.google.dev/gemini-api/docs/pricing): `tiers.flex = { input: 0.5,
20
+ output: 0.5, cache_read: 1.0 }` (flex/batch halve input + output; cache read is
21
+ "Same as Standard"), `tiers.priority = 1.8` (every component), and the >200k
22
+ context band. The snapshot wins on shared keys and never carries `pricing_v2`
23
+ upstream, so the block lives on the snapshot entry itself; `getPricingV2` reads
24
+ it and the served-tier cost path (`src/run/cost.ts`) prices flex/priority rows
25
+ from it. Flat fields are untouched (base is byte-identical). `scripts/sync-upstream.ts
26
+ --apply` RE-GRAFTS every `pricing_v2` block from the current map onto the new
27
+ snapshot (and refuses to apply if upstream dropped an entry that carries one), so a
28
+ pin bump cannot silently drop them; the manual re-copy path above must do the same.
29
+
16
30
  Key naming (verified against the snapshot):
17
31
  - Gemini API models: `gemini/<model>` (bare ids resolve to vertex_ai pricing — do not use)
18
32
  - OpenAI models: bare `<model>`
@@ -20338,6 +20338,19 @@
20338
20338
  "tpm": 800000
20339
20339
  },
20340
20340
  "gemini/gemini-3.1-pro-preview": {
20341
+ "pricing_v2": {
20342
+ "base": { "input": 2e-06, "output": 1.2e-05, "cache_read": 2e-07 },
20343
+ "tiers": {
20344
+ "priority": 1.8,
20345
+ "flex": { "input": 0.5, "output": 0.5, "cache_read": 1.0 }
20346
+ },
20347
+ "context_bands": [
20348
+ { "above_tokens": 200000, "rates": { "input": 4e-06, "output": 1.8e-05, "cache_read": 4e-07 } }
20349
+ ],
20350
+ "source": "https://ai.google.dev/gemini-api/docs/pricing (Gemini 3.1 Pro Preview, verified 2026-10-11: flex/batch 0.5x input+output, cache read 'Same as Standard'; priority 1.8x every component incl. cache read; >200k band doubles input, output and cache read)",
20351
+ "as_of": "2026-10-11",
20352
+ "basis": "base · standard tier · text · prompts ≤ 200k tokens (the >200k context band is listed; cost rows price at the SERVED tier)"
20353
+ },
20341
20354
  "cache_read_input_token_cost": 2e-07,
20342
20355
  "cache_read_input_token_cost_above_200k_tokens": 4e-07,
20343
20356
  "input_cost_per_token": 2e-06,
@@ -20395,6 +20408,19 @@
20395
20408
  "web_search_billing_unit": "per_query"
20396
20409
  },
20397
20410
  "gemini/gemini-3.1-pro-preview-customtools": {
20411
+ "pricing_v2": {
20412
+ "base": { "input": 2e-06, "output": 1.2e-05, "cache_read": 2e-07 },
20413
+ "tiers": {
20414
+ "priority": 1.8,
20415
+ "flex": { "input": 0.5, "output": 0.5, "cache_read": 1.0 }
20416
+ },
20417
+ "context_bands": [
20418
+ { "above_tokens": 200000, "rates": { "input": 4e-06, "output": 1.8e-05, "cache_read": 4e-07 } }
20419
+ ],
20420
+ "source": "https://ai.google.dev/gemini-api/docs/pricing (Gemini 3.1 Pro Preview, verified 2026-10-11: flex/batch 0.5x input+output, cache read 'Same as Standard'; priority 1.8x every component incl. cache read; >200k band doubles input, output and cache read)",
20421
+ "as_of": "2026-10-11",
20422
+ "basis": "base · standard tier · text · prompts ≤ 200k tokens (the >200k context band is listed; cost rows price at the SERVED tier)"
20423
+ },
20398
20424
  "cache_read_input_token_cost": 2e-07,
20399
20425
  "cache_read_input_token_cost_above_200k_tokens": 4e-07,
20400
20426
  "input_cost_per_token": 2e-06,
@@ -14,7 +14,7 @@
14
14
  * - gemini: `finishReason` is `SAFETY` / `PROHIBITED_CONTENT` / `RECITATION`.
15
15
  * The refusal TEXT itself is never carried (content) — only the boolean.
16
16
  */
17
- import type { AttemptMetaV1, FinishReason } from "../telemetry/attempt-meta";
17
+ import type { AttemptMetaV1, FinishReason, PromptDelivery, ServedTierSource } from "../telemetry/attempt-meta";
18
18
  import type { AdapterRequest, ProviderId, ToolCall } from "../types/provider";
19
19
  /** Map a provider's native finish/stop reason to the `FinishReason` enum
20
20
  * (spec §2). Unknown/absent ⇒ undefined (never inferred); an unrecognised
@@ -51,6 +51,13 @@ export interface AdapterMetaSignals {
51
51
  };
52
52
  /** true = the run loop streamed deltas (stream()); false/absent = buffered. */
53
53
  readonly streaming?: boolean;
54
+ /** Issue #5 (R1): how the rendered prompt reached the model on this call —
55
+ * reported by the adapter that shaped the request (today: gemini, where a
56
+ * cache handle moves delivery). Absent ⇒ no `prompt_delivery` key. */
57
+ readonly promptDelivery?: PromptDelivery;
58
+ /** Issue #2 (L6 F5): did the provider ECHO the served tier, or did the adapter
59
+ * fall back to the requested tier? Stamped as `meta.served_tier_source`. */
60
+ readonly servedTierSource?: ServedTierSource;
54
61
  }
55
62
  /** Build one adapter's NATIVE `meta` (spec §2). Only defined signals appear —
56
63
  * everything else is unknown and omitted. The producer takes it from here. */
@@ -1 +1 @@
1
- {"version":3,"file":"attempt-meta-map.d.ts","sourceRoot":"","sources":["../../src/adapters/attempt-meta-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE9E;;kCAEkC;AAClC,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,UAAU,EACpB,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC7B,YAAY,GAAG,SAAS,CA6C1B;AAED;;;oDAGoD;AACpD,wBAAgB,YAAY,CAC1B,GAAG,EAAE,cAAc,GAClB;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAOlF;AAED;0EAC0E;AAC1E,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAAG,SAAS,GACzC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,SAAS,CAQ/C;AAED,2EAA2E;AAC3E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;IAC7B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACrE,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;8EAC8E;AAC9E,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,UAAU,EACpB,CAAC,EAAE,kBAAkB,GACpB,OAAO,CAAC,aAAa,CAAC,CAyBxB;AAED;;;uDAGuD;AACvD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CASzE"}
1
+ {"version":3,"file":"attempt-meta-map.d.ts","sourceRoot":"","sources":["../../src/adapters/attempt-meta-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC/G,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE9E;;kCAEkC;AAClC,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,UAAU,EACpB,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC7B,YAAY,GAAG,SAAS,CA6C1B;AAED;;;oDAGoD;AACpD,wBAAgB,YAAY,CAC1B,GAAG,EAAE,cAAc,GAClB;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAOlF;AAED;0EAC0E;AAC1E,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAAG,SAAS,GACzC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,SAAS,CAQ/C;AAED,2EAA2E;AAC3E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;IAC7B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACrE,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B;;0EAEsE;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC;gFAC4E;IAC5E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CAC9C;AAED;8EAC8E;AAC9E,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,UAAU,EACpB,CAAC,EAAE,kBAAkB,GACpB,OAAO,CAAC,aAAa,CAAC,CA2BxB;AAED;;;uDAGuD;AACvD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CASzE"}
@@ -134,6 +134,10 @@ function buildAdapterMeta(provider, s) {
134
134
  }
135
135
  if (typeof s.streaming === "boolean")
136
136
  meta.streaming = s.streaming;
137
+ if (s.promptDelivery !== undefined)
138
+ meta.prompt_delivery = s.promptDelivery;
139
+ if (s.servedTierSource !== undefined)
140
+ meta.served_tier_source = s.servedTierSource;
137
141
  return meta;
138
142
  }
139
143
  /** A transport/SDK error's coarse envelope (spec §2 `http_status`/`error_code`).
@@ -27,8 +27,8 @@ function buildAdapters(config, registryFacts, compatStatic) {
27
27
  }
28
28
  if (config.keys.grok)
29
29
  adapters.set("grok", (0, grok_1.createGrokAdapter)({ apiKey: config.keys.grok }));
30
- // A1 aggregator hosts behind the generic openai-compatible adapter.
31
- for (const hostId of ["deepinfra", "fireworks"]) {
30
+ // A1 aggregator hosts + Z.ai (B4a) behind the generic openai-compatible adapter.
31
+ for (const hostId of ["deepinfra", "fireworks", "zai"]) {
32
32
  const apiKey = config.keys[hostId];
33
33
  if (!apiKey)
34
34
  continue;
@@ -13,8 +13,9 @@
13
13
  * Copyright (c) 2023 Berri AI, MIT License — retain this notice if LiteLLM's
14
14
  * gemini transformation logic is consulted during the port.
15
15
  */
16
- import type { AdapterRequest, CapabilityFlag, ProviderAdapter } from "../types/provider";
16
+ import type { AdapterRequest, CapabilityFlag, ProviderAdapter, ServiceTier } from "../types/provider";
17
17
  import type { FailureClass } from "../types/failures";
18
+ import type { PromptDelivery, ServedTierSource } from "../telemetry/attempt-meta";
18
19
  export interface GeminiAdapterConfig {
19
20
  readonly apiKey: string;
20
21
  }
@@ -53,9 +54,35 @@ export interface GeminiShapedRequest {
53
54
  readonly model: string;
54
55
  readonly contents: string;
55
56
  readonly config: Record<string, unknown>;
57
+ /** Issue #5 (R1): how the rendered prompt was delivered — stamped on the
58
+ * attempt's `meta.prompt_delivery` so the control plane can prove it. */
59
+ readonly promptDelivery: PromptDelivery;
56
60
  }
61
+ /**
62
+ * THE CACHE-HANDLE DELIVERY RULE (GitLab issue #5, R1). A request that sets
63
+ * `cachedContent` may not set `systemInstruction` (Google rejects both — the
64
+ * system instruction and tools are baked into the cache at creation). Before
65
+ * R1 the adapter silently DROPPED a non-empty system prompt here, so a route
66
+ * whose template held the per-call delta lost it whenever a handle was passed
67
+ * (caching-lifecycle.md's "you do not need to empty your template" was a trap).
68
+ * The rule now: **with a cache handle, everything rendered is delivered as the
69
+ * user turn** — the system prompt (when non-empty), a blank line, then the user
70
+ * turn — and nothing is dropped. The cache holds the shared prefix; the user
71
+ * turn carries what changes per call. Pure; exported for the adapter tests.
72
+ */
73
+ export declare function cachedUserTurn(systemPrompt: string, userPrompt: string): string;
57
74
  /** Pure request shaping — everything except the abort signal. */
58
75
  export declare function buildGeminiRequest(model: string, req: ShapingInput): GeminiShapedRequest;
76
+ /** Best-effort served-tier extraction (ported): response.serviceTier, then
77
+ * usageMetadata.trafficType (Vertex path), else assume the requested tier —
78
+ * production bills flex when a flex request was accepted without a tier echo.
79
+ * Issue #2 (L6 F5): now that cost rows PRICE at the served tier, the fallback
80
+ * is disclosed — `source` lands on `meta.served_tier_source`. Pure; exported
81
+ * for the unit test. No behaviour change in the tier itself. */
82
+ export declare function extractGeminiServedTier(rawServiceTier: string | undefined, trafficType: string | undefined, requested: ServiceTier): {
83
+ readonly tier: ServiceTier;
84
+ readonly source: ServedTierSource;
85
+ };
59
86
  /** A candidate's parts as we read them post-response — `functionCall`, `text`,
60
87
  * and (if `includeThoughts` were ever set) a `thought`-flagged text part may all
61
88
  * appear on the same candidate (a grounded / thinking turn). */
@@ -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;AAQlE,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;;gEAEgE;AAChE,KAAK,mBAAmB,GAAG;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AACF,KAAK,oBAAoB,GAAG;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAA;KAAE,CAAA;CAAE,CAAC;AAEvG;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,oBAAoB,GAAG,SAAS,GAAG,MAAM,CAM9F;AAED;;qEAEqE;AACrE,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,oBAAoB,GAAG,SAAS,GAAG,OAAO,CAInG;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,oBAAoB,GAAG,SAAS,EAC3C,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,QAAQ,EAAE,OAAO,GAChB,MAAM,CAGR;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,eAAe,CA4MhF"}
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,EACf,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,mBAAmB,CAAC;AAGlE,OAAO,KAAK,EAAiB,cAAc,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAKjG,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;IACzC;6EACyE;IACzE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;CACzC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/E;AAED,iEAAiE;AACjE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,mBAAmB,CA4DxF;AAUD;;;;;gEAKgE;AAChE,wBAAgB,uBAAuB,CACrC,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,SAAS,EAAE,WAAW,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,CAOnE;AAED;;gEAEgE;AAChE,KAAK,mBAAmB,GAAG;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AACF,KAAK,oBAAoB,GAAG;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAA;KAAE,CAAA;CAAE,CAAC;AAEvG;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,oBAAoB,GAAG,SAAS,GAAG,MAAM,CAM9F;AAED;;qEAEqE;AACrE,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,oBAAoB,GAAG,SAAS,GAAG,OAAO,CAInG;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,oBAAoB,GAAG,SAAS,EAC3C,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,QAAQ,EAAE,OAAO,GAChB,MAAM,CAGR;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,eAAe,CA8MhF"}