modelrig 0.5.0 → 0.6.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 +109 -0
  2. package/README.md +52 -3
  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 +15 -3
  30. package/dist/artifacts/run-context.d.ts.map +1 -1
  31. package/dist/artifacts/run-context.js +49 -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 +17 -0
  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 +40 -36
  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
@@ -26,6 +26,7 @@ exports.resolveGeminiText = resolveGeminiText;
26
26
  exports.createGeminiAdapter = createGeminiAdapter;
27
27
  const genai_1 = require("@google/genai");
28
28
  const tool_mapping_1 = require("./tool-mapping");
29
+ const attempt_meta_map_1 = require("./attempt-meta-map");
29
30
  const sampling_1 = require("./sampling");
30
31
  /** Models that accept thinkingConfig — passing it to 2.x models 400s.
31
32
  * Ported from the production THINKING_CAPABLE_MODELS set. EXPORTED (CE-9 R6) so
@@ -240,6 +241,8 @@ function createGeminiAdapter(config) {
240
241
  const model = req.candidate.model;
241
242
  const abortController = new AbortController();
242
243
  const timeoutId = setTimeout(() => abortController.abort(), req.timeoutMs);
244
+ // WI-1: native attempt envelope for this call (see openai.ts for the pattern).
245
+ let attemptMeta;
243
246
  // F1: post-response failures (refusal/empty/truncation) still billed
244
247
  // tokens — they attach the response usage so the run loop accounts them.
245
248
  const fail = (cls, message, fixHint, usage) => ({
@@ -252,6 +255,7 @@ function createGeminiAdapter(config) {
252
255
  ...(fixHint !== undefined ? { fixHint } : {}),
253
256
  },
254
257
  ...(usage !== undefined ? { usage } : {}),
258
+ ...(attemptMeta !== undefined ? { meta: attemptMeta } : {}),
255
259
  });
256
260
  // CE-2 F3: reject reasoning on a non-thinking model BEFORE any dispatch —
257
261
  // classified, terminal, never a silent drop (spec §3 F3).
@@ -282,6 +286,24 @@ function createGeminiAdapter(config) {
282
286
  const usage = { tokensIn, tokensOut, tokensCached, servedTier, ttfbMs };
283
287
  const candidates = response.candidates;
284
288
  const finishReason = candidates?.[0]?.finishReason;
289
+ // WI-1 native fields: provider-reported model snapshot + request id.
290
+ const respIds = response;
291
+ // WI-1: native attempt envelope. refusal derives from the safety/recitation
292
+ // finishReasons (mapFinishReason folds them to content_filter). grounding
293
+ // present when this route asked for native grounding (googleSearch).
294
+ const mkMeta = (toolCalls) => (0, attempt_meta_map_1.buildAdapterMeta)("gemini", {
295
+ req,
296
+ finishReasonRaw: finishReason,
297
+ refusal: finishReason === "SAFETY" ||
298
+ finishReason === "PROHIBITED_CONTENT" ||
299
+ finishReason === "RECITATION",
300
+ reasoningTokens: usageMetadata.thoughtsTokenCount,
301
+ modelSnapshot: respIds.modelVersion,
302
+ providerRequestId: respIds.responseId,
303
+ toolCalls,
304
+ grounding: req.grounding === true ? { searchProvider: "google" } : undefined,
305
+ streaming: false,
306
+ });
285
307
  // Tool-call turn (spec §4.4): collect functionCall parts. When present,
286
308
  // surface normalized calls and skip the text/empty/truncation guards.
287
309
  // Read any accompanying text from the parts (not the `response.text`
@@ -297,9 +319,12 @@ function createGeminiAdapter(config) {
297
319
  .map((p) => p.text)
298
320
  .filter((t) => typeof t === "string")
299
321
  .join("");
300
- return { ok: true, text: toolText, usage, toolCalls };
322
+ attemptMeta = mkMeta(toolCalls);
323
+ return { ok: true, text: toolText, usage, toolCalls, meta: attemptMeta };
301
324
  }
302
325
  }
326
+ // Non-tool paths share one envelope (refusal / empty / truncation / ok).
327
+ attemptMeta = mkMeta();
303
328
  if (finishReason === "SAFETY" || finishReason === "PROHIBITED_CONTENT") {
304
329
  return fail("refusal", `Gemini blocked output via safety filter (finishReason=${finishReason})`, undefined, usage);
305
330
  }
@@ -325,10 +350,11 @@ function createGeminiAdapter(config) {
325
350
  // A truncated structured emission is mid-object and unparseable.
326
351
  return fail("content_invalid", "Gemini structured output truncated (finishReason=MAX_TOKENS)", "raise maxOutputTokens for this route or reduce thinking budget", usage);
327
352
  }
328
- return { ok: true, text, usage };
353
+ return { ok: true, text, usage, meta: attemptMeta };
329
354
  }
330
355
  catch (error) {
331
356
  const message = error instanceof Error ? error.message : String(error);
357
+ attemptMeta = (0, attempt_meta_map_1.transportErrorMeta)(error);
332
358
  // An abort we triggered is our timeout regardless of message text.
333
359
  if (abortController.signal.aborted) {
334
360
  return fail("timeout", `Gemini call exceeded timeoutMs=${req.timeoutMs}: ${message}`);
@@ -344,6 +370,7 @@ function createGeminiAdapter(config) {
344
370
  cause: error,
345
371
  ...(fixHint !== undefined ? { fixHint } : {}),
346
372
  },
373
+ meta: attemptMeta,
347
374
  };
348
375
  }
349
376
  finally {
@@ -1 +1 @@
1
- {"version":3,"file":"grok.d.ts","sourceRoot":"","sources":["../../src/adapters/grok.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAIV,cAAc,EACd,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAMhE,OAAO,KAAK,EAAuB,cAAc,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAOhG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAKD,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAc7E;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CA+BtE;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE;QAAE,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAA;KAAE,CAAC;IAC1D,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;8EAC0E;IAC1E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;4DAGwD;IACxD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC3C,QAAQ,CAAC,eAAe,CAAC,EACrB;QAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;KAAE,GAChC;QACE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;QAC7B,QAAQ,CAAC,WAAW,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;SAAE,CAAC;KACjG,CAAC;IACN,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,gBAAgB,CAmDlF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,eAAe,CAgK5E"}
1
+ {"version":3,"file":"grok.d.ts","sourceRoot":"","sources":["../../src/adapters/grok.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAIV,cAAc,EACd,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAMhE,OAAO,KAAK,EAAuB,cAAc,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAShG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAKD,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAc7E;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CA+BtE;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE;QAAE,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAA;KAAE,CAAC;IAC1D,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;8EAC0E;IAC1E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;4DAGwD;IACxD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC3C,QAAQ,CAAC,eAAe,CAAC,EACrB;QAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;KAAE,GAChC;QACE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;QAC7B,QAAQ,CAAC,WAAW,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;SAAE,CAAC;KACjG,CAAC;IACN,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,gBAAgB,CAmDlF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,eAAe,CAsL5E"}
@@ -22,6 +22,7 @@ exports.createGrokAdapter = createGrokAdapter;
22
22
  const openai_1 = __importDefault(require("openai"));
23
23
  const tool_mapping_1 = require("./tool-mapping");
24
24
  const openai_stream_1 = require("./openai-stream");
25
+ const attempt_meta_map_1 = require("./attempt-meta-map");
25
26
  const sampling_1 = require("./sampling");
26
27
  const raw_support_1 = require("../run/raw-support");
27
28
  const GROK_BASE_URL = "https://api.x.ai/v1";
@@ -135,6 +136,8 @@ function createGrokAdapter(config) {
135
136
  supportsTools: () => true,
136
137
  async call(req) {
137
138
  const model = req.candidate.model;
139
+ // WI-1: native attempt envelope for this call (see openai.ts for the pattern).
140
+ let attemptMeta;
138
141
  // F1: post-stream failures still billed tokens — attach usage.
139
142
  const fail = (cls, message, fixHint, usage) => ({
140
143
  ok: false,
@@ -146,6 +149,7 @@ function createGrokAdapter(config) {
146
149
  ...(fixHint !== undefined ? { fixHint } : {}),
147
150
  },
148
151
  ...(usage !== undefined ? { usage } : {}),
152
+ ...(attemptMeta !== undefined ? { meta: attemptMeta } : {}),
149
153
  });
150
154
  try {
151
155
  const client = new openai_1.default({
@@ -161,10 +165,16 @@ function createGrokAdapter(config) {
161
165
  let cachedTokens = 0;
162
166
  let cacheWriteTokens = 0;
163
167
  let finishReason = null;
168
+ let responseModel = "";
169
+ let responseId = "";
164
170
  const toolCallAcc = (0, tool_mapping_1.createOpenAIToolCallAccumulator)();
165
171
  // Only delta.content is accumulated — a reasoning model's trace must
166
172
  // never leak into the JSON buffer (deepseek reasoner rule applied).
167
173
  for await (const chunk of stream) {
174
+ if (responseModel === "" && typeof chunk.model === "string")
175
+ responseModel = chunk.model;
176
+ if (responseId === "" && typeof chunk.id === "string")
177
+ responseId = chunk.id;
168
178
  const choice = chunk.choices[0];
169
179
  if (choice?.delta?.content)
170
180
  text += choice.delta.content;
@@ -190,8 +200,19 @@ function createGrokAdapter(config) {
190
200
  // Tool-call turn (spec §4.4): surface normalized calls, skip the
191
201
  // empty/truncation content guards below.
192
202
  const toolCalls = toolCallAcc.finalize();
203
+ // WI-1: native attempt envelope (no `refusal` delta on this dialect →
204
+ // refusal derives from finish_reason content_filter).
205
+ attemptMeta = (0, attempt_meta_map_1.buildAdapterMeta)("grok", {
206
+ req,
207
+ finishReasonRaw: finishReason,
208
+ refusal: finishReason === "content_filter",
209
+ modelSnapshot: responseModel,
210
+ providerRequestId: responseId,
211
+ toolCalls,
212
+ streaming: false,
213
+ });
193
214
  if (toolCalls !== undefined) {
194
- return { ok: true, text, usage, toolCalls };
215
+ return { ok: true, text, usage, toolCalls, meta: attemptMeta };
195
216
  }
196
217
  if (text.trim().length === 0) {
197
218
  return fail("content_invalid", `empty response from Grok (finish_reason=${finishReason ?? "none"})`, undefined, usage);
@@ -199,11 +220,12 @@ function createGrokAdapter(config) {
199
220
  if (finishReason === "length" && req.outputSchema !== null) {
200
221
  return fail("content_invalid", "Grok structured output truncated (finish_reason=length)", "raise maxOutputTokens for this route", usage);
201
222
  }
202
- return { ok: true, text, usage };
223
+ return { ok: true, text, usage, meta: attemptMeta };
203
224
  }
204
225
  catch (error) {
205
226
  const message = error instanceof Error ? error.message : String(error);
206
227
  const { cls, fixHint } = classifyGrokFailure(message);
228
+ attemptMeta = (0, attempt_meta_map_1.transportErrorMeta)(error);
207
229
  return {
208
230
  ok: false,
209
231
  failure: {
@@ -214,6 +236,7 @@ function createGrokAdapter(config) {
214
236
  cause: error,
215
237
  ...(fixHint !== undefined ? { fixHint } : {}),
216
238
  },
239
+ meta: attemptMeta,
217
240
  };
218
241
  }
219
242
  },
@@ -1 +1 @@
1
- {"version":3,"file":"openai-compat.d.ts","sourceRoot":"","sources":["../../src/adapters/openai-compat.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"openai-compat.d.ts","sourceRoot":"","sources":["../../src/adapters/openai-compat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAIV,cAAc,EACd,eAAe,EACf,UAAU,EACX,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAMhE,OAAO,KAAK,EAAuB,cAAc,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAUhG;;8CAE8C;AAC9C,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACnD;AAED;;;0EAG0E;AAC1E,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,WAAW,GAAG,WAAW,EAAE,gBAAgB,CAAC,CAG7F,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,iBAAiB,CA2BtF;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE;QAAE,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAA;KAAE,CAAC;IAC1D,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;oFAEgF;IAChF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClE;kEAC8D;IAC9D,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,eAAe,CAAC,EACrB;QAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;KAAE,GAChC;QACE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;QAC7B,QAAQ,CAAC,WAAW,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;KACjG,CAAC;IACN,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAOD;;2DAE2D;AAC3D,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,YAAY,EACjB,YAAY,EAAE,WAAW,CAAC,cAAc,CAAC,GACxC,wBAAwB,CAmD1B;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD;+DAC2D;IAC3D,QAAQ,CAAC,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,WAAW,CAAC,cAAc,CAAC,CAAC;CAC5E;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,yBAAyB,GAAG,eAAe,CA8L5F"}
@@ -30,6 +30,7 @@ exports.createOpenAICompatAdapter = createOpenAICompatAdapter;
30
30
  const openai_1 = __importDefault(require("openai"));
31
31
  const tool_mapping_1 = require("./tool-mapping");
32
32
  const openai_stream_1 = require("./openai-stream");
33
+ const attempt_meta_map_1 = require("./attempt-meta-map");
33
34
  const sampling_1 = require("./sampling");
34
35
  const DEFAULT_MAX_TOKENS = 32000;
35
36
  /** Fireworks serves models at accounts/fireworks/models/<short> — routes
@@ -146,6 +147,8 @@ function createOpenAICompatAdapter(config) {
146
147
  supportsTools: () => true,
147
148
  async call(req) {
148
149
  const model = req.candidate.model;
150
+ // WI-1: native attempt envelope for this call (see openai.ts for the pattern).
151
+ let attemptMeta;
149
152
  const fail = (cls, message, fixHint, usage) => ({
150
153
  ok: false,
151
154
  failure: {
@@ -156,6 +159,7 @@ function createOpenAICompatAdapter(config) {
156
159
  ...(fixHint !== undefined ? { fixHint } : {}),
157
160
  },
158
161
  ...(usage !== undefined ? { usage } : {}),
162
+ ...(attemptMeta !== undefined ? { meta: attemptMeta } : {}),
159
163
  });
160
164
  try {
161
165
  const client = new openai_1.default({
@@ -174,10 +178,16 @@ function createOpenAICompatAdapter(config) {
174
178
  let finishReason = null;
175
179
  // TTFB (Phase 4 WS3): first streamed chunk = first body byte.
176
180
  let ttfbMs;
181
+ let responseModel = "";
182
+ let responseId = "";
177
183
  const toolCallAcc = (0, tool_mapping_1.createOpenAIToolCallAccumulator)();
178
184
  for await (const chunk of stream) {
179
185
  if (ttfbMs === undefined)
180
186
  ttfbMs = Date.now() - dispatchedAt;
187
+ if (responseModel === "" && typeof chunk.model === "string")
188
+ responseModel = chunk.model;
189
+ if (responseId === "" && typeof chunk.id === "string")
190
+ responseId = chunk.id;
181
191
  const choice = chunk.choices[0];
182
192
  if (choice?.delta?.content)
183
193
  text += choice.delta.content;
@@ -203,8 +213,19 @@ function createOpenAICompatAdapter(config) {
203
213
  };
204
214
  // Tool-call turn (spec §4.4): surface normalized calls, skip content guards.
205
215
  const toolCalls = toolCallAcc.finalize();
216
+ // WI-1: native attempt envelope (no `refusal` delta on this dialect →
217
+ // refusal derives from finish_reason content_filter).
218
+ attemptMeta = (0, attempt_meta_map_1.buildAdapterMeta)(config.providerId, {
219
+ req,
220
+ finishReasonRaw: finishReason,
221
+ refusal: finishReason === "content_filter",
222
+ modelSnapshot: responseModel,
223
+ providerRequestId: responseId,
224
+ toolCalls,
225
+ streaming: false,
226
+ });
206
227
  if (toolCalls !== undefined) {
207
- return { ok: true, text, usage, toolCalls };
228
+ return { ok: true, text, usage, toolCalls, meta: attemptMeta };
208
229
  }
209
230
  if (text.trim().length === 0) {
210
231
  return fail("content_invalid", `empty response from ${config.providerId} (finish_reason=${finishReason ?? "none"})`, undefined, usage);
@@ -212,11 +233,12 @@ function createOpenAICompatAdapter(config) {
212
233
  if (finishReason === "length" && req.outputSchema !== null) {
213
234
  return fail("content_invalid", `${config.providerId} JSON output truncated (finish_reason=length)`, "raise maxOutputTokens for this route", usage);
214
235
  }
215
- return { ok: true, text, usage };
236
+ return { ok: true, text, usage, meta: attemptMeta };
216
237
  }
217
238
  catch (error) {
218
239
  const message = error instanceof Error ? error.message : String(error);
219
240
  const { cls, fixHint } = classifyCompatFailure(message, config.providerId);
241
+ attemptMeta = (0, attempt_meta_map_1.transportErrorMeta)(error);
220
242
  return {
221
243
  ok: false,
222
244
  failure: {
@@ -227,6 +249,7 @@ function createOpenAICompatAdapter(config) {
227
249
  cause: error,
228
250
  ...(fixHint !== undefined ? { fixHint } : {}),
229
251
  },
252
+ meta: attemptMeta,
230
253
  };
231
254
  }
232
255
  },
@@ -16,6 +16,8 @@ import type { AdapterRequest, AdapterStreamEvent, ProviderId, ServiceTier } from
16
16
  import type { OpenAIToolCallDelta } from "./tool-mapping";
17
17
  /** The permissive chunk shape common to the OpenAI-dialect hosts. */
18
18
  export interface OpenAIDialectChunk {
19
+ readonly id?: string;
20
+ readonly model?: string;
19
21
  readonly service_tier?: string;
20
22
  readonly choices: Array<{
21
23
  readonly delta?: {
@@ -1 +1 @@
1
- {"version":3,"file":"openai-stream.d.ts","sourceRoot":"","sources":["../../src/adapters/openai-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,kBAAkB,EAElB,UAAU,EACV,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAG3B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,qEAAqE;AACrE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;QACtB,QAAQ,CAAC,KAAK,CAAC,EAAE;YACf,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;YACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;YACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;SACtD,CAAC;QACF,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACxC,CAAC,CAAC;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAChC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;QACpC,QAAQ,CAAC,qBAAqB,CAAC,EAAE;YAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACrE,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;QAC1C;;6DAEqD;QACrD,QAAQ,CAAC,oBAAoB,CAAC,EAAE;YAAE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KAC1E,GAAG,IAAI,CAAC;CACV;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,oEAAoE;IACpE,QAAQ,CAAC,iBAAiB,EAAE,SAAS,GAAG,WAAW,CAAC;IACpD,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,EAAE,WAAW,KAAK,WAAW,CAAC;CACrF;AAED;;;;;;GAMG;AACH,wBAAuB,mBAAmB,CACxC,MAAM,EAAE,aAAa,CAAC,kBAAkB,CAAC,EACzC,GAAG,EAAE,cAAc,EACnB,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,kBAAkB,GACtB,cAAc,CAAC,kBAAkB,CAAC,CAqGpC"}
1
+ {"version":3,"file":"openai-stream.d.ts","sourceRoot":"","sources":["../../src/adapters/openai-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,kBAAkB,EAElB,UAAU,EACV,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAK3B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,qEAAqE;AACrE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;QACtB,QAAQ,CAAC,KAAK,CAAC,EAAE;YACf,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;YACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;YACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;SACtD,CAAC;QACF,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACxC,CAAC,CAAC;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAChC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;QACpC,QAAQ,CAAC,qBAAqB,CAAC,EAAE;YAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACrE,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;QAC1C;;6DAEqD;QACrD,QAAQ,CAAC,oBAAoB,CAAC,EAAE;YAAE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KAC1E,GAAG,IAAI,CAAC;CACV;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,oEAAoE;IACpE,QAAQ,CAAC,iBAAiB,EAAE,SAAS,GAAG,WAAW,CAAC;IACpD,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,EAAE,WAAW,KAAK,WAAW,CAAC;CACrF;AAED;;;;;;GAMG;AACH,wBAAuB,mBAAmB,CACxC,MAAM,EAAE,aAAa,CAAC,kBAAkB,CAAC,EACzC,GAAG,EAAE,cAAc,EACnB,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,kBAAkB,GACtB,cAAc,CAAC,kBAAkB,CAAC,CAuHpC"}
@@ -15,6 +15,7 @@
15
15
  */
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.streamOpenAIDialect = streamOpenAIDialect;
18
+ const attempt_meta_map_1 = require("./attempt-meta-map");
18
19
  const tool_mapping_1 = require("./tool-mapping");
19
20
  /**
20
21
  * Drain an OpenAI-dialect completion stream into `AdapterStreamEvent`s. Emits a
@@ -34,11 +35,17 @@ async function* streamOpenAIDialect(chunks, req, dispatchedAt, cfg) {
34
35
  let cacheWriteTokens = 0;
35
36
  let responseTier = null;
36
37
  let finishReason = null;
38
+ let responseModel = "";
39
+ let responseId = "";
37
40
  for await (const chunk of chunks) {
38
41
  if (ttfbMs === undefined)
39
42
  ttfbMs = Date.now() - dispatchedAt;
40
43
  if (responseTier === null && chunk.service_tier !== undefined)
41
44
  responseTier = chunk.service_tier;
45
+ if (responseModel === "" && typeof chunk.model === "string")
46
+ responseModel = chunk.model;
47
+ if (responseId === "" && typeof chunk.id === "string")
48
+ responseId = chunk.id;
42
49
  const choice = chunk.choices[0];
43
50
  const content = choice?.delta?.content;
44
51
  if (content) {
@@ -73,6 +80,18 @@ async function* streamOpenAIDialect(chunks, req, dispatchedAt, cfg) {
73
80
  servedTier: cfg.mapServedTier(responseTier, req.requestedTier),
74
81
  ...(ttfbMs !== undefined ? { ttfbMs } : {}),
75
82
  };
83
+ const toolCalls = toolCallAcc.finalize();
84
+ // WI-1: native attempt envelope for the streamed call (streaming: true). Built
85
+ // once, attached to every terminal `final` result below.
86
+ const meta = (0, attempt_meta_map_1.buildAdapterMeta)(cfg.provider, {
87
+ req,
88
+ finishReasonRaw: finishReason,
89
+ refusal: refusalText.length > 0 || finishReason === "content_filter",
90
+ modelSnapshot: responseModel,
91
+ providerRequestId: responseId,
92
+ toolCalls,
93
+ streaming: true,
94
+ });
76
95
  const fail = (message, fixHint) => ({
77
96
  kind: "final",
78
97
  result: {
@@ -85,6 +104,7 @@ async function* streamOpenAIDialect(chunks, req, dispatchedAt, cfg) {
85
104
  ...(fixHint !== undefined ? { fixHint } : {}),
86
105
  },
87
106
  usage,
107
+ meta,
88
108
  },
89
109
  });
90
110
  if (refusalText.length > 0) {
@@ -99,13 +119,13 @@ async function* streamOpenAIDialect(chunks, req, dispatchedAt, cfg) {
99
119
  model: req.candidate.model,
100
120
  },
101
121
  usage,
122
+ meta,
102
123
  },
103
124
  };
104
125
  return;
105
126
  }
106
- const toolCalls = toolCallAcc.finalize();
107
127
  if (toolCalls !== undefined) {
108
- yield { kind: "final", result: { ok: true, text, usage, toolCalls } };
128
+ yield { kind: "final", result: { ok: true, text, usage, toolCalls, meta } };
109
129
  return;
110
130
  }
111
131
  if (text.trim().length === 0) {
@@ -116,5 +136,5 @@ async function* streamOpenAIDialect(chunks, req, dispatchedAt, cfg) {
116
136
  yield fail(`${cfg.provider} output truncated (finish_reason=length)`, "raise maxOutputTokens for this route");
117
137
  return;
118
138
  }
119
- yield { kind: "final", result: { ok: true, text, usage } };
139
+ yield { kind: "final", result: { ok: true, text, usage, meta } };
120
140
  }
@@ -1 +1 @@
1
- {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/adapters/openai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAIV,cAAc,EACd,eAAe,EAEhB,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,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAMD,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAI9D;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAe9E;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CA8BxE;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,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,YAAY,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAC5C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,KAAK,CAAC;IACxC;;;oEAGgE;IAChE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClE,QAAQ,CAAC,eAAe,CAAC,EACrB;QAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;KAAE,GAChC;QACE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;QAC7B,QAAQ,CAAC,WAAW,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;SAAE,CAAC;KACjG,CAAC;IACN,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAID,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,kBAAkB,CAuDtF;AAcD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,eAAe,CAwMhF"}
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/adapters/openai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAIV,cAAc,EACd,eAAe,EAEhB,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,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAMD,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAI9D;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAe9E;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CA8BxE;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,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,YAAY,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAC5C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,KAAK,CAAC;IACxC;;;oEAGgE;IAChE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClE,QAAQ,CAAC,eAAe,CAAC,EACrB;QAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;KAAE,GAChC;QACE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;QAC7B,QAAQ,CAAC,WAAW,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;SAAE,CAAC;KACjG,CAAC;IACN,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAID,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,kBAAkB,CAuDtF;AAcD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,eAAe,CAsOhF"}
@@ -24,6 +24,7 @@ exports.createOpenAIAdapter = createOpenAIAdapter;
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
  /** OpenAI's documented flex-eligible allowlist as of 2026-05 (ported).
29
30
  * Mini and nano variants are silently downgraded to default tier. */
@@ -162,6 +163,11 @@ function createOpenAIAdapter(config) {
162
163
  supportsTools: () => true,
163
164
  async call(req) {
164
165
  const model = req.candidate.model;
166
+ // WI-1: the native attempt envelope for this call — set once the signals
167
+ // are known (after the stream, or from the transport error), read by every
168
+ // return via the closure. Absent until then (a pre-signal throw ⇒ null meta
169
+ // downstream, byte-identical run result).
170
+ let attemptMeta;
165
171
  // F1: post-stream failures still billed tokens — attach usage.
166
172
  const fail = (cls, message, fixHint, usage) => ({
167
173
  ok: false,
@@ -173,6 +179,7 @@ function createOpenAIAdapter(config) {
173
179
  ...(fixHint !== undefined ? { fixHint } : {}),
174
180
  },
175
181
  ...(usage !== undefined ? { usage } : {}),
182
+ ...(attemptMeta !== undefined ? { meta: attemptMeta } : {}),
176
183
  });
177
184
  try {
178
185
  const client = new openai_1.default({ apiKey: config.apiKey, timeout: req.timeoutMs });
@@ -191,12 +198,19 @@ function createOpenAIAdapter(config) {
191
198
  let reasoningTokens = 0;
192
199
  let responseTier = null;
193
200
  let finishReason = null;
201
+ // WI-1 native fields: the provider-reported model snapshot + request id.
202
+ let responseModel = "";
203
+ let responseId = "";
194
204
  for await (const chunk of stream) {
195
205
  if (ttfbMs === undefined)
196
206
  ttfbMs = Date.now() - dispatchedAt;
197
207
  if (responseTier === null && chunk.service_tier !== undefined) {
198
208
  responseTier = chunk.service_tier;
199
209
  }
210
+ if (responseModel === "" && typeof chunk.model === "string")
211
+ responseModel = chunk.model;
212
+ if (responseId === "" && typeof chunk.id === "string")
213
+ responseId = chunk.id;
200
214
  const choice = chunk.choices[0];
201
215
  if (choice?.delta?.content)
202
216
  text += choice.delta.content;
@@ -223,17 +237,30 @@ function createOpenAIAdapter(config) {
223
237
  servedTier: mapServedTier(responseTier, req.requestedTier),
224
238
  ...(ttfbMs !== undefined ? { ttfbMs } : {}),
225
239
  };
240
+ // Tool-call turn (spec §4.4): the model chose to call a tool. Surface
241
+ // the normalized calls; text may legitimately be empty — do NOT run the
242
+ // empty/suspicious-short/validation guards below.
243
+ const toolCalls = toolCallAcc.finalize();
244
+ // WI-1: native attempt envelope, built once from this call's signals.
245
+ // refusal derivation: a streamed `refusal` delta OR finish_reason
246
+ // content_filter (documented in attempt-meta-map.ts).
247
+ attemptMeta = (0, attempt_meta_map_1.buildAdapterMeta)("openai", {
248
+ req,
249
+ finishReasonRaw: finishReason,
250
+ refusal: refusalText.length > 0 || finishReason === "content_filter",
251
+ reasoningTokens,
252
+ modelSnapshot: responseModel,
253
+ providerRequestId: responseId,
254
+ toolCalls,
255
+ streaming: false,
256
+ });
226
257
  // Any streamed refusal is terminal for this call, even alongside
227
258
  // partial content — OpenAI's structured-output semantics (ported).
228
259
  if (refusalText.length > 0) {
229
260
  return fail("refusal", `OpenAI refused structured output: ${refusalText.slice(0, 200)}`, undefined, usage);
230
261
  }
231
- // Tool-call turn (spec §4.4): the model chose to call a tool. Surface
232
- // the normalized calls; text may legitimately be empty — do NOT run the
233
- // empty/suspicious-short/validation guards below.
234
- const toolCalls = toolCallAcc.finalize();
235
262
  if (toolCalls !== undefined) {
236
- return { ok: true, text, usage, toolCalls };
263
+ return { ok: true, text, usage, toolCalls, meta: attemptMeta };
237
264
  }
238
265
  const visibleCompletionTokens = Math.max(0, completionTokens - reasoningTokens);
239
266
  if (finishReason === "stop" &&
@@ -249,11 +276,13 @@ function createOpenAIAdapter(config) {
249
276
  if (finishReason === "length" && req.outputSchema !== null) {
250
277
  return fail("content_invalid", "OpenAI structured output truncated (finish_reason=length)", "raise maxOutputTokens for this route", usage);
251
278
  }
252
- return { ok: true, text, usage };
279
+ return { ok: true, text, usage, meta: attemptMeta };
253
280
  }
254
281
  catch (error) {
255
282
  const message = error instanceof Error ? error.message : String(error);
256
283
  const { cls, fixHint } = classifyOpenAIFailure(message);
284
+ // WI-1: a transport/SDK error carries only its coarse status/code.
285
+ attemptMeta = (0, attempt_meta_map_1.transportErrorMeta)(error);
257
286
  return {
258
287
  ok: false,
259
288
  failure: {
@@ -264,6 +293,7 @@ function createOpenAIAdapter(config) {
264
293
  cause: error,
265
294
  ...(fixHint !== undefined ? { fixHint } : {}),
266
295
  },
296
+ meta: attemptMeta,
267
297
  };
268
298
  }
269
299
  },
@@ -1 +1 @@
1
- {"version":3,"file":"artifact.d.ts","sourceRoot":"","sources":["../../src/artifacts/artifact.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"artifact.d.ts","sourceRoot":"","sources":["../../src/artifacts/artifact.ts"],"names":[],"mappings":"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"}
@@ -20,6 +20,8 @@ const node_crypto_1 = require("node:crypto");
20
20
  const fingerprint_1 = require("../registry/fingerprint");
21
21
  const run_context_1 = require("./run-context");
22
22
  const scrub_1 = require("./scrub");
23
+ const classification_1 = require("./classification");
24
+ const task_settings_1 = require("../settings/task-settings");
23
25
  /** Map an artifact `type` to a content-type for the object store. */
24
26
  function contentTypeFor(type) {
25
27
  const t = type.toLowerCase();
@@ -171,6 +173,19 @@ function createArtifactApi(deps) {
171
173
  // `artifact.save` and the run/@step graph compose. Null outside a step (or
172
174
  // outside a run context), exactly as before.
173
175
  const stepKey = opts.stepKey ?? ctx?.currentStepKey() ?? null;
176
+ // S8-b: validate data_classification against the shared enum. A bad value
177
+ // would fail the DB CHECK (00058) and drop the row at ingest, so catch it
178
+ // here with a teaching warning and drop the LABEL (the artifact still
179
+ // saves — a metadata typo must not lose the artifact). Normalize first so
180
+ // "PHI"/" pii " keep their meaning (and their scrub floor) rather than
181
+ // silently dropping.
182
+ let classification = (0, classification_1.normalizeClassification)(opts.classification);
183
+ if (opts.classification !== undefined && classification !== null && !(0, classification_1.isDataClassification)(classification)) {
184
+ console.warn(`[modelrig artifacts] artifact "${opts.name}" has an unknown data_classification ` +
185
+ `${JSON.stringify(opts.classification)} — label dropped (artifact still saved). ` +
186
+ `fix_hint: use one of ${classification_1.DATA_CLASSIFICATIONS.join(", ")}.`);
187
+ classification = null;
188
+ }
174
189
  // Custody applies only when a rig content policy exists AND the caller left
175
190
  // the bytes to us (a caller-supplied contentRef = their own store; managed
176
191
  // custody must not double-home it).
@@ -178,8 +193,12 @@ function createArtifactApi(deps) {
178
193
  // Scrub is computed HERE (before the row is written) so its counts-only
179
194
  // summary can ride the immutable artifact row (00039). Only when custody
180
195
  // applies; null otherwise (custody off ⇒ nothing was scanned).
181
- const scrubbed = custodyApplies && content !== undefined ? (0, scrub_1.scrubContent)(serialized, content.scrub) : null;
182
- const scrubSummary = scrubbed !== null && content !== undefined ? (0, scrub_1.summarizeScrub)(content.scrub, scrubbed) : null;
196
+ // S8-c: a `pii`/`phi` artifact forces the scrub mode to a floor of
197
+ // `redact`, so PII/PHI content is never uploaded verbatim even when the
198
+ // task's configured mode is `off`/`detect`. A stronger `block` stays.
199
+ const effScrub = content !== undefined ? (0, task_settings_1.effectiveScrubMode)(content.scrub, classification) : null;
200
+ const scrubbed = custodyApplies && content !== undefined && effScrub !== null ? (0, scrub_1.scrubContent)(serialized, effScrub) : null;
201
+ const scrubSummary = scrubbed !== null && effScrub !== null ? (0, scrub_1.summarizeScrub)(effScrub, scrubbed) : null;
183
202
  if (scrubbed !== null)
184
203
  scrubFindings += scrubbed.findings.reduce((n, f) => n + f.count, 0);
185
204
  try {
@@ -201,7 +220,7 @@ function createArtifactApi(deps) {
201
220
  contentRef: opts.contentRef ?? null,
202
221
  contentHash,
203
222
  contentBytes: bytes,
204
- dataClassification: opts.classification ?? null,
223
+ dataClassification: classification,
205
224
  expiresAt: opts.expiresAt ?? null,
206
225
  // Per-row scrub findings ride the original (scanned) row (00039).
207
226
  scrub: scrubSummary,
@@ -236,7 +255,7 @@ function createArtifactApi(deps) {
236
255
  console.warn(`[modelrig artifacts] scrub blocked content for "${opts.name}" ` +
237
256
  `(${scrubbed.findings.length} finding type(s)); metadata + hash kept, no content stored.`);
238
257
  }
239
- else if (content.scrub === "redact" && scrubbed.findings.length > 0 && scrubbed.text !== null) {
258
+ else if (effScrub === "redact" && scrubbed.findings.length > 0 && scrubbed.text !== null) {
240
259
  // Redaction is a SUPERSEDING version (immutability): the original row
241
260
  // keeps its hash and stores NO content; a new version records the
242
261
  // masked content's hash and uploads the masked bytes.
@@ -262,7 +281,7 @@ function createArtifactApi(deps) {
262
281
  contentRef: null,
263
282
  contentHash: maskedHash,
264
283
  contentBytes: maskedBytes,
265
- dataClassification: opts.classification ?? null,
284
+ dataClassification: classification,
266
285
  expiresAt: opts.expiresAt ?? null,
267
286
  // Findings ride the original scanned row; the masked version stores
268
287
  // no PII, so its own scrub is null (avoid double-counting).
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The `data_classification` enum — the ONE place the vocabulary is spelled
3
+ * (screening SC-0 S8). Backs three enforcement points that must agree:
4
+ * • the SDK save gate (`artifact.ts` — teaching warn + drop on a bad value),
5
+ * • the server ingest validator (`ingest-artifacts.ts` — enum-only, counted),
6
+ * • the DB CHECK (`00058_artifacts_data_classification_enum.sql`).
7
+ * Keeping the list here (not re-spelled at each site) is what keeps the three
8
+ * from drifting; the migration and this constant are diffed by eye in review.
9
+ *
10
+ * `pii`/`phi` are the SENSITIVE tiers: an artifact tagged either resolves an
11
+ * effective scrub mode of at least `redact` regardless of the task's configured
12
+ * mode (S8-c, `settings/task-settings.ts effectiveScrubMode`). NULL stays a
13
+ * valid "unclassified" state — the column is nullable and the SDK sends null
14
+ * when no classification is given; the enum constrains only NON-null values.
15
+ */
16
+ export declare const DATA_CLASSIFICATIONS: readonly ["public", "internal", "confidential", "pii", "phi"];
17
+ export type DataClassification = (typeof DATA_CLASSIFICATIONS)[number];
18
+ /** The tiers that force the scrub floor (S8-c). A set for O(1) membership. */
19
+ export declare const SENSITIVE_CLASSIFICATIONS: ReadonlySet<string>;
20
+ /** Normalize a caller-supplied classification the way both enforcement sides
21
+ * do BEFORE the enum check — trim and lowercase, so `"PHI"`/`" phi "` resolve to
22
+ * `phi` (and keep their scrub floor) rather than silently dropping. Non-strings
23
+ * return null. */
24
+ export declare function normalizeClassification(value: unknown): string | null;
25
+ /** True when `value` is one of the enum values (after normalization). */
26
+ export declare function isDataClassification(value: unknown): value is DataClassification;
27
+ //# sourceMappingURL=classification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classification.d.ts","sourceRoot":"","sources":["../../src/artifacts/classification.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,eAAO,MAAM,oBAAoB,+DAAgE,CAAC;AAElG,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvE,8EAA8E;AAC9E,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAA2B,CAAC;AAEtF;;;kBAGkB;AAClB,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAIrE;AAED,yEAAyE;AACzE,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,kBAAkB,CAGhF"}