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.
- package/CHANGELOG.md +109 -0
- package/README.md +52 -3
- package/dist/adapters/anthropic.d.ts.map +1 -1
- package/dist/adapters/anthropic.js +36 -7
- package/dist/adapters/attempt-meta-map.d.ts +63 -0
- package/dist/adapters/attempt-meta-map.d.ts.map +1 -0
- package/dist/adapters/attempt-meta-map.js +154 -0
- package/dist/adapters/deepseek.d.ts.map +1 -1
- package/dist/adapters/deepseek.js +25 -2
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +29 -2
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js +25 -2
- package/dist/adapters/openai-compat.d.ts.map +1 -1
- package/dist/adapters/openai-compat.js +25 -2
- package/dist/adapters/openai-stream.d.ts +2 -0
- package/dist/adapters/openai-stream.d.ts.map +1 -1
- package/dist/adapters/openai-stream.js +23 -3
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +36 -6
- package/dist/artifacts/artifact.d.ts.map +1 -1
- package/dist/artifacts/artifact.js +24 -5
- package/dist/artifacts/classification.d.ts +27 -0
- package/dist/artifacts/classification.d.ts.map +1 -0
- package/dist/artifacts/classification.js +38 -0
- package/dist/artifacts/index.d.ts +3 -1
- package/dist/artifacts/index.d.ts.map +1 -1
- package/dist/artifacts/index.js +10 -1
- package/dist/artifacts/run-context.d.ts +15 -3
- package/dist/artifacts/run-context.d.ts.map +1 -1
- package/dist/artifacts/run-context.js +49 -0
- package/dist/artifacts/scrub.d.ts +26 -0
- package/dist/artifacts/scrub.d.ts.map +1 -1
- package/dist/artifacts/scrub.js +53 -0
- package/dist/artifacts/step.d.ts +9 -0
- package/dist/artifacts/step.d.ts.map +1 -1
- package/dist/artifacts/step.js +9 -0
- package/dist/bakeoff/engine.d.ts +46 -4
- package/dist/bakeoff/engine.d.ts.map +1 -1
- package/dist/bakeoff/engine.js +269 -19
- package/dist/bakeoff/eval-cases.d.ts +5 -0
- package/dist/bakeoff/eval-cases.d.ts.map +1 -1
- package/dist/bakeoff/eval-cases.js +3 -0
- package/dist/bakeoff/judge.d.ts +39 -0
- package/dist/bakeoff/judge.d.ts.map +1 -0
- package/dist/bakeoff/judge.js +102 -0
- package/dist/bakeoff/quality.d.ts +44 -0
- package/dist/bakeoff/quality.d.ts.map +1 -0
- package/dist/bakeoff/quality.js +126 -0
- package/dist/bakeoff/reference.d.ts +34 -0
- package/dist/bakeoff/reference.d.ts.map +1 -0
- package/dist/bakeoff/reference.js +68 -0
- package/dist/bakeoff/replay.d.ts +25 -0
- package/dist/bakeoff/replay.d.ts.map +1 -1
- package/dist/bakeoff/replay.js +48 -1
- package/dist/bakeoff/report.d.ts +8 -1
- package/dist/bakeoff/report.d.ts.map +1 -1
- package/dist/bakeoff/report.js +83 -12
- package/dist/bakeoff/samples.d.ts +35 -2
- package/dist/bakeoff/samples.d.ts.map +1 -1
- package/dist/bakeoff/samples.js +67 -6
- package/dist/bakeoff/stats.d.ts +17 -1
- package/dist/bakeoff/stats.d.ts.map +1 -1
- package/dist/bakeoff/stats.js +67 -0
- package/dist/bakeoff/types.d.ts +76 -5
- package/dist/bakeoff/types.d.ts.map +1 -1
- package/dist/bakeoff/types.js +4 -3
- package/dist/cli-query.d.ts +14 -0
- package/dist/cli-query.d.ts.map +1 -0
- package/dist/cli-query.js +59 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +26 -23
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -0
- package/dist/detect/detector.d.ts +82 -0
- package/dist/detect/detector.d.ts.map +1 -0
- package/dist/detect/detector.js +23 -0
- package/dist/index.d.ts +19 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -11
- package/dist/query/analytics.d.ts +84 -0
- package/dist/query/analytics.d.ts.map +1 -0
- package/dist/query/analytics.js +280 -0
- package/dist/query/columns.d.ts +59 -0
- package/dist/query/columns.d.ts.map +1 -0
- package/dist/query/columns.js +89 -0
- package/dist/query/grammar.d.ts +74 -7
- package/dist/query/grammar.d.ts.map +1 -1
- package/dist/query/grammar.js +179 -71
- package/dist/query/saved.d.ts +26 -0
- package/dist/query/saved.d.ts.map +1 -0
- package/dist/query/saved.js +51 -0
- package/dist/routes/loader.js +15 -10
- package/dist/routes/objective-loader.d.ts +35 -0
- package/dist/routes/objective-loader.d.ts.map +1 -0
- package/dist/routes/objective-loader.js +286 -0
- package/dist/run/accept.d.ts +43 -0
- package/dist/run/accept.d.ts.map +1 -0
- package/dist/run/accept.js +65 -0
- package/dist/run/cost.d.ts +13 -0
- package/dist/run/cost.d.ts.map +1 -1
- package/dist/run/cost.js +27 -0
- package/dist/run/dispatch.d.ts +24 -1
- package/dist/run/dispatch.d.ts.map +1 -1
- package/dist/run/dispatch.js +19 -0
- package/dist/run/ladder-key.d.ts +4 -0
- package/dist/run/ladder-key.d.ts.map +1 -1
- package/dist/run/ladder-key.js +3 -0
- package/dist/run/quality-gate.d.ts +39 -0
- package/dist/run/quality-gate.d.ts.map +1 -0
- package/dist/run/quality-gate.js +70 -0
- package/dist/run/raw.d.ts.map +1 -1
- package/dist/run/raw.js +17 -0
- package/dist/run/record-attempt.d.ts.map +1 -1
- package/dist/run/record-attempt.js +22 -1
- package/dist/run/routing-docs.d.ts.map +1 -1
- package/dist/run/routing-docs.js +11 -4
- package/dist/run/run.d.ts.map +1 -1
- package/dist/run/run.js +40 -36
- package/dist/settings/index.d.ts +1 -1
- package/dist/settings/index.d.ts.map +1 -1
- package/dist/settings/index.js +2 -1
- package/dist/settings/task-settings.d.ts +10 -0
- package/dist/settings/task-settings.d.ts.map +1 -1
- package/dist/settings/task-settings.js +17 -0
- package/dist/telemetry/attempt-meta.d.ts +178 -0
- package/dist/telemetry/attempt-meta.d.ts.map +1 -0
- package/dist/telemetry/attempt-meta.js +319 -0
- package/dist/telemetry/exporter.d.ts.map +1 -1
- package/dist/telemetry/exporter.js +6 -0
- package/dist/telemetry/ingest-protocol.d.ts +11 -0
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
- package/dist/telemetry/ingest-protocol.js +3 -0
- package/dist/telemetry/otel-map.d.ts +10 -0
- package/dist/telemetry/otel-map.d.ts.map +1 -1
- package/dist/telemetry/otel-map.js +64 -0
- package/dist/telemetry/store-migrations.d.ts.map +1 -1
- package/dist/telemetry/store-migrations.js +15 -0
- package/dist/telemetry/store.d.ts.map +1 -1
- package/dist/telemetry/store.js +25 -7
- package/dist/types/failures-teaching.d.ts.map +1 -1
- package/dist/types/failures-teaching.js +20 -0
- package/dist/types/failures.d.ts +1 -1
- package/dist/types/failures.d.ts.map +1 -1
- package/dist/types/failures.js +1 -0
- package/dist/types/provider.d.ts +9 -0
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/quality.d.ts +41 -0
- package/dist/types/quality.d.ts.map +1 -0
- package/dist/types/quality.js +12 -0
- package/dist/types/route.d.ts +89 -0
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/telemetry.d.ts +13 -0
- package/dist/types/telemetry.d.ts.map +1 -1
- package/dist/validate-cli.d.ts.map +1 -1
- package/dist/validate-cli.js +10 -0
- package/package.json +1 -1
package/dist/adapters/gemini.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
|
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"}
|
package/dist/adapters/grok.js
CHANGED
|
@@ -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":"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;
|
|
1
|
+
{"version":3,"file":"openai-compat.d.ts","sourceRoot":"","sources":["../../src/adapters/openai-compat.ts"],"names":[],"mappings":"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"}
|
|
@@ -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;
|
|
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;
|
|
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"}
|
package/dist/adapters/openai.js
CHANGED
|
@@ -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":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGzD,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"artifact.d.ts","sourceRoot":"","sources":["../../src/artifacts/artifact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGzD,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAGvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAEnF;4DAC4D;AAC5D,MAAM,MAAM,uBAAuB,GAAG,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAEnG;8CAC8C;AAC9C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;CAC1C;AAYD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;iFAK6E;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;gFAC4E;IAC5E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,yFAAyF;IACzF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gFAAgF;IAChF,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,CAAC;IACrC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,aAAa,GAAG,OAAO,CAAC;IACzD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;gDAE4C;IAC5C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,GAAG,MAAM,CAAC;IAC7C;uCACmC;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;CAClC;AAED,MAAM,WAAW,WAAW;IAC1B;;mCAE+B;IAC/B,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,mBAAmB,GAAG,cAAc,GAAG,IAAI,CAAC;IACvE,iFAAiF;IACjF,IAAI,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAAC;IACzC,sDAAsD;IACtD,IAAI,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,EAAE,EAAE,EAAE,cAAc,GAAG,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAAC;IAC1G,gDAAgD;IAChD,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,EAAE,IAAI,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAC5E;;;;;6CAKyC;IACzC,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,EAAE,IAAI,CAAC,EAAE,sBAAsB,GAAG,MAAM,GAAG,IAAI,CAAC;IAC1F;;iFAE6E;IAC7E,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IACzB;;kFAE8E;IAC9E,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B;;;;;;0DAMsD;IACtD,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC;8EAC0E;IAC1E,QAAQ,IAAI;QACV,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;QAC/B,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;QACtC,8CAA8C;QAC9C,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;QACjC,6EAA6E;QAC7E,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;QACjC,yDAAyD;QACzD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;QAChC;;0EAEkE;QAClE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;QAChC,oEAAoE;QACpE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;KAChC,CAAC;CACH;AA2CD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,oEAAoE;IACpE,QAAQ,CAAC,OAAO,CAAC,EAAE,mBAAmB,CAAC;CACxC,GAAG,WAAW,CA2Vd"}
|
|
@@ -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
|
-
|
|
182
|
-
|
|
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:
|
|
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 (
|
|
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:
|
|
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"}
|