@zanii/blackbox 0.21.2 → 0.23.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 (95) hide show
  1. package/README.md +1 -0
  2. package/dist/actions/index.d.ts +29 -0
  3. package/dist/actions/index.js +114 -0
  4. package/dist/admission/index.d.ts +30 -0
  5. package/dist/admission/index.js +106 -0
  6. package/dist/analysis/accepted.d.ts +26 -0
  7. package/dist/analysis/accepted.js +61 -0
  8. package/dist/analysis/detectors.js +12 -9
  9. package/dist/analysis/faults.js +71 -0
  10. package/dist/analysis/grounding.js +5 -9
  11. package/dist/analysis/guard.d.ts +72 -0
  12. package/dist/analysis/guard.js +240 -0
  13. package/dist/analysis/hallucination.js +2 -2
  14. package/dist/analysis/index.d.ts +21 -1
  15. package/dist/analysis/index.js +101 -19
  16. package/dist/analysis/injection.d.ts +2 -1
  17. package/dist/analysis/injection.js +16 -16
  18. package/dist/analysis/landing.js +60 -14
  19. package/dist/analysis/plan-checks.d.ts +55 -0
  20. package/dist/analysis/plan-checks.js +748 -0
  21. package/dist/analysis/taxonomy.js +1 -0
  22. package/dist/analysis/waste.js +2 -3
  23. package/dist/approvals/index.d.ts +12 -3
  24. package/dist/approvals/index.js +36 -10
  25. package/dist/authority/index.js +3 -3
  26. package/dist/aviation/index.d.ts +422 -0
  27. package/dist/aviation/index.js +1014 -0
  28. package/dist/aviation/qrh.d.ts +4 -0
  29. package/dist/aviation/qrh.js +171 -0
  30. package/dist/budgets/index.d.ts +63 -0
  31. package/dist/budgets/index.js +161 -0
  32. package/dist/cli.js +199 -24
  33. package/dist/compliance/index.d.ts +2 -0
  34. package/dist/compliance/index.js +26 -2
  35. package/dist/cost/batch.d.ts +26 -0
  36. package/dist/cost/batch.js +173 -0
  37. package/dist/cost/index.d.ts +21 -0
  38. package/dist/cost/index.js +115 -30
  39. package/dist/cv/index.js +1 -1
  40. package/dist/duty/index.js +2 -1
  41. package/dist/evals/index.d.ts +233 -0
  42. package/dist/evals/index.js +488 -0
  43. package/dist/evidence/index.d.ts +9 -0
  44. package/dist/evidence/index.js +50 -0
  45. package/dist/federation/index.d.ts +9 -0
  46. package/dist/federation/index.js +8 -1
  47. package/dist/finance/index.js +3 -4
  48. package/dist/fleet/index.d.ts +25 -0
  49. package/dist/fleet/index.js +57 -17
  50. package/dist/fleet/insights.js +4 -1
  51. package/dist/health/index.js +2 -2
  52. package/dist/hooks/ai-sdk.d.ts +17 -19
  53. package/dist/hooks/ai-sdk.js +13 -7
  54. package/dist/hooks/index.d.ts +1 -0
  55. package/dist/hooks/index.js +1 -0
  56. package/dist/hooks/mastra.d.ts +26 -0
  57. package/dist/hooks/mastra.js +63 -0
  58. package/dist/index.d.ts +24 -9
  59. package/dist/index.js +25 -9
  60. package/dist/mcp-requests/index.d.ts +26 -0
  61. package/dist/mcp-requests/index.js +154 -0
  62. package/dist/mcp-server/index.d.ts +12 -0
  63. package/dist/mcp-server/index.js +84 -2
  64. package/dist/mcp-wrap/index.js +8 -4
  65. package/dist/otlp/index.d.ts +155 -0
  66. package/dist/otlp/index.js +468 -0
  67. package/dist/otlp/protobuf.d.ts +7 -0
  68. package/dist/otlp/protobuf.js +314 -0
  69. package/dist/packs/index.d.ts +2 -0
  70. package/dist/packs/index.js +5 -1
  71. package/dist/pdf/arabic.d.ts +45 -0
  72. package/dist/pdf/arabic.js +373 -0
  73. package/dist/pdf/index.d.ts +77 -0
  74. package/dist/pdf/index.js +673 -0
  75. package/dist/policy/index.d.ts +0 -4
  76. package/dist/policy/index.js +13 -5
  77. package/dist/realtime/index.d.ts +33 -0
  78. package/dist/realtime/index.js +204 -0
  79. package/dist/reconcile/record.d.ts +14 -0
  80. package/dist/reconcile/record.js +161 -66
  81. package/dist/session/index.d.ts +43 -0
  82. package/dist/session/index.js +83 -0
  83. package/dist/undo/index.js +3 -3
  84. package/dist/verify/anchor.d.ts +5 -1
  85. package/dist/verify/anchor.js +13 -4
  86. package/dist/verify/chain.d.ts +4 -0
  87. package/dist/verify/chain.js +4 -3
  88. package/dist/verify/envelope.d.ts +1 -1
  89. package/dist/verify/envelope.js +5 -0
  90. package/dist/version.d.ts +1 -1
  91. package/dist/version.js +1 -1
  92. package/dist/walls/index.js +2 -2
  93. package/dist/warehouse/index.d.ts +62 -0
  94. package/dist/warehouse/index.js +142 -0
  95. package/package.json +6 -3
@@ -1,6 +1,7 @@
1
1
  // The cost engine (spec/cost.md): integer micro-USD from a versioned price table, AED display.
2
2
  // Mirrors sdks/python/src/zanii_blackbox/cost.py.
3
3
  import { createHash } from "node:crypto";
4
+ import { envelopesOf } from "../reconcile/record.js";
4
5
  const CLASSES = ["input", "output", "cache_read", "cache_write"];
5
6
  const EXTRA = [
6
7
  "cache_write_1h",
@@ -47,6 +48,9 @@ export function loadPrices(bytes) {
47
48
  whole(model, `service_tiers.${name}.${c}`, t[c]);
48
49
  if (p.status !== undefined && p.status !== "deprecated" && p.status !== "beta")
49
50
  throw new Error(`price table: ${model}.status must be deprecated or beta`);
51
+ if (p.batch_percent !== undefined &&
52
+ !(Number.isSafeInteger(p.batch_percent) && p.batch_percent >= 1 && p.batch_percent <= 100))
53
+ throw new Error(`price table: ${model}.batch_percent must be an integer 1-100`);
50
54
  }
51
55
  if (table.uplifts !== undefined) {
52
56
  if (!Array.isArray(table.uplifts))
@@ -85,7 +89,8 @@ provider) {
85
89
  return plain !== model ? lookup(plain, table) : null;
86
90
  }
87
91
  const GEO = /^(?:us|eu|apac|au|ca|jp|global|us-gov)\./;
88
- const VENDOR = /^(?:anthropic|amazon|meta|mistral|cohere|ai21|deepseek|openai|qwen|writer|google)\./;
92
+ // `google/`: Vertex's OpenAI-compatible route (spec/cost.md §1)
93
+ const VENDOR = /^(?:(?:anthropic|amazon|meta|mistral|cohere|ai21|deepseek|openai|qwen|writer|google)\.|google\/)/;
89
94
  function lookup(model, table) {
90
95
  const exact = table.models[model];
91
96
  if (exact)
@@ -96,6 +101,12 @@ function lookup(model, table) {
96
101
  best = key;
97
102
  return best === null ? null : { key: best, price: table.models[best] };
98
103
  }
104
+ const SONIC = [
105
+ "input_speechTokens",
106
+ "input_textTokens",
107
+ "output_speechTokens",
108
+ "output_textTokens",
109
+ ];
99
110
  /** Token classes from a recorded, flattened `meta.usage` (spec/cost.md §2). */
100
111
  export function tokensOf(usage) {
101
112
  const n = (k) => {
@@ -105,14 +116,16 @@ export function tokensOf(usage) {
105
116
  if ("promptTokenCount" in usage) {
106
117
  // Gemini (spec/providers.md §5): the prompt count includes the cached part; thoughts are output.
107
118
  const cached = n("cachedContentTokenCount");
119
+ // Gemini Live names its output `responseTokenCount` (spec/providers.md §7)
108
120
  const audioIn = n("promptTokensDetails_AUDIO");
109
- const audioOut = n("candidatesTokensDetails_AUDIO");
121
+ const audioOut = n("candidatesTokensDetails_AUDIO") + n("responseTokensDetails_AUDIO");
110
122
  const imageIn = n("promptTokensDetails_IMAGE");
111
- const imageOut = n("candidatesTokensDetails_IMAGE");
123
+ const imageOut = n("candidatesTokensDetails_IMAGE") + n("responseTokensDetails_IMAGE");
112
124
  return extras({
113
125
  input: Math.max(0, n("promptTokenCount") - cached - audioIn - imageIn) +
114
126
  n("toolUsePromptTokenCount"),
115
- output: Math.max(0, n("candidatesTokenCount") - audioOut - imageOut) + n("thoughtsTokenCount"),
127
+ output: Math.max(0, n("candidatesTokenCount") + n("responseTokenCount") - audioOut - imageOut) +
128
+ n("thoughtsTokenCount"),
116
129
  cache_read: cached,
117
130
  cache_write: 0,
118
131
  }, {
@@ -122,6 +135,14 @@ export function tokensOf(usage) {
122
135
  output_image: imageOut,
123
136
  });
124
137
  }
138
+ if (SONIC.some((k) => k in usage))
139
+ // Nova Sonic's usageEvent delta (spec/providers.md §8): speech is priced as audio
140
+ return extras({
141
+ input: n("input_textTokens"),
142
+ output: n("output_textTokens"),
143
+ cache_read: 0,
144
+ cache_write: 0,
145
+ }, { input_audio: n("input_speechTokens"), output_audio: n("output_speechTokens") });
125
146
  if ("inputTokens" in usage)
126
147
  // Bedrock Converse (spec/providers.md §5)
127
148
  return {
@@ -146,16 +167,22 @@ export function tokensOf(usage) {
146
167
  // Anthropic's cache names are its own: they win over the Responses shape, since Anthropic also
147
168
  // sends output_tokens_details (thinking_tokens) since Claude Sonnet 5
148
169
  const anthropicCache = "cache_read_input_tokens" in usage || "cache_creation_input_tokens" in usage;
149
- // OpenAI's transcriptions write input_token_details (singular); its images, image_tokens
170
+ // OpenAI's transcriptions and Realtime write input_token_details (singular); its images,
171
+ // image_tokens. Realtime's audio and image counts include their cached part (spec/providers.md §7).
150
172
  if (!anthropicCache &&
151
173
  Object.keys(usage).some((k) => k.startsWith("input_tokens_details_") ||
152
174
  k.startsWith("output_tokens_details_") ||
153
- k.startsWith("input_token_details_"))) {
154
- const cached = n("input_tokens_details_cached_tokens");
175
+ k.startsWith("input_token_details_") ||
176
+ k.startsWith("output_token_details_"))) {
177
+ const cached = n("input_tokens_details_cached_tokens") + n("input_token_details_cached_tokens");
155
178
  const written = n("input_tokens_details_cache_write_tokens");
156
- const audioIn = n("input_tokens_details_audio_tokens") + n("input_token_details_audio_tokens");
157
- const audioOut = n("output_tokens_details_audio_tokens");
158
- const imageIn = n("input_tokens_details_image_tokens");
179
+ const audioIn = n("input_tokens_details_audio_tokens") +
180
+ Math.max(0, n("input_token_details_audio_tokens") -
181
+ n("input_token_details_cached_tokens_details_audio_tokens"));
182
+ const audioOut = n("output_tokens_details_audio_tokens") + n("output_token_details_audio_tokens");
183
+ const imageIn = n("input_tokens_details_image_tokens") +
184
+ Math.max(0, n("input_token_details_image_tokens") -
185
+ n("input_token_details_cached_tokens_details_image_tokens"));
159
186
  const imageOut = n("output_tokens_details_image_tokens");
160
187
  return extras({
161
188
  input: Math.max(0, n("input_tokens") - cached - written - audioIn - imageIn),
@@ -249,7 +276,17 @@ export function priceCall(call, table) {
249
276
  const match = call.model === null ? null : priceFor(call.model, table, call.provider);
250
277
  if (match) {
251
278
  const { price, tier } = effectivePrice(match.price, tokens, call.serviceTier);
252
- const base = callMicroUsd(tokens, price, requests);
279
+ const percent = match.price.batch_percent;
280
+ if (call.batch && percent === undefined)
281
+ return {
282
+ key: match.key,
283
+ tokens,
284
+ micro_usd: null,
285
+ ...(requests ? { requests } : {}),
286
+ batch: { requests: call.batch.requests },
287
+ };
288
+ const full = callMicroUsd(tokens, price, requests);
289
+ const base = call.batch ? Math.floor((full * percent + 50) / 100) : full;
253
290
  const uplift = upliftPercent(table, call.provider ?? null, call.model, call.usage);
254
291
  return {
255
292
  key: match.key,
@@ -258,8 +295,19 @@ export function priceCall(call, table) {
258
295
  ...(requests ? { requests } : {}),
259
296
  ...(tier ? { tier } : {}),
260
297
  ...(uplift ? { uplift_percent: uplift } : {}),
298
+ ...(call.batch
299
+ ? { batch: { requests: call.batch.requests, percent: percent } }
300
+ : {}),
261
301
  };
262
302
  }
303
+ if (call.batch)
304
+ return {
305
+ key: null,
306
+ tokens,
307
+ micro_usd: null,
308
+ ...(requests ? { requests } : {}),
309
+ batch: { requests: call.batch.requests },
310
+ };
263
311
  if (typeof call.reportedMicroUsd === "number")
264
312
  return {
265
313
  key: null,
@@ -294,6 +342,22 @@ export function formatAed(fils) {
294
342
  const abs = Math.abs(fils);
295
343
  return `${sign}AED ${Math.floor(abs / 100)}.${String(abs % 100).padStart(2, "0")}`;
296
344
  }
345
+ /** A results download's `meta.batch.models` (spec/providers.md §6), the well-formed entries; null
346
+ * when the response isn't one. */
347
+ export function batchModels(batch) {
348
+ if (typeof batch !== "object" || batch === null)
349
+ return null;
350
+ const models = batch.models;
351
+ if (!Array.isArray(models))
352
+ return null;
353
+ return models.filter((m) => typeof m === "object" &&
354
+ m !== null &&
355
+ typeof m.model === "string" &&
356
+ Number.isSafeInteger(m.requests) &&
357
+ typeof m.usage === "object" &&
358
+ m.usage !== null &&
359
+ !Array.isArray(m.usage));
360
+ }
297
361
  /** The session's cost report from its recorded lines. */
298
362
  export function sessionCost(lines, prices) {
299
363
  const calls = [];
@@ -304,8 +368,7 @@ export function sessionCost(lines, prices) {
304
368
  const providers = new Map(); // request seq → the configured provider id
305
369
  let webSearch = 0;
306
370
  let pinnedSha; // session.open's meta.prices_sha256 (L3.1.5)
307
- for (const line of lines) {
308
- const e = JSON.parse(line);
371
+ for (const e of envelopesOf(lines)) {
309
372
  if (e.kind === "session.open")
310
373
  pinnedSha = e.meta.prices_sha256;
311
374
  if (e.kind === "llm.request") {
@@ -316,10 +379,44 @@ export function sessionCost(lines, prices) {
316
379
  providers.set(e.seq, e.meta.provider);
317
380
  continue;
318
381
  }
319
- if (e.kind !== "llm.response" && e.kind !== "llm.incomplete")
382
+ // spec/cost.md §2: a client's sampling reply that names its model and usage is a model call too
383
+ const sampling = e.kind === "tool.sampling" && e.meta.phase === "reply" && typeof e.meta.model === "string";
384
+ // spec/providers.md §7: a realtime session's usage reports are its model calls
385
+ const realtime = e.kind === "llm.realtime.server";
386
+ if (e.kind !== "llm.response" && e.kind !== "llm.incomplete" && !sampling && !realtime)
320
387
  continue;
321
- if (replayed.has(e.meta.request_seq))
388
+ if (!sampling && replayed.has(e.meta.request_seq))
322
389
  continue;
390
+ const provider = sampling ? null : (providers.get(e.meta.request_seq) ?? null);
391
+ const via = sampling
392
+ ? { via: "mcp_sampling" }
393
+ : realtime
394
+ ? { via: "realtime" }
395
+ : {};
396
+ const add = (seq, model, priced) => {
397
+ const { key, ...call } = priced;
398
+ for (const c of CLASSES)
399
+ total[c] += call.tokens[c];
400
+ for (const c of EXTRA)
401
+ if (call.tokens[c] !== undefined)
402
+ total[c] = (total[c] ?? 0) + call.tokens[c];
403
+ webSearch += call.requests?.web_search ?? 0;
404
+ // a model with a price but no batch rate is unpriced, not unknown (spec/cost.md §2)
405
+ if (call.micro_usd === null) {
406
+ if (key === null)
407
+ unknown.add(model ?? "(no model)");
408
+ }
409
+ else
410
+ micro += call.micro_usd;
411
+ calls.push({ seq, model, ...call, ...via });
412
+ };
413
+ // spec/cost.md §2: a batch results download, one call per model, at the batch rate
414
+ const batch = sampling ? null : batchModels(e.meta.batch);
415
+ if (batch) {
416
+ for (const m of batch)
417
+ add(e.seq, m.model, priceCall({ model: m.model, usage: m.usage, provider, batch: { requests: m.requests } }, prices.table));
418
+ continue;
419
+ }
323
420
  const usage = e.meta.usage;
324
421
  const model = typeof e.meta.model === "string" ? e.meta.model : null;
325
422
  if (typeof usage !== "object" || usage === null) {
@@ -334,27 +431,15 @@ export function sessionCost(lines, prices) {
334
431
  });
335
432
  continue;
336
433
  }
337
- const priced = priceCall({
434
+ add(e.seq, model, priceCall({
338
435
  model,
339
436
  usage: usage,
340
- provider: providers.get(e.meta.request_seq) ?? null,
437
+ provider,
341
438
  serviceTier: typeof e.meta.service_tier === "string" ? e.meta.service_tier : null,
342
439
  reportedMicroUsd: Number.isSafeInteger(e.meta.reported_cost_micro_usd)
343
440
  ? e.meta.reported_cost_micro_usd
344
441
  : null,
345
- }, prices.table);
346
- const { key: _key, ...call } = priced;
347
- for (const c of CLASSES)
348
- total[c] += call.tokens[c];
349
- for (const c of EXTRA)
350
- if (call.tokens[c] !== undefined)
351
- total[c] = (total[c] ?? 0) + call.tokens[c];
352
- webSearch += call.requests?.web_search ?? 0;
353
- if (call.micro_usd === null)
354
- unknown.add(model ?? "(no model)");
355
- else
356
- micro += call.micro_usd;
357
- calls.push({ seq: e.seq, model, ...call });
442
+ }, prices.table));
358
443
  }
359
444
  return {
360
445
  v: 1,
package/dist/cv/index.js CHANGED
@@ -17,7 +17,7 @@ export function buildCv(o) {
17
17
  v: 1,
18
18
  type: "agentcv",
19
19
  did: o.did,
20
- owner: o.owner ?? null,
20
+ ...(o.owner != null ? { owner: o.owner } : {}), // left out when unset, as zanii 0.26 does
21
21
  summary: o.summary,
22
22
  work: o.work ?? [],
23
23
  authority: o.authority ?? [],
@@ -1,6 +1,7 @@
1
1
  // Duty-time limits (spec/duty.md): long sessions and full context windows, detected. Pure; mirrors
2
2
  // sdks/python/src/zanii_blackbox/duty.py; pinned by spec/vectors/duty.json.
3
3
  import { priceFor, promptTokens, sessionCost } from "../cost/index.js";
4
+ import { envelopesOf } from "../reconcile/record.js";
4
5
  const KEYS = ["max_minutes", "max_llm_calls", "max_context_pct"];
5
6
  /** spec/duty.md §1: the flight plan's limits win, limit by limit, over the server's defaults. */
6
7
  function limitsOf(open, defaults) {
@@ -15,7 +16,7 @@ function limitsOf(open, defaults) {
15
16
  }
16
17
  /** spec/duty.md §2: at most one DUTY_LIMIT per limit, where it was first crossed. */
17
18
  export function dutyFindings(lines, prices, defaults = {}) {
18
- const events = lines.map((l) => JSON.parse(l));
19
+ const events = envelopesOf(lines);
19
20
  const open = events[0]?.kind === "session.open" ? events[0] : undefined;
20
21
  const limits = limitsOf(open, defaults);
21
22
  const out = [];
@@ -0,0 +1,233 @@
1
+ import type { Summary } from "../fleet/index.ts";
2
+ type Obj = {
3
+ [key: string]: unknown;
4
+ };
5
+ export declare const SCORERS: readonly ["exact", "contains", "regex", "json_schema", "numeric", "tool_called", "tool_not_called", "findings_absent", "judge"];
6
+ export type ScorerKind = (typeof SCORERS)[number];
7
+ export interface Scorer {
8
+ kind: ScorerKind;
9
+ pattern?: string;
10
+ schema?: Obj;
11
+ tolerance_milli?: number;
12
+ tool?: string;
13
+ codes?: string[];
14
+ }
15
+ export interface EvalCase {
16
+ id: string;
17
+ input: unknown;
18
+ expected?: string | number;
19
+ rubric?: string;
20
+ tags: string[];
21
+ scorer: Scorer;
22
+ }
23
+ export declare const CASE_ID: RegExp;
24
+ /** §2: a scorer, checked: the normalised scorer, or what's wrong with it. */
25
+ export declare function checkScorer(v: unknown, at?: string): Scorer | string;
26
+ /**
27
+ * §1: a dataset, checked: `{name, cases}` with every case normalised (an id, tags, its scorer or
28
+ * the dataset's), or the first problem. Numbers must be integers: decimals are written as strings.
29
+ */
30
+ export declare function checkDataset(v: unknown): {
31
+ name: string;
32
+ cases: EvalCase[];
33
+ } | string;
34
+ /** §1: what names a dataset's content exactly: `sha256:` over the canonical JSON of {name, cases}. */
35
+ export declare function datasetHash(d: {
36
+ name: string;
37
+ cases: readonly EvalCase[];
38
+ }): string;
39
+ /** §1: a JSON Lines file of cases: one object per non-empty line, or the first line that isn't. */
40
+ export declare function parseCasesJsonl(text: string): unknown[] | string;
41
+ /** §1: one case per saved scenario (spec/replay.md §3): a fork of the incident, passing when none
42
+ * of its findings comes back. */
43
+ export declare function casesFromScenarios(scenarios: readonly unknown[]): Obj[] | string;
44
+ /** §1: one case per recorded session: a fork of it, expected to answer what it answered then. */
45
+ export declare function casesFromSessions(items: ReadonlyArray<{
46
+ session_id: string;
47
+ answer: string;
48
+ }>): Obj[];
49
+ /** What one run of the agent left, read from its record: the final answer, the tools it called,
50
+ * its findings, and the judge's verdict (undefined: not asked; null: an outage). */
51
+ export interface Observation {
52
+ answer: string | null;
53
+ tools: {
54
+ [tool: string]: number;
55
+ };
56
+ finding_codes: {
57
+ [code: string]: number;
58
+ };
59
+ judge?: {
60
+ steps: Array<{
61
+ rubric: string;
62
+ pass: boolean;
63
+ reason?: string;
64
+ }>;
65
+ } | null;
66
+ }
67
+ export interface CaseScore {
68
+ pass: boolean;
69
+ reason: string;
70
+ outage?: true;
71
+ }
72
+ /** A decimal as integer milli-units, the digits past the third decimal dropped. */
73
+ export declare function toMilli(s: string): bigint;
74
+ /** §2: one case scored against what its run left. An outage of the judge is never a pass. */
75
+ export declare function scoreCase(c: EvalCase, o: Observation): CaseScore;
76
+ export interface RunSummary {
77
+ total: number;
78
+ passed: number;
79
+ failed: number;
80
+ outages: number;
81
+ pass_ppm: number;
82
+ pass_low_ppm: number;
83
+ pass_high_ppm: number;
84
+ cost_micro_usd: number;
85
+ cost_per_case_micro_usd: number;
86
+ tags: {
87
+ [tag: string]: {
88
+ total: number;
89
+ passed: number;
90
+ };
91
+ };
92
+ }
93
+ /** §3: a run's totals: passes with the 95% range, outages, cost, and per tag. */
94
+ export declare function runSummary(results: ReadonlyArray<{
95
+ pass: boolean;
96
+ outage?: boolean;
97
+ cost_micro_usd: number;
98
+ tags?: readonly string[];
99
+ }>): RunSummary;
100
+ type Verdict = "better" | "worse" | "unclear";
101
+ interface RunDoc {
102
+ id: string;
103
+ dataset: {
104
+ id: string;
105
+ hash: string;
106
+ };
107
+ results: ReadonlyArray<{
108
+ case: string;
109
+ pass: boolean;
110
+ }>;
111
+ summary: RunSummary;
112
+ }
113
+ /** §3: run `b` against run `a`: better, worse or unclear, and the cases each fixed or broke. */
114
+ export declare function compareRuns(a: RunDoc, b: RunDoc): {
115
+ a: string;
116
+ b: string;
117
+ same_dataset: boolean;
118
+ verdict: Verdict;
119
+ pass_ppm: {
120
+ a: number;
121
+ b: number;
122
+ };
123
+ pass_range_ppm: {
124
+ a: number[];
125
+ b: number[];
126
+ };
127
+ fixed: string[];
128
+ broken: string[];
129
+ cost_per_case_delta_micro_usd: number;
130
+ };
131
+ export declare const PROMPT_NAME: RegExp;
132
+ /** §4: a prompt text's version hash: the gateway's `system_sha256` of a request whose system
133
+ * prompt is this text (an Anthropic `system` string, or a Chat Completions system message). */
134
+ export declare function promptSha256(text: string): string;
135
+ /** §5: the arm a session falls in: the first 8 hex of sha256(`<experiment>:<session>`) mod 1000,
136
+ * walked through the arms' per-mille shares in order. The same session always gets the same arm. */
137
+ export declare function assignArm(experimentId: string, sessionId: string, arms: ReadonlyArray<{
138
+ name: string;
139
+ share_permille: number;
140
+ }>): string;
141
+ export interface Arm {
142
+ name: string;
143
+ version: number;
144
+ share_permille: number;
145
+ }
146
+ /** §5: an experiment, checked against its prompt's versions: `{name, prompt, arms}`, or why not. */
147
+ export declare function checkExperiment(v: unknown, versions: readonly number[]): {
148
+ name: string;
149
+ prompt: string;
150
+ arms: Arm[];
151
+ } | string;
152
+ /** §4: how a group of runs did: success with the 95% range, spend per run, findings. */
153
+ export declare function armStats(runs: readonly Summary[]): {
154
+ sessions: number;
155
+ success: number;
156
+ success_ppm: number;
157
+ success_low_ppm: number;
158
+ success_high_ppm: number;
159
+ spend_per_run_micro_usd: number;
160
+ findings: {
161
+ advisory: number;
162
+ caution: number;
163
+ warning: number;
164
+ };
165
+ finding_codes: {
166
+ [k: string]: number;
167
+ };
168
+ };
169
+ /**
170
+ * §4: each version of a prompt, from closed sessions' summaries: the runs that declared it (an
171
+ * experiment's arm or the SDK's `promptVersion`), and the runs that declared none whose first
172
+ * request's system prompt hash is the version's. Oldest version first, each against the one before.
173
+ */
174
+ export declare function promptResults(summaries: readonly Summary[], prompt: {
175
+ name: string;
176
+ versions: ReadonlyArray<{
177
+ version: number;
178
+ sha256: string;
179
+ }>;
180
+ }): {
181
+ name: string;
182
+ sessions: number;
183
+ versions: {
184
+ sessions: number;
185
+ success: number;
186
+ success_ppm: number;
187
+ success_low_ppm: number;
188
+ success_high_ppm: number;
189
+ spend_per_run_micro_usd: number;
190
+ findings: {
191
+ advisory: number;
192
+ caution: number;
193
+ warning: number;
194
+ };
195
+ finding_codes: {
196
+ [k: string]: number;
197
+ };
198
+ version: number;
199
+ sha256: string;
200
+ declared: number;
201
+ detected: number;
202
+ vs_previous: Verdict | null;
203
+ }[];
204
+ };
205
+ /** §5: each arm of an experiment, from the closed sessions it assigned, against the first arm. */
206
+ export declare function experimentResults(summaries: readonly Summary[], experiment: {
207
+ id: string;
208
+ arms: readonly Arm[];
209
+ }): {
210
+ id: string;
211
+ sessions: number;
212
+ arms: {
213
+ sessions: number;
214
+ success: number;
215
+ success_ppm: number;
216
+ success_low_ppm: number;
217
+ success_high_ppm: number;
218
+ spend_per_run_micro_usd: number;
219
+ findings: {
220
+ advisory: number;
221
+ caution: number;
222
+ warning: number;
223
+ };
224
+ finding_codes: {
225
+ [k: string]: number;
226
+ };
227
+ name: string;
228
+ version: number;
229
+ share_permille: number;
230
+ vs_control: Verdict | null;
231
+ }[];
232
+ };
233
+ export {};