@tangle-network/agent-eval 0.20.12 → 0.21.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 (48) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +39 -1
  3. package/dist/{chunk-75MCTH7P.js → chunk-3GN6U53I.js} +198 -3
  4. package/dist/chunk-3GN6U53I.js.map +1 -0
  5. package/dist/chunk-3IX6QTB7.js +1349 -0
  6. package/dist/chunk-3IX6QTB7.js.map +1 -0
  7. package/dist/{chunk-PKCVBYTQ.js → chunk-5IIQKMD5.js} +38 -2
  8. package/dist/chunk-5IIQKMD5.js.map +1 -0
  9. package/dist/{chunk-MCMV7DUL.js → chunk-ARZ6BEV6.js} +2 -2
  10. package/dist/{chunk-HKYRWNHV.js → chunk-HRZELXCR.js} +2 -2
  11. package/dist/{chunk-ODFINDLQ.js → chunk-KRR4VMH7.js} +11 -1
  12. package/dist/chunk-KRR4VMH7.js.map +1 -0
  13. package/dist/chunk-SNUHRBDL.js +154 -0
  14. package/dist/chunk-SNUHRBDL.js.map +1 -0
  15. package/dist/{chunk-KWUAAIHR.js → chunk-WOK2RTWG.js} +157 -1
  16. package/dist/chunk-WOK2RTWG.js.map +1 -0
  17. package/dist/{chunk-HNJLMAJ2.js → chunk-WOPGKVN4.js} +2 -2
  18. package/dist/cli.js +3 -2
  19. package/dist/cli.js.map +1 -1
  20. package/dist/{control-C8NKbF3w.d.ts → control-cxwMOAsy.d.ts} +3 -2
  21. package/dist/control.d.ts +4 -3
  22. package/dist/control.js +2 -2
  23. package/dist/emitter-B2XqDKFU.d.ts +121 -0
  24. package/dist/{feedback-trajectory-BGQ_ANCN.d.ts → feedback-trajectory-CB0A32o3.d.ts} +2 -1
  25. package/dist/index.d.ts +71 -83
  26. package/dist/index.js +48 -60
  27. package/dist/index.js.map +1 -1
  28. package/dist/openapi.json +1 -1
  29. package/dist/optimization.d.ts +3 -2
  30. package/dist/optimization.js +2 -2
  31. package/dist/reporting-Da2ihlcM.d.ts +672 -0
  32. package/dist/reporting.d.ts +5 -426
  33. package/dist/reporting.js +6 -2
  34. package/dist/{emitter-BYO2nSDA.d.ts → store-u47QaJ9G.d.ts} +1 -91
  35. package/dist/traces.d.ts +259 -3
  36. package/dist/traces.js +24 -4
  37. package/dist/wire/index.js +3 -2
  38. package/docs/research-report-methodology.md +155 -0
  39. package/package.json +10 -12
  40. package/dist/chunk-75MCTH7P.js.map +0 -1
  41. package/dist/chunk-IKFVX537.js +0 -717
  42. package/dist/chunk-IKFVX537.js.map +0 -1
  43. package/dist/chunk-KWUAAIHR.js.map +0 -1
  44. package/dist/chunk-ODFINDLQ.js.map +0 -1
  45. package/dist/chunk-PKCVBYTQ.js.map +0 -1
  46. /package/dist/{chunk-MCMV7DUL.js.map → chunk-ARZ6BEV6.js.map} +0 -0
  47. /package/dist/{chunk-HKYRWNHV.js.map → chunk-HRZELXCR.js.map} +0 -0
  48. /package/dist/{chunk-HNJLMAJ2.js.map → chunk-WOPGKVN4.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,81 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.21.0 — capture integrity + launch-grade reporting
4
+
5
+ This release closes the layer-1 gap a downstream consumer surfaced: better
6
+ post-run statistics don't help if the underlying data wasn't captured. 0.21
7
+ adds first-class raw provider-event capture, a fail-loud route guard, a
8
+ run-completion integrity check, and run-complete hooks (with a trace-analyst
9
+ auto-execution helper) so a direct matrix run produces complete forensics
10
+ without out-of-band glue.
11
+
12
+ ### Added
13
+
14
+ - **`RawProviderSink` (capture).** First-class persistence for HTTP-level
15
+ provider request / response / error payloads alongside the structured
16
+ `LlmSpan`. `InMemoryRawProviderSink`, `FileSystemRawProviderSink` (NDJSON,
17
+ rolls at 32 MiB), and `NoopRawProviderSink` ship in core. Default redactor
18
+ strips `Authorization` / `X-Api-Key` / `Cookie` headers and credential-shaped
19
+ body fields (`apiKey`, `bearer`, `password`, `secret`, `token`); redacted
20
+ paths are recorded on `event.redactedFields` so a reviewer can see what was
21
+ stripped without exposing values. Wired into `callLlm` via
22
+ `LlmClientOptions.rawSink` — every retry attempt produces a `request` and
23
+ either a `response` or `error` event with the attempt index attached.
24
+ - **`assertLlmRoute` (route guard).** Pure function that throws
25
+ `LlmRouteAssertionError` when the configured client doesn't match the
26
+ caller's route requirements: `requireExplicitBaseUrl`, `allowedBaseUrls`,
27
+ `blockedBaseUrls`, `requireAuth`, `expectedProvider`. Designed for the
28
+ matrix-runner preflight — fail loud at the boundary instead of silently
29
+ falling back to the public/free-tier router.
30
+ - **`assertRunCaptured` (integrity check).** Read-only check on
31
+ `(store, runId, expectations)` that returns a structured
32
+ `RunIntegrityReport` with issue codes (`missing_llm_spans`,
33
+ `missing_raw_events`, `orphan_llm_span`, `no_raw_sink`, `missing_outcome`,
34
+ …). Pair with the new `requireRawCoverageOfLlmSpans` to assert every
35
+ `LlmSpan` has a matching raw `request` event. Use directly or via
36
+ `throwIfRunIncomplete` for strict mode.
37
+ - **`onRunComplete` hooks on `TraceEmitter`.** New
38
+ `TraceEmitterOptions.onRunComplete` array fires after `endRun` / `abortRun`
39
+ with full run context (run id, outcome, status, store, emitter). Errors are
40
+ swallowed and recorded as `log` events by default; opt into propagation via
41
+ `hookErrors: 'throw'`. `addRunCompleteHook` attaches hooks after construction.
42
+ - **`traceAnalystOnRunComplete` factory.** Drop-in run-complete hook that
43
+ runs `analyzeTraces` after each run and persists the result. Resolves the
44
+ "trace analyst never ran on this matrix sweep" complaint by making
45
+ auto-execution declarative.
46
+ - **`researchReport`** — executive research-report layer for coding-vertical
47
+ benchmark runs (originally landed in #34, elevated in #35). Composes
48
+ `summaryTable`, `paretoChart`, `gainHistogram`, held-out gate decisions,
49
+ and optional `failureClusterView` output into one structured artifact:
50
+ promote / hold / equivalent / reject / needs-more-data guidance with
51
+ rationale, risks, next actions, markdown, HTML, and JSON chart specs.
52
+ - Decisions are made on paired evidence — never on marginal means alone.
53
+ - ROPE (Region of Practical Equivalence) supported via the `rope` option.
54
+ - Bayesian-bootstrap-style `Pr(Δ>0)` and `Pr(Δ∈ROPE)` summaries (Rubin 1981).
55
+ - Per-candidate minimum detectable paired effect via `pairedMde`.
56
+ - SHA-256 `runFingerprint` and optional `preregistrationHash` linking a
57
+ signed `HypothesisManifest`.
58
+ - Embedded methodology + `docs/research-report-methodology.md` companion.
59
+ - **`pairedMde`** in `power-analysis`: closed-form minimum detectable paired
60
+ effect (inverse to the paired-t / sign-rank power formula).
61
+
62
+ ### Changed
63
+
64
+ - `researchReport` is async (uses Web Crypto via `hashJson` for the run
65
+ fingerprint).
66
+ - Default `researchReport.minPairs` is 20 (soft floor); hard floor of 6 is
67
+ enforced regardless via `RESEARCH_REPORT_HARD_PAIR_FLOOR`.
68
+
69
+ ### Wire-protocol consumers
70
+
71
+ No wire-protocol changes. The new capture / integrity / hook primitives are
72
+ TypeScript-only; cross-language consumers continue to use the existing RPC
73
+ surface.
74
+
75
+ ### Python client
76
+
77
+ Locked at `tangle-agent-eval==0.21.0` to match the npm package.
78
+
3
79
  ## 0.20.10 — hardening audit follow-up
4
80
 
5
81
  ### Fixed
package/README.md CHANGED
@@ -111,9 +111,47 @@ import { renderReleaseReport } from '@tangle-network/agent-eval/reporting'
111
111
  | Compare prompt/tool/retrieval policies over full trajectories | `runMultiShotOptimization` |
112
112
  | Gate releases with paired evidence and holdouts | `evaluateReleaseConfidence`, `HeldOutGate` |
113
113
  | Explain regressions across trace corpora | `TraceAnalyst` / `analyzeTraces` |
114
- | Report a launch decision | `renderReleaseReport`, `summaryTable`, `paretoChart`, `gainHistogram` |
114
+ | Report a launch decision | `renderReleaseReport`, `researchReport`, `summaryTable`, `paretoChart`, `gainHistogram` |
115
+ | Capture every provider HTTP request / response for forensics | `RawProviderSink`, `LlmClientOptions.rawSink` |
116
+ | Fail loud if an eval would silently use the wrong route | `assertLlmRoute` |
117
+ | Assert at run-end that the artifact is complete | `assertRunCaptured`, `throwIfRunIncomplete` |
118
+ | Auto-execute the trace analyst on every run | `traceAnalystOnRunComplete` + `TraceEmitterOptions.onRunComplete` |
115
119
  | Model missing context separately from bad reasoning | `KnowledgeRequirement`, `KnowledgeBundle` |
116
120
 
121
+ ### Capture integrity (0.21+)
122
+
123
+ Launch-grade benchmark runs need four things that are easy to forget in glue
124
+ code: (1) raw HTTP capture alongside the structured spans so a reviewer can
125
+ verify which route answered, (2) a preflight assertion that the configured
126
+ client points at the intended provider, (3) a run-end assertion that the
127
+ expected events were actually written, and (4) auto-execution of the trace
128
+ analyst as part of the run lifecycle. The wiring fits in a few lines:
129
+
130
+ ```ts
131
+ import {
132
+ TraceEmitter, FileSystemRawProviderSink, callLlm, assertLlmRoute,
133
+ assertRunCaptured, throwIfRunIncomplete,
134
+ } from '@tangle-network/agent-eval'
135
+ import { traceAnalystOnRunComplete } from '@tangle-network/agent-eval/traces'
136
+
137
+ const sink = new FileSystemRawProviderSink({ dir: `${workDir}/raw-events` })
138
+ assertLlmRoute(llmOpts, { requireExplicitBaseUrl: true, allowedBaseUrls, requireAuth: true })
139
+
140
+ const emitter = new TraceEmitter(store, {
141
+ onRunComplete: [traceAnalystOnRunComplete({ analyze: analystOpts, save })],
142
+ })
143
+ await emitter.startRun(/* ... */)
144
+ // LLM calls flow through callLlm with `{ rawSink: sink, traceContext: { runId, spanId } }`.
145
+ await emitter.endRun({ pass, score })
146
+
147
+ throwIfRunIncomplete(await assertRunCaptured(store, emitter.runId, {
148
+ llmSpansMin: 1, rawSink: sink, requireRawCoverageOfLlmSpans: true, requireOutcome: true,
149
+ }))
150
+ ```
151
+
152
+ Directives, rationale, and shipped-bug context are in
153
+ [`SKILL.md` § Capture integrity](./.claude/skills/agent-eval/SKILL.md#capture-integrity-required-for-launch-grade-adoption).
154
+
117
155
  ## Examples
118
156
 
119
157
  Runnable examples live in
@@ -1,3 +1,8 @@
1
+ import {
2
+ defaultProviderRedactor,
3
+ providerFromBaseUrl
4
+ } from "./chunk-SNUHRBDL.js";
5
+
1
6
  // src/llm-client.ts
2
7
  var LlmCallError = class extends Error {
3
8
  constructor(message, status, body, model) {
@@ -135,25 +140,71 @@ function extractBalancedJson(input, start) {
135
140
  async function callLlm(req, opts = {}) {
136
141
  const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
137
142
  const url = `${baseUrl}/chat/completions`;
143
+ const endpoint = "/chat/completions";
138
144
  const timeoutMs = req.timeoutMs ?? opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
139
145
  const maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES;
140
146
  const fetchFn = opts.fetch ?? globalThis.fetch;
141
147
  const headers = buildHeaders(opts);
148
+ const provider = opts.provider ?? providerFromBaseUrl(baseUrl);
149
+ const sink = opts.rawSink;
150
+ const redactor = opts.redactor ?? defaultProviderRedactor;
151
+ const traceContext = opts.traceContext;
142
152
  let lastErr;
143
153
  for (let attempt = 0; attempt < maxRetries; attempt++) {
144
154
  const controller = new AbortController();
145
155
  const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);
146
156
  const started = Date.now();
157
+ const requestBody = buildBody(req, false);
158
+ let attemptErrorRecorded = false;
159
+ if (sink) {
160
+ await recordRaw(sink, redactor, {
161
+ eventId: cryptoEventId(),
162
+ runId: traceContext?.runId,
163
+ spanId: traceContext?.spanId,
164
+ provider,
165
+ model: req.model,
166
+ endpoint,
167
+ baseUrl,
168
+ attemptIndex: attempt,
169
+ direction: "request",
170
+ timestamp: started,
171
+ requestHeaders: headers,
172
+ requestBody,
173
+ redactedFields: []
174
+ });
175
+ }
147
176
  try {
148
177
  const res = await fetchFn(url, {
149
178
  method: "POST",
150
179
  headers,
151
- body: JSON.stringify(buildBody(req, false)),
180
+ body: JSON.stringify(requestBody),
152
181
  signal: controller.signal
153
182
  });
154
183
  clearTimeout(timeoutHandle);
184
+ const responseHeaders = sink ? headersToObject(res.headers) : void 0;
155
185
  if (!res.ok) {
156
186
  const body = await res.text();
187
+ if (sink) {
188
+ await recordRaw(sink, redactor, {
189
+ eventId: cryptoEventId(),
190
+ runId: traceContext?.runId,
191
+ spanId: traceContext?.spanId,
192
+ provider,
193
+ model: req.model,
194
+ endpoint,
195
+ baseUrl,
196
+ attemptIndex: attempt,
197
+ direction: "error",
198
+ timestamp: Date.now(),
199
+ durationMs: Date.now() - started,
200
+ statusCode: res.status,
201
+ responseHeaders,
202
+ responseBody: body,
203
+ errorMessage: `HTTP ${res.status}`,
204
+ redactedFields: []
205
+ });
206
+ attemptErrorRecorded = true;
207
+ }
157
208
  const err = new LlmCallError(
158
209
  `LLM call ${res.status}: ${body.slice(0, 300)}`,
159
210
  res.status,
@@ -168,7 +219,53 @@ async function callLlm(req, opts = {}) {
168
219
  }
169
220
  throw err;
170
221
  }
171
- const json = await res.json();
222
+ const text = await res.text();
223
+ let json;
224
+ try {
225
+ json = JSON.parse(text);
226
+ } catch (parseErr) {
227
+ if (sink) {
228
+ await recordRaw(sink, redactor, {
229
+ eventId: cryptoEventId(),
230
+ runId: traceContext?.runId,
231
+ spanId: traceContext?.spanId,
232
+ provider,
233
+ model: req.model,
234
+ endpoint,
235
+ baseUrl,
236
+ attemptIndex: attempt,
237
+ direction: "error",
238
+ timestamp: Date.now(),
239
+ durationMs: Date.now() - started,
240
+ statusCode: res.status,
241
+ responseHeaders,
242
+ responseBody: text,
243
+ errorMessage: `non-JSON response: ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`,
244
+ redactedFields: []
245
+ });
246
+ attemptErrorRecorded = true;
247
+ }
248
+ throw parseErr;
249
+ }
250
+ if (sink) {
251
+ await recordRaw(sink, redactor, {
252
+ eventId: cryptoEventId(),
253
+ runId: traceContext?.runId,
254
+ spanId: traceContext?.spanId,
255
+ provider,
256
+ model: req.model,
257
+ endpoint,
258
+ baseUrl,
259
+ attemptIndex: attempt,
260
+ direction: "response",
261
+ timestamp: Date.now(),
262
+ durationMs: Date.now() - started,
263
+ statusCode: res.status,
264
+ responseHeaders,
265
+ responseBody: json,
266
+ redactedFields: []
267
+ });
268
+ }
172
269
  const choice = json.choices?.[0];
173
270
  const usageRaw = json.usage ?? {};
174
271
  const costFromProxy = json._response_cost ?? json.cost_usd;
@@ -190,6 +287,23 @@ async function callLlm(req, opts = {}) {
190
287
  } catch (err) {
191
288
  clearTimeout(timeoutHandle);
192
289
  lastErr = err;
290
+ if (sink && !attemptErrorRecorded) {
291
+ await recordRaw(sink, redactor, {
292
+ eventId: cryptoEventId(),
293
+ runId: traceContext?.runId,
294
+ spanId: traceContext?.spanId,
295
+ provider,
296
+ model: req.model,
297
+ endpoint,
298
+ baseUrl,
299
+ attemptIndex: attempt,
300
+ direction: "error",
301
+ timestamp: Date.now(),
302
+ durationMs: Date.now() - started,
303
+ errorMessage: err instanceof Error ? err.message : String(err),
304
+ redactedFields: []
305
+ });
306
+ }
193
307
  if (attempt < maxRetries - 1 && isRetryableError(err)) {
194
308
  await sleep(backoffMs(attempt));
195
309
  continue;
@@ -199,6 +313,23 @@ async function callLlm(req, opts = {}) {
199
313
  }
200
314
  throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
201
315
  }
316
+ async function recordRaw(sink, redactor, event) {
317
+ try {
318
+ await sink.record(redactor(event));
319
+ } catch {
320
+ }
321
+ }
322
+ function headersToObject(h) {
323
+ const out = {};
324
+ h.forEach((value, key) => {
325
+ out[key] = value;
326
+ });
327
+ return out;
328
+ }
329
+ function cryptoEventId() {
330
+ if (typeof globalThis.crypto?.randomUUID === "function") return globalThis.crypto.randomUUID();
331
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
332
+ }
202
333
  async function callLlmJson(req, opts = {}) {
203
334
  try {
204
335
  const result = await callLlm({ ...req, jsonMode: req.jsonMode ?? !req.jsonSchema }, opts);
@@ -226,6 +357,68 @@ ${content.slice(0, 800)}`
226
357
  );
227
358
  }
228
359
  }
360
+ var LlmRouteAssertionError = class extends Error {
361
+ constructor(message, code, baseUrl) {
362
+ super(message);
363
+ this.code = code;
364
+ this.baseUrl = baseUrl;
365
+ this.name = "LlmRouteAssertionError";
366
+ }
367
+ code;
368
+ baseUrl;
369
+ };
370
+ function assertLlmRoute(opts, req = {}) {
371
+ const baseUrlExplicit = opts.baseUrl !== void 0;
372
+ const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
373
+ if (req.requireExplicitBaseUrl && !baseUrlExplicit) {
374
+ throw new LlmRouteAssertionError(
375
+ `assertLlmRoute: requireExplicitBaseUrl set but opts.baseUrl is undefined; would fall back to ${DEFAULT_BASE_URL}.`,
376
+ "no_explicit_base_url",
377
+ baseUrl
378
+ );
379
+ }
380
+ if (req.blockedBaseUrls?.some((p) => matchUrl(baseUrl, p))) {
381
+ throw new LlmRouteAssertionError(
382
+ `assertLlmRoute: baseUrl ${baseUrl} matches a blocked pattern.`,
383
+ "base_url_blocked",
384
+ baseUrl
385
+ );
386
+ }
387
+ if (req.allowedBaseUrls && req.allowedBaseUrls.length > 0) {
388
+ const ok = req.allowedBaseUrls.some((p) => matchUrl(baseUrl, p));
389
+ if (!ok) {
390
+ throw new LlmRouteAssertionError(
391
+ `assertLlmRoute: baseUrl ${baseUrl} is not in the allowed list (${req.allowedBaseUrls.map(describePattern).join(", ")}).`,
392
+ "base_url_not_allowed",
393
+ baseUrl
394
+ );
395
+ }
396
+ }
397
+ if (req.requireAuth && !opts.apiKey && !opts.bearer && !opts.authHeader) {
398
+ throw new LlmRouteAssertionError(
399
+ `assertLlmRoute: requireAuth set but no apiKey, bearer, or authHeader was supplied.`,
400
+ "no_auth",
401
+ baseUrl
402
+ );
403
+ }
404
+ if (req.expectedProvider) {
405
+ const actual = opts.provider ?? providerFromBaseUrl(baseUrl);
406
+ if (actual !== req.expectedProvider) {
407
+ throw new LlmRouteAssertionError(
408
+ `assertLlmRoute: expected provider ${req.expectedProvider} but baseUrl ${baseUrl} resolves to ${actual}.`,
409
+ "wrong_provider",
410
+ baseUrl
411
+ );
412
+ }
413
+ }
414
+ }
415
+ function matchUrl(url, pattern) {
416
+ if (pattern instanceof RegExp) return pattern.test(url);
417
+ return url.toLowerCase().startsWith(pattern.toLowerCase());
418
+ }
419
+ function describePattern(p) {
420
+ return p instanceof RegExp ? p.source : p;
421
+ }
229
422
  async function probeLlm(model, opts = {}) {
230
423
  const start = Date.now();
231
424
  try {
@@ -265,7 +458,9 @@ export {
265
458
  stripFencedJson,
266
459
  callLlm,
267
460
  callLlmJson,
461
+ LlmRouteAssertionError,
462
+ assertLlmRoute,
268
463
  probeLlm,
269
464
  LlmClient
270
465
  };
271
- //# sourceMappingURL=chunk-75MCTH7P.js.map
466
+ //# sourceMappingURL=chunk-3GN6U53I.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/llm-client.ts"],"sourcesContent":["/**\n * LLM client with graceful degrade.\n *\n * OpenAI-compatible `/v1/chat/completions` client with:\n * - Exponential-backoff retry on 429 + 5xx gateway errors (502/503/504).\n * - Retry on transient network errors (fetch failed, AbortError, ECONNRESET).\n * - Graceful json_schema → json_object degrade on 400 with schema-reject body.\n * - Fenced-JSON stripping (```json ... ```) for models that wrap structured output.\n * - Configurable base URL + api key / bearer, works with LiteLLM proxies, OpenAI\n * directly, cli-bridge subscriptions, and any router that speaks the spec.\n *\n * Usage:\n * const { value, result } = await callLlmJson<MyType>(\n * { model: 'gpt-4o', messages: [...], jsonSchema: { name: 'x', schema: {...} } },\n * { baseUrl: 'https://router.tangle.tools/v1', apiKey: process.env.KEY },\n * )\n *\n * This is THE llm-calling seam for agent-eval primitives that need structured\n * output (semantic concept judge, reviewer directives, critic scores). Primitives\n * that need free-form text use `callLlm` and parse output themselves.\n */\n\nimport {\n defaultProviderRedactor,\n providerFromBaseUrl,\n type ProviderRedactor,\n type RawProviderEvent,\n type RawProviderSink,\n} from './trace/raw-provider-sink'\n\n// ─── Types ──────────────────────────────────────────────────────────────\n\nexport interface LlmMessage {\n role: 'system' | 'user' | 'assistant'\n /**\n * Either a plain text content string OR a multimodal content array\n * (text + image_url parts) for vision-capable models.\n */\n content:\n | string\n | Array<\n | { type: 'text'; text: string }\n | { type: 'image_url'; image_url: { url: string; detail?: 'auto' | 'low' | 'high' } }\n >\n}\n\nexport interface LlmCallRequest {\n model: string\n messages: LlmMessage[]\n /** Optional JSON-mode response format (response_format: json_object). */\n jsonMode?: boolean\n /** Optional structured output via JSON Schema. Falls back to json_object on 400. */\n jsonSchema?: { name: string; schema: Record<string, unknown> }\n temperature?: number\n maxTokens?: number\n /** Per-call timeout, default 60s. */\n timeoutMs?: number\n}\n\nexport interface LlmUsage {\n promptTokens: number\n completionTokens: number\n totalTokens: number\n /** Proxies populate this when prompt caching is on. */\n cachedPromptTokens?: number\n}\n\nexport interface LlmCallResult {\n /** The text content of the first choice. Empty string if none. */\n content: string\n usage: LlmUsage\n /**\n * Cost in USD. Pulled from proxy's `_response_cost` field when present;\n * `null` when neither the proxy nor the caller can derive it.\n */\n costUsd: number | null\n /** Model name actually used (echoed from response). */\n model: string\n /** Wall-clock duration of the HTTP call (last attempt, if retried). */\n durationMs: number\n /** Raw response body. */\n raw: Record<string, unknown>\n}\n\nexport class LlmCallError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly body: string,\n public readonly model: string,\n ) {\n super(message)\n this.name = 'LlmCallError'\n }\n}\n\nexport interface LlmClientOptions {\n /** Base URL (without trailing slash). Must end at the `/v1` prefix. */\n baseUrl?: string\n /** Bearer token — either `apiKey` or `bearer` populates `Authorization: Bearer ...`. */\n apiKey?: string\n bearer?: string\n /** Override for the `Authorization` header (e.g. `X-Auth: ...`). Takes precedence over apiKey/bearer. */\n authHeader?: { name: string; value: string }\n /** Default timeout in ms. Per-call can override. */\n defaultTimeoutMs?: number\n /** Max retry attempts on retriable errors. Default 3 (1 initial + 2 retries). */\n maxRetries?: number\n /** Fetch implementation — defaults to global `fetch`. Override for custom transport (e.g. tests). */\n fetch?: typeof fetch\n /**\n * Optional raw HTTP capture sink. When provided, every request, response,\n * and error (across all retry attempts) is recorded to the sink, with auth\n * headers and credential-shaped body fields redacted by default. This is\n * the layer-1 forensics primitive: structured `LlmSpan`s record intent,\n * raw events record what actually crossed the wire.\n */\n rawSink?: RawProviderSink\n /**\n * Logical provider id attached to raw events. When omitted, derived from\n * `baseUrl` via `providerFromBaseUrl`.\n */\n provider?: string\n /** Trace context attached to raw events; populated by emitter-aware callers. */\n traceContext?: { runId?: string; spanId?: string }\n /** Override the redaction strategy for this call. Defaults to `defaultProviderRedactor`. */\n redactor?: ProviderRedactor\n}\n\n// ─── Internals ──────────────────────────────────────────────────────────\n\nconst DEFAULT_BASE_URL = 'https://router.tangle.tools/v1'\nconst DEFAULT_TIMEOUT_MS = 60_000\nconst DEFAULT_MAX_RETRIES = 3\n\nconst RETRYABLE_STATUS = new Set([429, 502, 503, 504])\n\nfunction isRetryableError(err: unknown): boolean {\n if (err instanceof LlmCallError) return RETRYABLE_STATUS.has(err.status)\n if (err instanceof Error) {\n return (\n err.name === 'AbortError' ||\n err.name === 'TimeoutError' ||\n /fetch failed|ECONNRESET|ETIMEDOUT|EAI_AGAIN/i.test(err.message)\n )\n }\n return false\n}\n\nfunction parseRetryAfter(headers: Headers): number | null {\n const h = headers.get('retry-after')\n if (!h) return null\n const asNumber = Number(h)\n if (Number.isFinite(asNumber) && asNumber > 0) return asNumber * 1000\n const asDate = Date.parse(h)\n if (Number.isFinite(asDate)) return Math.max(0, asDate - Date.now())\n return null\n}\n\nfunction backoffMs(attempt: number): number {\n // 500ms, 1s, 2s, 4s, ...\n return Math.min(500 * Math.pow(2, attempt), 16_000)\n}\n\nfunction buildHeaders(opts: LlmClientOptions): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n }\n if (opts.authHeader) {\n headers[opts.authHeader.name] = opts.authHeader.value\n } else if (opts.bearer || opts.apiKey) {\n headers.Authorization = `Bearer ${opts.bearer ?? opts.apiKey}`\n }\n return headers\n}\n\nfunction isSchemaRejection(status: number, body: string): boolean {\n if (status !== 400) return false\n const lower = body.toLowerCase()\n return (\n lower.includes('response_format') ||\n lower.includes('json_schema') ||\n lower.includes('is unavailable') ||\n lower.includes('not supported')\n )\n}\n\nfunction buildBody(req: LlmCallRequest, forceJsonObject: boolean): Record<string, unknown> {\n const body: Record<string, unknown> = {\n model: req.model,\n messages: req.messages,\n temperature: req.temperature ?? 0,\n }\n if (req.maxTokens != null) {\n if (usesMaxCompletionTokens(req.model)) body.max_completion_tokens = req.maxTokens\n else body.max_tokens = req.maxTokens\n }\n\n if (req.jsonSchema && !forceJsonObject) {\n body.response_format = {\n type: 'json_schema',\n json_schema: { name: req.jsonSchema.name, schema: req.jsonSchema.schema, strict: true },\n }\n } else if (req.jsonMode || req.jsonSchema) {\n body.response_format = { type: 'json_object' }\n }\n\n return body\n}\n\nfunction usesMaxCompletionTokens(model: string): boolean {\n return /^gpt-5(?:[.\\-]|$)/i.test(model)\n}\n\nasync function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n// ─── Public API ─────────────────────────────────────────────────────────\n\n/**\n * Strip a ```json / ``` code fence if the model emitted one.\n * Idempotent for naked JSON. Some models (claude-code via router, certain\n * deepseek models) wrap output even under json_object.\n */\nexport function stripFencedJson(raw: string): string {\n const trimmed = raw.trim()\n const m = trimmed.match(/^```(?:json)?\\s*\\n?([\\s\\S]*?)\\n?```\\s*$/)\n return m ? m[1]!.trim() : trimmed\n}\n\nexport function extractJsonPayload(raw: string): string {\n const stripped = stripFencedJson(raw)\n try {\n JSON.parse(stripped)\n return stripped\n } catch {\n // Continue with balanced extraction below.\n }\n\n const starts = [...stripped.matchAll(/[\\[{]/g)].map((match) => match.index).filter((index) => index != null)\n for (const start of starts) {\n const candidate = extractBalancedJson(stripped, start)\n if (!candidate) continue\n try {\n JSON.parse(candidate)\n return candidate\n } catch {\n // Keep scanning; earlier braces may belong to prose.\n }\n }\n\n return stripped\n}\n\nfunction extractBalancedJson(input: string, start: number): string | null {\n const opener = input[start]\n const closer = opener === '{' ? '}' : opener === '[' ? ']' : null\n if (!closer) return null\n\n const stack: string[] = [closer]\n let isInString = false\n let isEscaped = false\n\n for (let i = start + 1; i < input.length; i++) {\n const char = input[i]!\n if (isEscaped) {\n isEscaped = false\n continue\n }\n if (char === '\\\\') {\n isEscaped = isInString\n continue\n }\n if (char === '\"') {\n isInString = !isInString\n continue\n }\n if (isInString) continue\n\n if (char === '{') stack.push('}')\n else if (char === '[') stack.push(']')\n else if (char === stack[stack.length - 1]) {\n stack.pop()\n if (stack.length === 0) return input.slice(start, i + 1)\n }\n }\n\n return null\n}\n\n/**\n * Low-level call. Returns raw content + usage + cost. Retries on transient\n * failures; does NOT degrade schema here — callers that want graceful\n * degrade use `callLlmJson`.\n */\nexport async function callLlm(\n req: LlmCallRequest,\n opts: LlmClientOptions = {},\n): Promise<LlmCallResult> {\n const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '')\n const url = `${baseUrl}/chat/completions`\n const endpoint = '/chat/completions'\n const timeoutMs = req.timeoutMs ?? opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS\n const maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES\n const fetchFn = opts.fetch ?? globalThis.fetch\n const headers = buildHeaders(opts)\n const provider = opts.provider ?? providerFromBaseUrl(baseUrl)\n const sink = opts.rawSink\n const redactor = opts.redactor ?? defaultProviderRedactor\n const traceContext = opts.traceContext\n\n let lastErr: unknown\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n const controller = new AbortController()\n const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs)\n const started = Date.now()\n const requestBody = buildBody(req, false)\n let attemptErrorRecorded = false\n if (sink) {\n await recordRaw(sink, redactor, {\n eventId: cryptoEventId(),\n runId: traceContext?.runId,\n spanId: traceContext?.spanId,\n provider,\n model: req.model,\n endpoint,\n baseUrl,\n attemptIndex: attempt,\n direction: 'request',\n timestamp: started,\n requestHeaders: headers,\n requestBody,\n redactedFields: [],\n })\n }\n\n try {\n const res = await fetchFn(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(requestBody),\n signal: controller.signal,\n })\n clearTimeout(timeoutHandle)\n const responseHeaders = sink ? headersToObject(res.headers) : undefined\n\n if (!res.ok) {\n const body = await res.text()\n if (sink) {\n await recordRaw(sink, redactor, {\n eventId: cryptoEventId(),\n runId: traceContext?.runId,\n spanId: traceContext?.spanId,\n provider,\n model: req.model,\n endpoint,\n baseUrl,\n attemptIndex: attempt,\n direction: 'error',\n timestamp: Date.now(),\n durationMs: Date.now() - started,\n statusCode: res.status,\n responseHeaders,\n responseBody: body,\n errorMessage: `HTTP ${res.status}`,\n redactedFields: [],\n })\n attemptErrorRecorded = true\n }\n const err = new LlmCallError(\n `LLM call ${res.status}: ${body.slice(0, 300)}`,\n res.status,\n body,\n req.model,\n )\n if (RETRYABLE_STATUS.has(res.status) && attempt < maxRetries - 1) {\n lastErr = err\n const retryAfter = parseRetryAfter(res.headers)\n await sleep(retryAfter ?? backoffMs(attempt))\n continue\n }\n throw err\n }\n\n const text = await res.text()\n let json: Record<string, unknown>\n try {\n json = JSON.parse(text) as Record<string, unknown>\n } catch (parseErr) {\n if (sink) {\n await recordRaw(sink, redactor, {\n eventId: cryptoEventId(),\n runId: traceContext?.runId,\n spanId: traceContext?.spanId,\n provider,\n model: req.model,\n endpoint,\n baseUrl,\n attemptIndex: attempt,\n direction: 'error',\n timestamp: Date.now(),\n durationMs: Date.now() - started,\n statusCode: res.status,\n responseHeaders,\n responseBody: text,\n errorMessage: `non-JSON response: ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`,\n redactedFields: [],\n })\n attemptErrorRecorded = true\n }\n throw parseErr\n }\n if (sink) {\n await recordRaw(sink, redactor, {\n eventId: cryptoEventId(),\n runId: traceContext?.runId,\n spanId: traceContext?.spanId,\n provider,\n model: req.model,\n endpoint,\n baseUrl,\n attemptIndex: attempt,\n direction: 'response',\n timestamp: Date.now(),\n durationMs: Date.now() - started,\n statusCode: res.status,\n responseHeaders,\n responseBody: json,\n redactedFields: [],\n })\n }\n const choice = (json.choices as Array<{ message?: { content?: string } }> | undefined)?.[0]\n const usageRaw = (json.usage as Record<string, unknown> | undefined) ?? {}\n const costFromProxy = (json._response_cost ?? json.cost_usd) as number | undefined\n\n return {\n content: choice?.message?.content ?? '',\n usage: {\n promptTokens: Number(usageRaw.prompt_tokens ?? 0),\n completionTokens: Number(usageRaw.completion_tokens ?? 0),\n totalTokens: Number(usageRaw.total_tokens ?? 0),\n cachedPromptTokens:\n usageRaw.prompt_tokens_details &&\n typeof usageRaw.prompt_tokens_details === 'object'\n ? Number(\n (usageRaw.prompt_tokens_details as Record<string, unknown>).cached_tokens ?? 0,\n )\n : undefined,\n },\n costUsd: typeof costFromProxy === 'number' ? costFromProxy : null,\n model: (json.model as string) ?? req.model,\n durationMs: Date.now() - started,\n raw: json,\n }\n } catch (err) {\n clearTimeout(timeoutHandle)\n lastErr = err\n if (sink && !attemptErrorRecorded) {\n // Record only if neither the !res.ok branch nor the JSON.parse catch\n // already produced an error event for this attempt. Covers network\n // failures, timeouts, and aborts.\n await recordRaw(sink, redactor, {\n eventId: cryptoEventId(),\n runId: traceContext?.runId,\n spanId: traceContext?.spanId,\n provider,\n model: req.model,\n endpoint,\n baseUrl,\n attemptIndex: attempt,\n direction: 'error',\n timestamp: Date.now(),\n durationMs: Date.now() - started,\n errorMessage: err instanceof Error ? err.message : String(err),\n redactedFields: [],\n })\n }\n if (attempt < maxRetries - 1 && isRetryableError(err)) {\n await sleep(backoffMs(attempt))\n continue\n }\n throw err\n }\n }\n throw lastErr instanceof Error ? lastErr : new Error(String(lastErr))\n}\n\nasync function recordRaw(\n sink: RawProviderSink,\n redactor: ProviderRedactor,\n event: RawProviderEvent,\n): Promise<void> {\n // Errors from sinks must not crash the LLM call. Forensic capture is\n // best-effort; the structured trace is the system of record.\n try {\n await sink.record(redactor(event))\n } catch {\n // Intentionally swallowed.\n }\n}\n\nfunction headersToObject(h: Headers): Record<string, string> {\n const out: Record<string, string> = {}\n h.forEach((value, key) => {\n out[key] = value\n })\n return out\n}\n\nfunction cryptoEventId(): string {\n if (typeof globalThis.crypto?.randomUUID === 'function') return globalThis.crypto.randomUUID()\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n}\n\n/**\n * Structured-output call. Returns parsed JSON plus the raw result envelope.\n * Degrades `jsonSchema` → `jsonMode` on a 400 that names the schema param —\n * critical for deepseek-v3/v4, kimi-k2.6, and other models that don't accept\n * the `response_format.json_schema` shape but DO accept `json_object`.\n */\nexport async function callLlmJson<T = unknown>(\n req: LlmCallRequest,\n opts: LlmClientOptions = {},\n): Promise<{ value: T; result: LlmCallResult }> {\n try {\n const result = await callLlm({ ...req, jsonMode: req.jsonMode ?? !req.jsonSchema }, opts)\n const value = parseJsonSafely<T>(result.content, result.model)\n return { value, result }\n } catch (err) {\n if (err instanceof LlmCallError && isSchemaRejection(err.status, err.body) && req.jsonSchema) {\n // Degrade to json_object + retry.\n const degradedReq: LlmCallRequest = { ...req, jsonMode: true, jsonSchema: undefined }\n const result = await callLlm(degradedReq, opts)\n const value = parseJsonSafely<T>(result.content, result.model)\n return { value, result }\n }\n throw err\n }\n}\n\nfunction parseJsonSafely<T>(content: string, model: string): T {\n const stripped = extractJsonPayload(content)\n try {\n return JSON.parse(stripped) as T\n } catch (err) {\n throw new Error(\n `LLM returned non-JSON content (model=${model}): ${\n err instanceof Error ? err.message : String(err)\n }\\n--- raw content ---\\n${content.slice(0, 800)}`,\n )\n }\n}\n\n// ─── Route assertion ────────────────────────────────────────────────────\n\nexport class LlmRouteAssertionError extends Error {\n constructor(\n message: string,\n public readonly code:\n | 'no_explicit_base_url'\n | 'base_url_blocked'\n | 'base_url_not_allowed'\n | 'no_auth'\n | 'wrong_provider',\n public readonly baseUrl: string,\n ) {\n super(message)\n this.name = 'LlmRouteAssertionError'\n }\n}\n\nexport interface LlmRouteRequirements {\n /**\n * Throw if `opts.baseUrl` is undefined, i.e. the call would fall back to\n * `DEFAULT_BASE_URL`. Set this for evaluation runs where silently using\n * the public/free-tier router is a defect — the launch reviewer needs to\n * know exactly which provider answered.\n */\n requireExplicitBaseUrl?: boolean\n /**\n * Allowlist of acceptable base URLs. Strings match by prefix\n * (case-insensitive); RegExps test against the full base URL.\n */\n allowedBaseUrls?: Array<string | RegExp>\n /** Blocklist that takes precedence over `allowedBaseUrls`. */\n blockedBaseUrls?: Array<string | RegExp>\n /** Throw if no auth header / api key is configured. */\n requireAuth?: boolean\n /**\n * Logical provider id the configured `baseUrl` is expected to match (via\n * `providerFromBaseUrl`). Mainly useful when paired with `requireExplicitBaseUrl`.\n */\n expectedProvider?: string\n}\n\n/**\n * Fail-loud assertion that the configured LLM client points at the route\n * the caller intends. Designed for the matrix-runner preflight: invoke\n * once before any LLM call to catch misconfiguration before a sweep burns\n * dollars on the wrong provider.\n *\n * Throws `LlmRouteAssertionError`. Pure — no I/O — so it's safe to call\n * from constructors and CI gates.\n */\nexport function assertLlmRoute(opts: LlmClientOptions, req: LlmRouteRequirements = {}): void {\n const baseUrlExplicit = opts.baseUrl !== undefined\n const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '')\n\n if (req.requireExplicitBaseUrl && !baseUrlExplicit) {\n throw new LlmRouteAssertionError(\n `assertLlmRoute: requireExplicitBaseUrl set but opts.baseUrl is undefined; would fall back to ${DEFAULT_BASE_URL}.`,\n 'no_explicit_base_url',\n baseUrl,\n )\n }\n\n if (req.blockedBaseUrls?.some((p) => matchUrl(baseUrl, p))) {\n throw new LlmRouteAssertionError(\n `assertLlmRoute: baseUrl ${baseUrl} matches a blocked pattern.`,\n 'base_url_blocked',\n baseUrl,\n )\n }\n\n if (req.allowedBaseUrls && req.allowedBaseUrls.length > 0) {\n const ok = req.allowedBaseUrls.some((p) => matchUrl(baseUrl, p))\n if (!ok) {\n throw new LlmRouteAssertionError(\n `assertLlmRoute: baseUrl ${baseUrl} is not in the allowed list (${req.allowedBaseUrls.map(describePattern).join(', ')}).`,\n 'base_url_not_allowed',\n baseUrl,\n )\n }\n }\n\n if (req.requireAuth && !opts.apiKey && !opts.bearer && !opts.authHeader) {\n throw new LlmRouteAssertionError(\n `assertLlmRoute: requireAuth set but no apiKey, bearer, or authHeader was supplied.`,\n 'no_auth',\n baseUrl,\n )\n }\n\n if (req.expectedProvider) {\n const actual = opts.provider ?? providerFromBaseUrl(baseUrl)\n if (actual !== req.expectedProvider) {\n throw new LlmRouteAssertionError(\n `assertLlmRoute: expected provider ${req.expectedProvider} but baseUrl ${baseUrl} resolves to ${actual}.`,\n 'wrong_provider',\n baseUrl,\n )\n }\n }\n}\n\nfunction matchUrl(url: string, pattern: string | RegExp): boolean {\n if (pattern instanceof RegExp) return pattern.test(url)\n return url.toLowerCase().startsWith(pattern.toLowerCase())\n}\n\nfunction describePattern(p: string | RegExp): string {\n return p instanceof RegExp ? p.source : p\n}\n\n/**\n * Probe whether a model is reachable. Returns latency + null error on\n * success; `ok=false` + error message on any failure (HTTP, timeout,\n * network, parse). Designed for sweep preflights — fail loud at the\n * boundary before burning a 30-leaf run on a misconfigured router.\n *\n * Sends a tiny `ping` message with `maxTokens=64`. Reasoning models\n * (glm-5.1, deepseek-v4) can burn the entire budget on internal reasoning\n * for short prompts, so don't tighten this further. We don't validate\n * content; HTTP 200 means reachable.\n */\nexport async function probeLlm(\n model: string,\n opts: LlmClientOptions & { timeoutMs?: number } = {},\n): Promise<{ ok: boolean; latencyMs: number; error: string | null }> {\n const start = Date.now()\n try {\n await callLlm(\n {\n model,\n messages: [{ role: 'user', content: 'ping' }],\n maxTokens: 64,\n timeoutMs: opts.timeoutMs ?? 30_000,\n },\n opts,\n )\n return { ok: true, latencyMs: Date.now() - start, error: null }\n } catch (err) {\n return {\n ok: false,\n latencyMs: Date.now() - start,\n error: err instanceof Error ? err.message : String(err),\n }\n }\n}\n\n/**\n * Stateful client — construct once with defaults, call many times.\n * Thin wrapper around the free functions; exists for callers that want\n * to inject a single configured instance into multiple primitives.\n */\nexport class LlmClient {\n constructor(private readonly opts: LlmClientOptions = {}) {}\n\n call(req: LlmCallRequest, per?: LlmClientOptions): Promise<LlmCallResult> {\n return callLlm(req, { ...this.opts, ...per })\n }\n\n callJson<T = unknown>(\n req: LlmCallRequest,\n per?: LlmClientOptions,\n ): Promise<{ value: T; result: LlmCallResult }> {\n return callLlmJson<T>(req, { ...this.opts, ...per })\n }\n}\n"],"mappings":"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","names":[]}