modelrig 0.5.0 → 0.7.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 (159) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +54 -4
  3. package/dist/adapters/anthropic.d.ts.map +1 -1
  4. package/dist/adapters/anthropic.js +36 -7
  5. package/dist/adapters/attempt-meta-map.d.ts +63 -0
  6. package/dist/adapters/attempt-meta-map.d.ts.map +1 -0
  7. package/dist/adapters/attempt-meta-map.js +154 -0
  8. package/dist/adapters/deepseek.d.ts.map +1 -1
  9. package/dist/adapters/deepseek.js +25 -2
  10. package/dist/adapters/gemini.d.ts.map +1 -1
  11. package/dist/adapters/gemini.js +29 -2
  12. package/dist/adapters/grok.d.ts.map +1 -1
  13. package/dist/adapters/grok.js +25 -2
  14. package/dist/adapters/openai-compat.d.ts.map +1 -1
  15. package/dist/adapters/openai-compat.js +25 -2
  16. package/dist/adapters/openai-stream.d.ts +2 -0
  17. package/dist/adapters/openai-stream.d.ts.map +1 -1
  18. package/dist/adapters/openai-stream.js +23 -3
  19. package/dist/adapters/openai.d.ts.map +1 -1
  20. package/dist/adapters/openai.js +36 -6
  21. package/dist/artifacts/artifact.d.ts.map +1 -1
  22. package/dist/artifacts/artifact.js +24 -5
  23. package/dist/artifacts/classification.d.ts +27 -0
  24. package/dist/artifacts/classification.d.ts.map +1 -0
  25. package/dist/artifacts/classification.js +38 -0
  26. package/dist/artifacts/index.d.ts +3 -1
  27. package/dist/artifacts/index.d.ts.map +1 -1
  28. package/dist/artifacts/index.js +10 -1
  29. package/dist/artifacts/run-context.d.ts +25 -3
  30. package/dist/artifacts/run-context.d.ts.map +1 -1
  31. package/dist/artifacts/run-context.js +61 -0
  32. package/dist/artifacts/scrub.d.ts +26 -0
  33. package/dist/artifacts/scrub.d.ts.map +1 -1
  34. package/dist/artifacts/scrub.js +53 -0
  35. package/dist/artifacts/step.d.ts +9 -0
  36. package/dist/artifacts/step.d.ts.map +1 -1
  37. package/dist/artifacts/step.js +9 -0
  38. package/dist/bakeoff/engine.d.ts +46 -4
  39. package/dist/bakeoff/engine.d.ts.map +1 -1
  40. package/dist/bakeoff/engine.js +269 -19
  41. package/dist/bakeoff/eval-cases.d.ts +5 -0
  42. package/dist/bakeoff/eval-cases.d.ts.map +1 -1
  43. package/dist/bakeoff/eval-cases.js +3 -0
  44. package/dist/bakeoff/judge.d.ts +39 -0
  45. package/dist/bakeoff/judge.d.ts.map +1 -0
  46. package/dist/bakeoff/judge.js +102 -0
  47. package/dist/bakeoff/quality.d.ts +44 -0
  48. package/dist/bakeoff/quality.d.ts.map +1 -0
  49. package/dist/bakeoff/quality.js +126 -0
  50. package/dist/bakeoff/reference.d.ts +34 -0
  51. package/dist/bakeoff/reference.d.ts.map +1 -0
  52. package/dist/bakeoff/reference.js +68 -0
  53. package/dist/bakeoff/replay.d.ts +25 -0
  54. package/dist/bakeoff/replay.d.ts.map +1 -1
  55. package/dist/bakeoff/replay.js +48 -1
  56. package/dist/bakeoff/report.d.ts +8 -1
  57. package/dist/bakeoff/report.d.ts.map +1 -1
  58. package/dist/bakeoff/report.js +83 -12
  59. package/dist/bakeoff/samples.d.ts +35 -2
  60. package/dist/bakeoff/samples.d.ts.map +1 -1
  61. package/dist/bakeoff/samples.js +67 -6
  62. package/dist/bakeoff/stats.d.ts +17 -1
  63. package/dist/bakeoff/stats.d.ts.map +1 -1
  64. package/dist/bakeoff/stats.js +67 -0
  65. package/dist/bakeoff/types.d.ts +76 -5
  66. package/dist/bakeoff/types.d.ts.map +1 -1
  67. package/dist/bakeoff/types.js +4 -3
  68. package/dist/cli-query.d.ts +14 -0
  69. package/dist/cli-query.d.ts.map +1 -0
  70. package/dist/cli-query.js +59 -0
  71. package/dist/cli.d.ts +2 -1
  72. package/dist/cli.d.ts.map +1 -1
  73. package/dist/cli.js +26 -23
  74. package/dist/config.d.ts +6 -0
  75. package/dist/config.d.ts.map +1 -1
  76. package/dist/config.js +14 -0
  77. package/dist/detect/detector.d.ts +82 -0
  78. package/dist/detect/detector.d.ts.map +1 -0
  79. package/dist/detect/detector.js +23 -0
  80. package/dist/index.d.ts +19 -14
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +38 -11
  83. package/dist/query/analytics.d.ts +84 -0
  84. package/dist/query/analytics.d.ts.map +1 -0
  85. package/dist/query/analytics.js +280 -0
  86. package/dist/query/columns.d.ts +59 -0
  87. package/dist/query/columns.d.ts.map +1 -0
  88. package/dist/query/columns.js +89 -0
  89. package/dist/query/grammar.d.ts +74 -7
  90. package/dist/query/grammar.d.ts.map +1 -1
  91. package/dist/query/grammar.js +179 -71
  92. package/dist/query/saved.d.ts +26 -0
  93. package/dist/query/saved.d.ts.map +1 -0
  94. package/dist/query/saved.js +51 -0
  95. package/dist/routes/loader.js +15 -10
  96. package/dist/routes/objective-loader.d.ts +35 -0
  97. package/dist/routes/objective-loader.d.ts.map +1 -0
  98. package/dist/routes/objective-loader.js +286 -0
  99. package/dist/run/accept.d.ts +43 -0
  100. package/dist/run/accept.d.ts.map +1 -0
  101. package/dist/run/accept.js +65 -0
  102. package/dist/run/cost.d.ts +13 -0
  103. package/dist/run/cost.d.ts.map +1 -1
  104. package/dist/run/cost.js +27 -0
  105. package/dist/run/dispatch.d.ts +24 -1
  106. package/dist/run/dispatch.d.ts.map +1 -1
  107. package/dist/run/dispatch.js +19 -0
  108. package/dist/run/ladder-key.d.ts +4 -0
  109. package/dist/run/ladder-key.d.ts.map +1 -1
  110. package/dist/run/ladder-key.js +3 -0
  111. package/dist/run/quality-gate.d.ts +39 -0
  112. package/dist/run/quality-gate.d.ts.map +1 -0
  113. package/dist/run/quality-gate.js +70 -0
  114. package/dist/run/raw.d.ts.map +1 -1
  115. package/dist/run/raw.js +22 -3
  116. package/dist/run/record-attempt.d.ts.map +1 -1
  117. package/dist/run/record-attempt.js +22 -1
  118. package/dist/run/routing-docs.d.ts.map +1 -1
  119. package/dist/run/routing-docs.js +11 -4
  120. package/dist/run/run.d.ts.map +1 -1
  121. package/dist/run/run.js +46 -39
  122. package/dist/settings/index.d.ts +1 -1
  123. package/dist/settings/index.d.ts.map +1 -1
  124. package/dist/settings/index.js +2 -1
  125. package/dist/settings/task-settings.d.ts +10 -0
  126. package/dist/settings/task-settings.d.ts.map +1 -1
  127. package/dist/settings/task-settings.js +17 -0
  128. package/dist/telemetry/attempt-meta.d.ts +178 -0
  129. package/dist/telemetry/attempt-meta.d.ts.map +1 -0
  130. package/dist/telemetry/attempt-meta.js +319 -0
  131. package/dist/telemetry/exporter.d.ts.map +1 -1
  132. package/dist/telemetry/exporter.js +6 -0
  133. package/dist/telemetry/ingest-protocol.d.ts +11 -0
  134. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  135. package/dist/telemetry/ingest-protocol.js +3 -0
  136. package/dist/telemetry/otel-map.d.ts +10 -0
  137. package/dist/telemetry/otel-map.d.ts.map +1 -1
  138. package/dist/telemetry/otel-map.js +64 -0
  139. package/dist/telemetry/store-migrations.d.ts.map +1 -1
  140. package/dist/telemetry/store-migrations.js +15 -0
  141. package/dist/telemetry/store.d.ts.map +1 -1
  142. package/dist/telemetry/store.js +25 -7
  143. package/dist/types/failures-teaching.d.ts.map +1 -1
  144. package/dist/types/failures-teaching.js +20 -0
  145. package/dist/types/failures.d.ts +1 -1
  146. package/dist/types/failures.d.ts.map +1 -1
  147. package/dist/types/failures.js +1 -0
  148. package/dist/types/provider.d.ts +9 -0
  149. package/dist/types/provider.d.ts.map +1 -1
  150. package/dist/types/quality.d.ts +41 -0
  151. package/dist/types/quality.d.ts.map +1 -0
  152. package/dist/types/quality.js +12 -0
  153. package/dist/types/route.d.ts +89 -0
  154. package/dist/types/route.d.ts.map +1 -1
  155. package/dist/types/telemetry.d.ts +13 -0
  156. package/dist/types/telemetry.d.ts.map +1 -1
  157. package/dist/validate-cli.d.ts.map +1 -1
  158. package/dist/validate-cli.js +10 -0
  159. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -10,6 +10,131 @@ published package diverge.
10
10
  This project adheres to [Semantic Versioning](https://semver.org/). Dates are
11
11
  UTC.
12
12
 
13
+ ## [0.7.0] — 2026-08-23
14
+
15
+ Released with the docs/registry mirror in one R-A ceremony. Headline: wrap a
16
+ pipeline in `rig.artifacts.run.start({ pipeline, episodeKey })` and that is the
17
+ whole instrumentation story — the SDK stamps `tags.run_id` on every call of both
18
+ lanes, so `/runs` AND `/projects` fill with no per-call tag plumbing.
19
+
20
+ Run-context tags (RC-1) — inside `rig.artifacts.run.start` / `run.scope`, the SDK
21
+ stamps `tags.run_id` (the run's `episodeKey`, else its id) on every attempt row of
22
+ both lanes when the caller passed none; a caller-supplied `run_id` always wins,
23
+ and outside a run context rows are byte-identical. The console `/projects` tree
24
+ joins a uuid-valued `run_id` by `runs.id` as well as by `episode_key`, so a
25
+ pipeline that calls `run.start({ pipeline })` alone now fills `/runs` AND
26
+ `/projects`. `ActiveRunContext.episodeKey` is exposed; `withRunIdTag` is the one
27
+ rule. ZERO DDL.
28
+
29
+ ## [0.6.0] — 2026-08-23
30
+
31
+ Released with the docs/registry mirror in one R-A ceremony. Headline for router
32
+ codebases: the candidate ladder can now fall through on **quality** (`qualityGate`
33
+ → `quality_rejected`), bake-offs measure **regret against a reference arm** under a
34
+ declared objective (legacy routes stay cost-first, byte-identical), the console
35
+ gains the **project tree** (`/projects`) and the **analytics API**
36
+ (`/v1/analytics/meta` + `/query`, metadata only — never content), and the
37
+ OpenAI-compatible proxy accepts the `developer` role. ZERO DDL across the line.
38
+
39
+ Regret-bounded bake-offs (RB-1, near-optimal) — the bake-off objective flips from
40
+ "cheapest that passes" to **near-optimal at a reasonable price**. New additive
41
+ route YAML: `objective` (`mode: quality-first | balanced | cost-first`,
42
+ `max_regret`, `price_ceiling`, `lambda`), `bakeoff` (`reference: auto | provider/model`,
43
+ `reference_envelope_usd`, `judge: { model, max_samples }`), and `value_checks`
44
+ (JSON-pointer assertions). Every bake-off can run a **reference (ceiling) arm**
45
+ and report each arm's **regret vs the reference with a seeded-bootstrap CI and n**.
46
+ Quality per sample resolves eval-case golden → `value_checks` → an opt-in capped,
47
+ metered judge (judge model ∈ declared candidates) → 1/0 conformance; it composes
48
+ with Q1's `qualityGate` (deterministic checks first, then the gate/judge). The
49
+ proposal gate is objective-aware: `cost-first` keeps Q1's gate byte-identical
50
+ (golden); a declared `quality-first`/`balanced` objective with no bound reports
51
+ regret but proposes nothing (no silent actuation). `modelrig bakeoff --objective
52
+ <mode>` overrides per run. The report/PR body state regret, cost and the ceiling
53
+ on one line. ZERO DDL. See `docs/bakeoffs.md` §5.
54
+
55
+ Analytics surface (WI-1b) — the read side of the attempt envelope. `GET
56
+ /v1/analytics/meta` describes every dimension and metric with its classification
57
+ and `since` version; `POST /v1/analytics/query` runs the SELECT-only grammar over
58
+ an org's inferences (metadata only — enums, numbers, hashes, identifiers; never
59
+ content; bake-off replay rows excluded). `modelrig query "<grammar>"` /
60
+ `--saved <id>` run the same grammar locally. Console: saved questions on
61
+ `/costs`, `/coverage`, `/routes/[route]`. OTLP-out gains `modelrig.meta.*` span
62
+ attributes for the metadata-safe envelope fields. Behaviour change: a free-text
63
+ `actor` is dropped with one warning (metadata-safe rule). New root exports:
64
+ `parseQuery`, `compileQuery`, `evaluateAnalyticsQuery`, `describeAnalyticsSchema`,
65
+ `planAnalyticsRead`, `runTelemetryQuery`, `SAVED_QUERIES`, `savedQueryById`, the
66
+ column registries and their types.
67
+ ZERO DDL.
68
+
69
+ Quality gate (#223, Q1) — the candidate ladder can now fall through on *quality*,
70
+ not just shape. Pass `rig.run(route, { qualityGate })` and a schema-valid output
71
+ your predicate rejects is recorded as a new `quality_rejected` failure (its own
72
+ retry budget, never the repair rung) and the ladder advances. The same predicate
73
+ scores bake-offs (`rig.bakeoff({ qualityGate })`) — a quality pass rate and an
74
+ `effectiveUsdPer1kPassing` metric beside the conformant one. SDK lane only: a
75
+ function cannot cross the hosted boundary; the route-declared hosted judge is Q2.
76
+
77
+ ### Added — quality gate (#223, Q1)
78
+
79
+ - **`RunOptions.qualityGate?`** — a builder predicate
80
+ `(output, meta) => { ok, score?, reason? }` (sync or async) evaluated on a
81
+ schema-VALID output before it is accepted. `ok:false` records a
82
+ `quality_rejected` attempt (message carries the reason + score), captures the
83
+ rejected text for replay, and falls the ladder through on the class's OWN retry
84
+ budget (`policy.retries.quality_rejected`, default 0) — the repair rung is NEVER
85
+ entered from this path. A throwing predicate rejects fail-closed (`reason:
86
+ "qualityGate threw: …"`, one warning per process). Tool-call turns and
87
+ `schema: null` routes are handled per spec §4.1. `runStream` inherits it.
88
+ - **`FailureClass "quality_rejected"`** — retryable (own non-fungible budget),
89
+ never terminal, with an errors-that-teach entry (`docsUrl`
90
+ → routing-reliability.html#quality_rejected) and a suggested `policy.retries`
91
+ edit. `policy.retries.quality_rejected` is accepted by the loader.
92
+ - **`RunMeta.quality?`** `{ score, verdict, judgedBy }` — present when a gate ran
93
+ (`verdict: "pass"` on a returned run; `judgedBy: null` for an SDK predicate).
94
+ SDK-owned `tags.quality` / `tags.quality_score` are stamped on the attempt row.
95
+ - **`rig.bakeoff` / `rig.bakeoffFromEvalCases` gain `qualityGate?`** — scored on
96
+ each replayed sample into `ReplaySampleResult.valueAccuracy` + `qualityPassed`;
97
+ `VariantMetrics` gains `qualityPassCi95` and `effectiveUsdPer1kPassing` (beside,
98
+ never redefining, `effectiveUsdPer1kConformant`), and `computeProposal` gains a
99
+ third clause (challenger quality-pass CI-lower ≥ incumbent's). The `modelrig
100
+ bakeoff` CLI cannot carry a function — use programmatic `rig.bakeoff` until Q2.
101
+ - **Exports:** `QualityGate`, `QualityVerdict`, `QualityGateMeta` from the root.
102
+ - **Boundary B1 rewritten** to "Route-declared judge (hosted lane)" (`not_built`,
103
+ tripwire `/\bjudge\??\s*:/`) now the SDK predicate ships; the routing-reliability
104
+ page, llms.txt (C11), README, `/definitions`, and the v4 setup prompt are swept
105
+ to match.
106
+
107
+ Project tree (G2b) — the console gains `/projects` (picker) and `/projects/[value]`
108
+ (project → run → step → call → attempts, cost + quality + lane on every node) over
109
+ one new read, `GET /v1/console/tree`. Server + console only; the SDK change below
110
+ is documentation. ZERO DDL.
111
+
112
+ - **`@step` / `step()` sets the ambient step key.** Documented in `step.ts` that
113
+ entering a step scope mints `label#n` and makes it the run context's ambient
114
+ step: an `artifact.save` with no explicit `stepKey` attaches to it — for saves
115
+ inside the scope AND for saves after it, until the next step mints a new key
116
+ (the ambient key is a running "most recent step", not a lexical bracket).
117
+ `run-context.ts` clarifies that within one run's parallel step waves only an
118
+ explicit `stepKey` prevents cross-attribution (`run.scope()` separates distinct
119
+ runs, not overlapping steps in one run).
120
+
121
+ Screening (B0 + B1) — two bug-class fixes and a shared detector contract.
122
+ - **Bake-off review samples are scrubbed before they persist.** A `--keep-outputs`
123
+ bake-off now runs each sample's input and output through the rig's content-custody
124
+ scrub mode (`detect` counts onto `arm_meta.scrub`, `redact` masks, `block` keeps a
125
+ metadata-only sample). Default `off` ⇒ rows byte-identical to before.
126
+ - **Proxy role enum.** `messages[].role` must be `system|developer|user|assistant|tool`
127
+ (`developer` normalises to the system slot); anything else is rejected with a
128
+ teaching message before dispatch (HTTP 422). Previously an arbitrary role was cast
129
+ through.
130
+ - **Raw-lane multi-turn encoding (behaviour change).** A genuine multi-turn transcript
131
+ is now flattened into the adapter's user prompt as one JSON object per line instead
132
+ of `role: content` text, so a crafted turn can never read as a synthetic system
133
+ line. Single-user-turn (and system + single user) requests are byte-identical.
134
+ - **Shared `Detector` interface.** `Detector`, `DetectionSpan`, `detectSpans`, and
135
+ `scrubDetectors` are exported from the package root; `scrubContent` is unchanged.
136
+ `buildBakeoffSamples` gains a scrub-resolver argument.
137
+
13
138
  ## [0.5.0] — 2026-08-22
14
139
 
15
140
  Released with the docs/registry mirror in one R-A ceremony. Headline for router
package/README.md CHANGED
@@ -145,7 +145,8 @@ provider's list price (zero ModelRig margin) and throws a typed `RigFailureError
145
145
  on failure, exactly like `rig.run`.
146
146
 
147
147
  Called inside a `rig.artifacts.run.start` context, `runRaw`/`runRawStream` also
148
- records a ground-truth **step** in the run's graph (`/runs/[id]`) — since
148
+ records a ground-truth **step** in the run's graph (`/runs/[id]`) and carries the
149
+ run's `run_id` tag — since
149
150
  modelrig 0.5.0, on success and on failure. Name it with `stepKey` (the same field
150
151
  `RunOptions` exposes on the routed lane; precedence `stepKey` > `tags.step` > an
151
152
  auto `provider/model#n` key), so a raw call and the artifacts you `save` around it
@@ -179,9 +180,55 @@ Every attempt is recorded in a local SQLite file (`.modelrig/telemetry.db`)
179
180
  tagged with your `tags`. Failures are typed —
180
181
  `RigFailureError.failure.class` is one of `content_invalid`, `capacity_shed`,
181
182
  `network`, `refusal`, `cache_invalid`, `timeout`, `config_auth`, `budget_exhausted`,
182
- `invariant_violation` — with a separate retry budget per class, so a flaky
183
+ `invariant_violation`, `quality_rejected` — with a separate retry budget per class, so a flaky
183
184
  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).
184
185
 
186
+ ## Metadata capture ("capture now, analyze later")
187
+
188
+ Every inference records a versioned, **metadata-safe** attempt envelope
189
+ (`AttemptMeta@v1`) on `inferences.meta` — in both the routed and the raw lane,
190
+ from the same producer. It is the metadata the adapters already see and used to
191
+ discard: finish reason, refusal, reasoning tokens, the provider-reported model
192
+ snapshot and request id, coarse HTTP status / error code, sampling echo, tool
193
+ names (declared kept, undeclared hashed) with counts, per-component cost, request
194
+ and output **hashes** (never the content), request/response byte sizes, response
195
+ char count, and a coarse context bucket.
196
+
197
+ **It never contains free text.** Every field is an enum, a number, a boolean, a
198
+ hash, or an identifier. The producer drops any string that is over 128 chars or
199
+ contains whitespace, and the hosted ingest endpoint re-applies the same rule as a
200
+ backstop — so analysis later is possible without prompts or outputs ever being
201
+ stored here. Building the envelope never blocks or changes a run: on any failure
202
+ it is simply absent (`meta` is null).
203
+
204
+ Runs carry a companion `RunMeta@v1` on `runs.meta`, set at `run.start`:
205
+
206
+ ```ts
207
+ const h = rig.run.start({
208
+ pipeline: "nightly-report",
209
+ meta: { v: 1, trigger: "cron", actor: "scheduler", config_hash: "…" },
210
+ // pipeline_sha auto-fills from git when you don't set it (never fails the run)
211
+ });
212
+ ```
213
+
214
+ ### Reserved dimensions
215
+
216
+ Business dimensions live in `tags`. Five keys are **well-known** and become
217
+ first-class group-bys in cost and coverage views — use them verbatim so your data
218
+ lines up with the console's built-ins:
219
+
220
+ | key | meaning |
221
+ |---|---|
222
+ | `subject` | the customer/tenant the work is for — an **opaque** id, hashed if it looks like PII |
223
+ | `feature` | the product feature the call serves |
224
+ | `user` | the end user (an opaque id) |
225
+ | `session` | the conversation/session the call belongs to |
226
+ | `cost_center` | the team/budget to attribute spend to |
227
+
228
+ `modelrig validate` warns on near-misses — a declared `rig.yaml` dimension named
229
+ `customer`, `tenant`, `user_id`, `team`, … is nudged toward the reserved key it
230
+ resembles. They stay ordinary tags; nothing is required.
231
+
185
232
  ## CLI
186
233
 
187
234
  The package ships a `modelrig` binary:
@@ -217,8 +264,11 @@ on the quickstart:
217
264
  - **`modelrig serve` needs the `modelrig-server` package** — it is not bundled
218
265
  here.
219
266
  - **What the ladder does and does not do.** Fall-through is structural and
220
- infrastructural, never a quality judgment; the full boundary (quality gate,
221
- raw-lane structure, latency ordering…) is generated and tripwire-tested on
267
+ infrastructural, plus an OPTIONAL SDK-lane `qualityGate` predicate you supply
268
+ (a schema-valid output it rejects becomes
269
+ `quality_rejected`); the route-declared
270
+ hosted judge is not built yet, and the full boundary (raw-lane structure, latency
271
+ ordering…) is generated and tripwire-tested on
222
272
  [Routing & reliability](https://modelrig.dev/routing-reliability.html#10-not-yet-implemented-the-boundary).
223
273
 
224
274
  ## License
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/adapters/anthropic.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/adapters/anthropic.ts"],"names":[],"mappings":"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"}
@@ -22,6 +22,7 @@ exports.normalizeAnthropicInputUsage = normalizeAnthropicInputUsage;
22
22
  exports.createAnthropicAdapter = createAnthropicAdapter;
23
23
  const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
24
24
  const tool_mapping_1 = require("./tool-mapping");
25
+ const attempt_meta_map_1 = require("./attempt-meta-map");
25
26
  const sampling_1 = require("./sampling");
26
27
  const DEFAULT_MAX_TOKENS = 32000;
27
28
  const OUTPUT_TOOL_NAME = "route_output";
@@ -159,6 +160,8 @@ function createAnthropicAdapter(config) {
159
160
  supportsTools: () => true,
160
161
  async call(req) {
161
162
  const model = req.candidate.model;
163
+ // WI-1: native attempt envelope for this call (see openai.ts for the pattern).
164
+ let attemptMeta;
162
165
  // F1: post-stream failures still billed tokens — attach usage.
163
166
  const fail = (cls, message, fixHint, usage) => ({
164
167
  ok: false,
@@ -170,6 +173,7 @@ function createAnthropicAdapter(config) {
170
173
  ...(fixHint !== undefined ? { fixHint } : {}),
171
174
  },
172
175
  ...(usage !== undefined ? { usage } : {}),
176
+ ...(attemptMeta !== undefined ? { meta: attemptMeta } : {}),
173
177
  });
174
178
  const wantJson = req.outputSchema !== null;
175
179
  try {
@@ -210,9 +214,20 @@ function createAnthropicAdapter(config) {
210
214
  };
211
215
  let completionTokens = 0;
212
216
  let stopReason = null;
217
+ // WI-1 native fields: message.id + message.model arrive at message_start.
218
+ let responseModel = "";
219
+ let responseId = "";
213
220
  for await (const event of events) {
214
- if (event.type === "message_start" && event.message?.usage !== undefined) {
215
- inputUsage = normalizeAnthropicInputUsage(event.message.usage);
221
+ if (event.type === "message_start") {
222
+ if (event.message?.usage !== undefined) {
223
+ inputUsage = normalizeAnthropicInputUsage(event.message.usage);
224
+ }
225
+ if (responseModel === "" && typeof event.message?.model === "string") {
226
+ responseModel = event.message.model;
227
+ }
228
+ if (responseId === "" && typeof event.message?.id === "string") {
229
+ responseId = event.message.id;
230
+ }
216
231
  }
217
232
  else if (event.type === "content_block_start" &&
218
233
  wantTools &&
@@ -257,13 +272,25 @@ function createAnthropicAdapter(config) {
257
272
  };
258
273
  // Tool-call turn (spec §4.4): `arguments` is the raw JSON string the
259
274
  // model streamed (OpenAI parity) — text may be empty, skip the guards.
260
- if (wantTools && toolBlocks.size > 0) {
261
- const toolCalls = [...toolBlocks.values()].map((b, i) => ({
275
+ const toolCalls = wantTools && toolBlocks.size > 0
276
+ ? [...toolBlocks.values()].map((b, i) => ({
262
277
  id: b.id !== "" ? b.id : `call_${i}`,
263
278
  type: "function",
264
279
  function: { name: b.name, arguments: b.json === "" ? "{}" : b.json },
265
- }));
266
- return { ok: true, text, usage, toolCalls };
280
+ }))
281
+ : undefined;
282
+ // WI-1: native attempt envelope. refusal derives from stop_reason=refusal.
283
+ attemptMeta = (0, attempt_meta_map_1.buildAdapterMeta)("anthropic", {
284
+ req,
285
+ finishReasonRaw: stopReason,
286
+ refusal: stopReason === "refusal",
287
+ modelSnapshot: responseModel,
288
+ providerRequestId: responseId,
289
+ toolCalls,
290
+ streaming: false,
291
+ });
292
+ if (toolCalls !== undefined) {
293
+ return { ok: true, text, usage, toolCalls, meta: attemptMeta };
267
294
  }
268
295
  if (stopReason === "refusal") {
269
296
  return fail("refusal", "Anthropic refused this request (stop_reason=refusal)", undefined, usage);
@@ -274,11 +301,12 @@ function createAnthropicAdapter(config) {
274
301
  if (stopReason === "max_tokens" && wantJson) {
275
302
  return fail("content_invalid", "Anthropic structured output truncated (stop_reason=max_tokens)", "raise maxOutputTokens for this route", usage);
276
303
  }
277
- return { ok: true, text, usage };
304
+ return { ok: true, text, usage, meta: attemptMeta };
278
305
  }
279
306
  catch (error) {
280
307
  const message = error instanceof Error ? error.message : String(error);
281
308
  const { cls, fixHint } = classifyAnthropicFailure(message);
309
+ attemptMeta = (0, attempt_meta_map_1.transportErrorMeta)(error);
282
310
  return {
283
311
  ok: false,
284
312
  failure: {
@@ -289,6 +317,7 @@ function createAnthropicAdapter(config) {
289
317
  cause: error,
290
318
  ...(fixHint !== undefined ? { fixHint } : {}),
291
319
  },
320
+ meta: attemptMeta,
292
321
  };
293
322
  }
294
323
  },
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Per-adapter NATIVE metadata mapping (WI-1, spec §2). ONE place that turns a
3
+ * provider's native finish/refusal/usage/id signals into the additive
4
+ * `AdapterResult.meta` (`Partial<AttemptMetaV1>`). Adapters map ONLY what the
5
+ * provider reports; the producer (`telemetry/attempt-meta.ts`) adds
6
+ * hashes/sizes/cost and enforces the classification rule. Absent = unknown;
7
+ * nothing here is inferred.
8
+ *
9
+ * `refusal` derivation, per adapter:
10
+ * - openai / grok / deepseek / deepinfra / fireworks (OpenAI dialect): the
11
+ * stream carried a non-empty `choices[].delta.refusal` string, OR
12
+ * `finish_reason === "content_filter"`.
13
+ * - anthropic: `stop_reason === "refusal"`.
14
+ * - gemini: `finishReason` is `SAFETY` / `PROHIBITED_CONTENT` / `RECITATION`.
15
+ * The refusal TEXT itself is never carried (content) — only the boolean.
16
+ */
17
+ import type { AttemptMetaV1, FinishReason } from "../telemetry/attempt-meta";
18
+ import type { AdapterRequest, ProviderId, ToolCall } from "../types/provider";
19
+ /** Map a provider's native finish/stop reason to the `FinishReason` enum
20
+ * (spec §2). Unknown/absent ⇒ undefined (never inferred); an unrecognised
21
+ * non-empty value ⇒ `"other"`. */
22
+ export declare function mapFinishReason(provider: ProviderId, raw: string | null | undefined): FinishReason | undefined;
23
+ /** The declared sampling echo (spec §2 `sampling`) — the values ModelRig sent,
24
+ * read back from the request. topP → top_p, maxOutputTokens → max_output_tokens
25
+ * (the raw lane's top-level or the routed lane's `sampling` block). Numbers
26
+ * only; undefined when the caller declared none. */
27
+ export declare function samplingEcho(req: AdapterRequest): {
28
+ temperature?: number;
29
+ top_p?: number;
30
+ max_output_tokens?: number;
31
+ } | undefined;
32
+ /** Aggregate normalized tool calls into `{ name, count }` (RAW names — the
33
+ * producer hashes undeclared ones). Undefined when the turn made none. */
34
+ export declare function aggregateToolCalls(toolCalls: readonly ToolCall[] | undefined): {
35
+ name: string;
36
+ count: number;
37
+ }[] | undefined;
38
+ /** The signals an adapter collects on ONE completed call. All optional. */
39
+ export interface AdapterMetaSignals {
40
+ readonly req: AdapterRequest;
41
+ readonly finishReasonRaw?: string | null;
42
+ readonly refusal?: boolean;
43
+ readonly reasoningTokens?: number;
44
+ readonly modelSnapshot?: string;
45
+ readonly providerRequestId?: string;
46
+ readonly region?: string;
47
+ readonly toolCalls?: readonly ToolCall[];
48
+ readonly grounding?: {
49
+ citations?: number;
50
+ searchProvider?: string;
51
+ };
52
+ /** true = the run loop streamed deltas (stream()); false/absent = buffered. */
53
+ readonly streaming?: boolean;
54
+ }
55
+ /** Build one adapter's NATIVE `meta` (spec §2). Only defined signals appear —
56
+ * everything else is unknown and omitted. The producer takes it from here. */
57
+ export declare function buildAdapterMeta(provider: ProviderId, s: AdapterMetaSignals): Partial<AttemptMetaV1>;
58
+ /** A transport/SDK error's coarse envelope (spec §2 `http_status`/`error_code`).
59
+ * Reads `error.status` (number) and a short `error.code` / `error.error.code`
60
+ * slug — NEVER the message (that is free text). The producer's classification
61
+ * rule drops a code that turns out to be free text. */
62
+ export declare function transportErrorMeta(error: unknown): Partial<AttemptMetaV1>;
63
+ //# sourceMappingURL=attempt-meta-map.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ /**
3
+ * Per-adapter NATIVE metadata mapping (WI-1, spec §2). ONE place that turns a
4
+ * provider's native finish/refusal/usage/id signals into the additive
5
+ * `AdapterResult.meta` (`Partial<AttemptMetaV1>`). Adapters map ONLY what the
6
+ * provider reports; the producer (`telemetry/attempt-meta.ts`) adds
7
+ * hashes/sizes/cost and enforces the classification rule. Absent = unknown;
8
+ * nothing here is inferred.
9
+ *
10
+ * `refusal` derivation, per adapter:
11
+ * - openai / grok / deepseek / deepinfra / fireworks (OpenAI dialect): the
12
+ * stream carried a non-empty `choices[].delta.refusal` string, OR
13
+ * `finish_reason === "content_filter"`.
14
+ * - anthropic: `stop_reason === "refusal"`.
15
+ * - gemini: `finishReason` is `SAFETY` / `PROHIBITED_CONTENT` / `RECITATION`.
16
+ * The refusal TEXT itself is never carried (content) — only the boolean.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.mapFinishReason = mapFinishReason;
20
+ exports.samplingEcho = samplingEcho;
21
+ exports.aggregateToolCalls = aggregateToolCalls;
22
+ exports.buildAdapterMeta = buildAdapterMeta;
23
+ exports.transportErrorMeta = transportErrorMeta;
24
+ /** Map a provider's native finish/stop reason to the `FinishReason` enum
25
+ * (spec §2). Unknown/absent ⇒ undefined (never inferred); an unrecognised
26
+ * non-empty value ⇒ `"other"`. */
27
+ function mapFinishReason(provider, raw) {
28
+ if (raw === null || raw === undefined || raw === "")
29
+ return undefined;
30
+ if (provider === "anthropic") {
31
+ switch (raw) {
32
+ case "end_turn":
33
+ case "stop_sequence":
34
+ return "stop";
35
+ case "max_tokens":
36
+ return "length";
37
+ case "tool_use":
38
+ return "tool_calls";
39
+ case "refusal":
40
+ return "refusal";
41
+ default:
42
+ return "other";
43
+ }
44
+ }
45
+ if (provider === "gemini") {
46
+ switch (raw) {
47
+ case "STOP":
48
+ return "stop";
49
+ case "MAX_TOKENS":
50
+ return "length";
51
+ case "SAFETY":
52
+ case "PROHIBITED_CONTENT":
53
+ case "RECITATION":
54
+ return "content_filter";
55
+ default:
56
+ return "other";
57
+ }
58
+ }
59
+ // OpenAI dialect (openai / grok / deepseek / deepinfra / fireworks).
60
+ switch (raw) {
61
+ case "stop":
62
+ return "stop";
63
+ case "length":
64
+ return "length";
65
+ case "tool_calls":
66
+ case "function_call":
67
+ return "tool_calls";
68
+ case "content_filter":
69
+ return "content_filter";
70
+ default:
71
+ return "other";
72
+ }
73
+ }
74
+ /** The declared sampling echo (spec §2 `sampling`) — the values ModelRig sent,
75
+ * read back from the request. topP → top_p, maxOutputTokens → max_output_tokens
76
+ * (the raw lane's top-level or the routed lane's `sampling` block). Numbers
77
+ * only; undefined when the caller declared none. */
78
+ function samplingEcho(req) {
79
+ const s = {};
80
+ if (typeof req.sampling?.temperature === "number")
81
+ s.temperature = req.sampling.temperature;
82
+ if (typeof req.sampling?.topP === "number")
83
+ s.top_p = req.sampling.topP;
84
+ const maxOut = req.sampling?.maxOutputTokens ?? req.maxOutputTokens;
85
+ if (typeof maxOut === "number")
86
+ s.max_output_tokens = maxOut;
87
+ return Object.keys(s).length > 0 ? s : undefined;
88
+ }
89
+ /** Aggregate normalized tool calls into `{ name, count }` (RAW names — the
90
+ * producer hashes undeclared ones). Undefined when the turn made none. */
91
+ function aggregateToolCalls(toolCalls) {
92
+ if (toolCalls === undefined || toolCalls.length === 0)
93
+ return undefined;
94
+ const counts = new Map();
95
+ for (const tc of toolCalls) {
96
+ const name = tc.function.name;
97
+ counts.set(name, (counts.get(name) ?? 0) + 1);
98
+ }
99
+ return [...counts.entries()].map(([name, count]) => ({ name, count }));
100
+ }
101
+ /** Build one adapter's NATIVE `meta` (spec §2). Only defined signals appear —
102
+ * everything else is unknown and omitted. The producer takes it from here. */
103
+ function buildAdapterMeta(provider, s) {
104
+ const meta = {};
105
+ const finish = mapFinishReason(provider, s.finishReasonRaw);
106
+ if (finish !== undefined)
107
+ meta.finish_reason = finish;
108
+ if (typeof s.refusal === "boolean")
109
+ meta.refusal = s.refusal;
110
+ if (typeof s.reasoningTokens === "number" && s.reasoningTokens > 0) {
111
+ meta.reasoning_tokens = s.reasoningTokens;
112
+ }
113
+ if (typeof s.modelSnapshot === "string" && s.modelSnapshot !== "")
114
+ meta.model_snapshot = s.modelSnapshot;
115
+ if (typeof s.providerRequestId === "string" && s.providerRequestId !== "") {
116
+ meta.provider_request_id = s.providerRequestId;
117
+ }
118
+ if (typeof s.region === "string" && s.region !== "")
119
+ meta.region = s.region;
120
+ const sampling = samplingEcho(s.req);
121
+ if (sampling !== undefined)
122
+ meta.sampling = sampling;
123
+ const tools = aggregateToolCalls(s.toolCalls);
124
+ if (tools !== undefined)
125
+ meta.tool_calls = tools;
126
+ if (s.grounding !== undefined) {
127
+ const g = {};
128
+ if (typeof s.grounding.citations === "number")
129
+ g.citations = s.grounding.citations;
130
+ if (typeof s.grounding.searchProvider === "string")
131
+ g.search_provider = s.grounding.searchProvider;
132
+ if (Object.keys(g).length > 0)
133
+ meta.grounding = g;
134
+ }
135
+ if (typeof s.streaming === "boolean")
136
+ meta.streaming = s.streaming;
137
+ return meta;
138
+ }
139
+ /** A transport/SDK error's coarse envelope (spec §2 `http_status`/`error_code`).
140
+ * Reads `error.status` (number) and a short `error.code` / `error.error.code`
141
+ * slug — NEVER the message (that is free text). The producer's classification
142
+ * rule drops a code that turns out to be free text. */
143
+ function transportErrorMeta(error) {
144
+ const meta = {};
145
+ if (typeof error === "object" && error !== null) {
146
+ const e = error;
147
+ if (typeof e.status === "number" && Number.isFinite(e.status))
148
+ meta.http_status = e.status;
149
+ const code = typeof e.code === "string" ? e.code : e.error?.code;
150
+ if (typeof code === "string" && code !== "")
151
+ meta.error_code = code;
152
+ }
153
+ return meta;
154
+ }
@@ -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,CAkC1F;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;AAQhG,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,CAoLpF"}
@@ -24,6 +24,7 @@ exports.createDeepSeekAdapter = createDeepSeekAdapter;
24
24
  const openai_1 = __importDefault(require("openai"));
25
25
  const tool_mapping_1 = require("./tool-mapping");
26
26
  const openai_stream_1 = require("./openai-stream");
27
+ const attempt_meta_map_1 = require("./attempt-meta-map");
27
28
  const sampling_1 = require("./sampling");
28
29
  const DEEPSEEK_BASE_URL = "https://api.deepseek.com";
29
30
  const DEFAULT_MAX_TOKENS = 64000; // R1/V3 max — full capacity for long reports (ported)
@@ -102,6 +103,8 @@ function createDeepSeekAdapter(config) {
102
103
  supportsTools: () => true,
103
104
  async call(req) {
104
105
  const model = req.candidate.model;
106
+ // WI-1: native attempt envelope for this call (see openai.ts for the pattern).
107
+ let attemptMeta;
105
108
  // F1: post-stream failures still billed tokens — attach usage.
106
109
  const fail = (cls, message, fixHint, usage) => ({
107
110
  ok: false,
@@ -113,6 +116,7 @@ function createDeepSeekAdapter(config) {
113
116
  ...(fixHint !== undefined ? { fixHint } : {}),
114
117
  },
115
118
  ...(usage !== undefined ? { usage } : {}),
119
+ ...(attemptMeta !== undefined ? { meta: attemptMeta } : {}),
116
120
  });
117
121
  try {
118
122
  const client = new openai_1.default({
@@ -130,12 +134,18 @@ function createDeepSeekAdapter(config) {
130
134
  let finishReason = null;
131
135
  // TTFB (Phase 4 WS3): first streamed chunk = first body byte.
132
136
  let ttfbMs;
137
+ let responseModel = "";
138
+ let responseId = "";
133
139
  const toolCallAcc = (0, tool_mapping_1.createOpenAIToolCallAccumulator)();
134
140
  // Only delta.content is accumulated — a reasoner's reasoning_content
135
141
  // (chain-of-thought) must never leak into the JSON buffer (ported).
136
142
  for await (const chunk of stream) {
137
143
  if (ttfbMs === undefined)
138
144
  ttfbMs = Date.now() - dispatchedAt;
145
+ if (responseModel === "" && typeof chunk.model === "string")
146
+ responseModel = chunk.model;
147
+ if (responseId === "" && typeof chunk.id === "string")
148
+ responseId = chunk.id;
139
149
  const choice = chunk.choices[0];
140
150
  if (choice?.delta?.content)
141
151
  text += choice.delta.content;
@@ -159,8 +169,19 @@ function createDeepSeekAdapter(config) {
159
169
  };
160
170
  // Tool-call turn (spec §4.4): surface normalized calls, skip content guards.
161
171
  const toolCalls = toolCallAcc.finalize();
172
+ // WI-1: native attempt envelope. DeepSeek has no `refusal` delta — a
173
+ // refusal surfaces as finish_reason content_filter.
174
+ attemptMeta = (0, attempt_meta_map_1.buildAdapterMeta)("deepseek", {
175
+ req,
176
+ finishReasonRaw: finishReason,
177
+ refusal: finishReason === "content_filter",
178
+ modelSnapshot: responseModel,
179
+ providerRequestId: responseId,
180
+ toolCalls,
181
+ streaming: false,
182
+ });
162
183
  if (toolCalls !== undefined) {
163
- return { ok: true, text, usage, toolCalls };
184
+ return { ok: true, text, usage, toolCalls, meta: attemptMeta };
164
185
  }
165
186
  if (text.trim().length === 0) {
166
187
  return fail("content_invalid", `empty response from DeepSeek (finish_reason=${finishReason ?? "none"})`, undefined, usage);
@@ -168,11 +189,12 @@ function createDeepSeekAdapter(config) {
168
189
  if (finishReason === "length" && req.outputSchema !== null) {
169
190
  return fail("content_invalid", "DeepSeek JSON output truncated (finish_reason=length)", "raise maxOutputTokens for this route", usage);
170
191
  }
171
- return { ok: true, text, usage };
192
+ return { ok: true, text, usage, meta: attemptMeta };
172
193
  }
173
194
  catch (error) {
174
195
  const message = error instanceof Error ? error.message : String(error);
175
196
  const { cls, fixHint } = classifyDeepSeekFailure(message);
197
+ attemptMeta = (0, attempt_meta_map_1.transportErrorMeta)(error);
176
198
  return {
177
199
  ok: false,
178
200
  failure: {
@@ -183,6 +205,7 @@ function createDeepSeekAdapter(config) {
183
205
  cause: error,
184
206
  ...(fixHint !== undefined ? { fixHint } : {}),
185
207
  },
208
+ meta: attemptMeta,
186
209
  };
187
210
  }
188
211
  },