@attest-ai/core 1.0.0-rc.1 → 1.0.0-rc.2

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/dist/esm/index.js CHANGED
@@ -12,6 +12,8 @@ var REASON_PROVIDER_NOT_CONFIGURED = "provider_not_configured";
12
12
  var REASON_SIMULATION = "simulation";
13
13
  var REASON_MISSING_EVIDENCE = "missing_evidence";
14
14
  var REASON_SHORT_CIRCUITED = "short_circuited";
15
+ var REASON_BUDGET_EXHAUSTED = "budget_exhausted";
16
+ var REASON_JUDGE_ABSTAINED = "judge_abstained";
15
17
  var OUTCOME_PASS = "pass";
16
18
  var OUTCOME_FAIL = "fail";
17
19
  var OUTCOME_SKIP = "skip";
@@ -102,6 +104,12 @@ var ProtocolDesyncError = class extends Error {
102
104
  this.diagnostics = diagnostics;
103
105
  }
104
106
  };
107
+ var OTLPConversionError = class extends Error {
108
+ constructor(message) {
109
+ super(message);
110
+ this.name = "OTLPConversionError";
111
+ }
112
+ };
105
113
 
106
114
  // src/proto/diagnostics.ts
107
115
  var DEFAULT_BUFFER_SIZE = 32;
@@ -208,8 +216,8 @@ function extractId(response) {
208
216
  }
209
217
 
210
218
  // src/version.ts
211
- var VERSION = "1.0.0-rc.1";
212
- var ENGINE_VERSION = "1.0.0-rc.1";
219
+ var VERSION = "1.0.0-rc.2";
220
+ var ENGINE_VERSION = "1.0.0-rc.2";
213
221
 
214
222
  // src/engine-manager.ts
215
223
  import { spawn, spawnSync } from "child_process";
@@ -602,6 +610,76 @@ function resetConfig() {
602
610
  _sampleRate = 0;
603
611
  }
604
612
 
613
+ // src/usage.ts
614
+ var INT_FIELDS = [
615
+ "model_calls",
616
+ "judge_calls",
617
+ "input_tokens",
618
+ "output_tokens",
619
+ "cached_tokens",
620
+ "unpriced_calls",
621
+ "budget_refused"
622
+ ];
623
+ var USD_FIELDS = ["cost_usd", "retry_cost_usd"];
624
+ function zeroUsage() {
625
+ return {
626
+ model_calls: 0,
627
+ judge_calls: 0,
628
+ input_tokens: 0,
629
+ output_tokens: 0,
630
+ cached_tokens: 0,
631
+ cost_usd: 0,
632
+ retry_cost_usd: 0,
633
+ unpriced_calls: 0,
634
+ budget_refused: 0
635
+ };
636
+ }
637
+ function addUsage(a, b) {
638
+ return {
639
+ model_calls: a.model_calls + b.model_calls,
640
+ judge_calls: a.judge_calls + b.judge_calls,
641
+ input_tokens: a.input_tokens + b.input_tokens,
642
+ output_tokens: a.output_tokens + b.output_tokens,
643
+ cached_tokens: a.cached_tokens + b.cached_tokens,
644
+ cost_usd: a.cost_usd + b.cost_usd,
645
+ retry_cost_usd: a.retry_cost_usd + b.retry_cost_usd,
646
+ unpriced_calls: a.unpriced_calls + b.unpriced_calls,
647
+ budget_refused: a.budget_refused + b.budget_refused
648
+ };
649
+ }
650
+ function sumUsage(usages) {
651
+ let total = zeroUsage();
652
+ for (const u of usages) total = addUsage(total, u);
653
+ return total;
654
+ }
655
+ function parseUsage(raw) {
656
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
657
+ throw new TypeError(`evaluate_batch result: usage must be an object, got ${JSON.stringify(raw)}`);
658
+ }
659
+ const obj = raw;
660
+ const field = (name, integer) => {
661
+ const v = obj[name];
662
+ if (typeof v !== "number" || !Number.isFinite(v) || v < 0 || integer && !Number.isInteger(v)) {
663
+ const kind = integer ? "a non-negative integer" : "a non-negative number";
664
+ throw new TypeError(`evaluate_batch result: usage.${name} must be ${kind}, got ${JSON.stringify(v)}`);
665
+ }
666
+ return v;
667
+ };
668
+ const ints = Object.fromEntries(INT_FIELDS.map((f) => [f, field(f, true)]));
669
+ const usd = Object.fromEntries(USD_FIELDS.map((f) => [f, field(f, false)]));
670
+ return { ...ints, ...usd };
671
+ }
672
+ function formatUsage(u) {
673
+ let line = `${u.model_calls} model calls \xB7 ${u.input_tokens + u.output_tokens} tokens (${u.cached_tokens} cached) \xB7 $${u.cost_usd.toFixed(4)} \xB7 retry cost $${u.retry_cost_usd.toFixed(4)}`;
674
+ if (u.budget_refused > 0) line += ` \xB7 ${u.budget_refused} calls refused by budget`;
675
+ if (u.unpriced_calls > 0) line += ` \xB7 ${u.unpriced_calls} unpriced calls not in $`;
676
+ return line;
677
+ }
678
+ function usageSummaryLine(u) {
679
+ if (u.model_calls === 0 && u.budget_refused === 0) return void 0;
680
+ return `attest usage: ${formatUsage(u)}`;
681
+ }
682
+
605
683
  // src/client.ts
606
684
  var DEFAULT_TIMEOUT_MS = 3e4;
607
685
  var DEFAULT_DIAGNOSTIC_BUFFER = 32;
@@ -855,14 +933,15 @@ var AttestClient = class {
855
933
  }
856
934
  const params = {
857
935
  trace,
858
- assertions: [...assertions]
936
+ assertions: [...assertions],
937
+ ...options?.budget !== void 0 ? { budget: options.budget } : {}
859
938
  };
860
939
  const raw = await this.sendRequest(
861
940
  "evaluate_batch",
862
941
  params,
863
942
  options?.timeout
864
943
  );
865
- return raw;
944
+ return { ...raw, usage: parseUsage(raw["usage"]) };
866
945
  }
867
946
  async submitPluginResult(traceId2, pluginName, assertionId2, status, score, explanation, options) {
868
947
  const result = {
@@ -1050,11 +1129,14 @@ var AgentResult = class {
1050
1129
  assertionResults;
1051
1130
  totalCost;
1052
1131
  totalDurationMs;
1053
- constructor(trace, assertionResults = [], totalCost = 0, totalDurationMs = 0) {
1132
+ /** Attest model usage of the evaluation that produced this result. */
1133
+ usage;
1134
+ constructor(trace, assertionResults = [], totalCost = 0, totalDurationMs = 0, usage = zeroUsage()) {
1054
1135
  this.trace = trace;
1055
1136
  this.assertionResults = assertionResults.map(normalizeAssertionResult);
1056
1137
  this.totalCost = totalCost;
1057
1138
  this.totalDurationMs = totalDurationMs;
1139
+ this.usage = usage;
1058
1140
  }
1059
1141
  /** True if every assertion has status "pass". */
1060
1142
  get passed() {
@@ -1233,7 +1315,8 @@ function appendJudgeMetadata(lines, meta) {
1233
1315
  if (meta.rubric_version) {
1234
1316
  rubric = `${rubric} @ ${meta.rubric_version}`;
1235
1317
  }
1236
- lines.push(` judge: ${meta.model} / ${rubric}`);
1318
+ const judge = meta.provider ? `${meta.provider}/${meta.model}` : meta.model;
1319
+ lines.push(` judge: ${judge} / ${rubric}`);
1237
1320
  }
1238
1321
  if (meta.prompt_hash) {
1239
1322
  lines.push(` prompt: #${meta.prompt_hash}`);
@@ -1757,6 +1840,86 @@ var ExpectChain = class {
1757
1840
  soft: opts?.soft ?? false
1758
1841
  });
1759
1842
  }
1843
+ // -- Layer 3: Behavior contracts (tool calls only, retries counted) --
1844
+ /**
1845
+ * Assert how many times a tool is called, per value of argument `per` when set.
1846
+ *
1847
+ * @example
1848
+ * ```ts
1849
+ * // fails with "issue_refund called 2× for order_id=O-1042 (max 1)"
1850
+ * expect(result).toolCardinality("issue_refund", { per: "order_id", atMost: 1 });
1851
+ * ```
1852
+ */
1853
+ toolCardinality(tool, opts) {
1854
+ if (opts.atMost === void 0 && opts.atLeast === void 0) {
1855
+ throw new Error("toolCardinality needs atMost, atLeast or both");
1856
+ }
1857
+ const spec = { check: "tool_cardinality", tool };
1858
+ if (opts.per !== void 0) spec.per = opts.per;
1859
+ if (opts.atLeast !== void 0) spec.min = opts.atLeast;
1860
+ if (opts.atMost !== void 0) spec.max = opts.atMost;
1861
+ spec.soft = opts.soft ?? false;
1862
+ return this.add(TYPE_TRACE, spec);
1863
+ }
1864
+ /**
1865
+ * Assert the tool-call trajectory matches `tools`: `strict` (same sequence,
1866
+ * the default), `unordered` (same calls, any order), `subset` (no call
1867
+ * outside `tools`) or `superset` (every call in `tools` made). Use
1868
+ * {@link trajectoryPartialOrder} for ordering constraints.
1869
+ */
1870
+ trajectory(tools, opts) {
1871
+ return this.add(TYPE_TRACE, {
1872
+ check: "trajectory",
1873
+ mode: opts?.mode ?? "strict",
1874
+ tools,
1875
+ soft: opts?.soft ?? false
1876
+ });
1877
+ }
1878
+ /**
1879
+ * Assert every tool in `tools` and `order` is called and each
1880
+ * `[before, after]` pair holds as in {@link toolCalledBefore}.
1881
+ */
1882
+ trajectoryPartialOrder(order, opts) {
1883
+ const spec = {
1884
+ check: "trajectory",
1885
+ mode: "partial_order",
1886
+ order: order.map(([before, after]) => [before, after])
1887
+ };
1888
+ if (opts?.tools !== void 0) spec.tools = opts.tools;
1889
+ spec.soft = opts?.soft ?? false;
1890
+ return this.add(TYPE_TRACE, spec);
1891
+ }
1892
+ /** Assert every call to `after` has an earlier call to `before`; holds when `after` is never called. */
1893
+ toolCalledBefore(before, after, opts) {
1894
+ return this.add(TYPE_TRACE, { check: "tool_order", before, after, soft: opts?.soft ?? false });
1895
+ }
1896
+ /** Assert no block of `period` (1–4) tool calls repeats `minRepeats` (default 2) times in a row; A→B→A→B fails period 2. */
1897
+ noToolCycles(period, opts) {
1898
+ return this.add(TYPE_TRACE, {
1899
+ check: "cycle",
1900
+ period,
1901
+ min_repeats: opts?.minRepeats ?? 2,
1902
+ soft: opts?.soft ?? false
1903
+ });
1904
+ }
1905
+ /** Assert at most `maxStreak` consecutive tool calls end in error (trace v2). */
1906
+ maxToolFailureStreak(maxStreak, opts) {
1907
+ return this.add(TYPE_TRACE, { check: "failure_streak", max: maxStreak, soft: opts?.soft ?? false });
1908
+ }
1909
+ /**
1910
+ * Assert every call to `tool` has argument `arg` (a dot path) satisfying
1911
+ * `op value`; holds when `tool` is never called.
1912
+ */
1913
+ toolArgSatisfies(tool, arg, op, value, opts) {
1914
+ return this.add(TYPE_TRACE, {
1915
+ check: "arg_predicate",
1916
+ tool,
1917
+ arg,
1918
+ op,
1919
+ value,
1920
+ soft: opts?.soft ?? false
1921
+ });
1922
+ }
1760
1923
  // -- Layer 4: Content --
1761
1924
  /**
1762
1925
  * Assert the output message contains the given string.
@@ -2927,119 +3090,113 @@ var OllamaAdapter = class {
2927
3090
  };
2928
3091
 
2929
3092
  // src/adapters/otel.ts
3093
+ import { spawnSync as spawnSync2 } from "child_process";
3094
+ var CONVERT_TIMEOUT_MS = 6e4;
3095
+ var MAX_TRACE_OUTPUT_BYTES = 64 * 1024 * 1024;
3096
+ var STATUS_CODE_ERROR = 2;
2930
3097
  var OTelAdapter = class _OTelAdapter {
2931
- agentId;
2932
- constructor(agentId2) {
2933
- this.agentId = agentId2;
2934
- }
2935
- static fromSpans(spans, agentId2) {
2936
- const adapter = new _OTelAdapter(agentId2);
2937
- return adapter.buildTrace(spans);
3098
+ /**
3099
+ * Build a trace from finished OpenTelemetry spans of one trace.
3100
+ *
3101
+ * @param agentId Trace `agent_id`; defaults to the first turn's `gen_ai.agent.name`.
3102
+ * @throws OTLPConversionError when the engine rejects the spans.
3103
+ */
3104
+ static async fromSpans(spans, agentId2) {
3105
+ return _OTelAdapter.fromOtlp(spansToOtlp(spans), agentId2);
2938
3106
  }
2939
- buildTrace(spans) {
2940
- const sorted = [...spans].sort((a, b) => {
2941
- const aTime = a.startTime[0] * 1e9 + a.startTime[1];
2942
- const bTime = b.startTime[0] * 1e9 + b.startTime[1];
2943
- return aTime - bTime;
3107
+ /**
3108
+ * Convert an OTLP/JSON export (`resourceSpans` → `scopeSpans` → `spans`).
3109
+ *
3110
+ * @param agentId Trace `agent_id`; defaults to the first turn's `gen_ai.agent.name`.
3111
+ * @throws OTLPConversionError when the engine rejects the export (malformed JSON, invalid
3112
+ * ids or timestamps, more than one trace, non-numeric token counts, ...).
3113
+ * @throws RangeError when a parsed export holds a non-finite number.
3114
+ * @throws OTLPConversionError when the engine times out, overflows the output buffer or
3115
+ * prints no trace.
3116
+ * @throws Error from `spawnSync` when the engine cannot start.
3117
+ */
3118
+ static async fromOtlp(otlp, agentId2) {
3119
+ const payload = typeof otlp === "string" || otlp instanceof Uint8Array ? otlp : JSON.stringify(otlp, (_key, v) => {
3120
+ if (typeof v === "number" && !Number.isFinite(v)) {
3121
+ throw new RangeError(`OTLP export holds a non-finite number: ${String(v)}`);
3122
+ }
3123
+ return v;
2944
3124
  });
2945
- const builder = new TraceBuilder(this.agentId);
2946
- const rootSpan = this.findRootSpan(sorted);
2947
- if (rootSpan) {
2948
- const traceId2 = rootSpan.spanContext().traceId;
2949
- if (traceId2) {
2950
- builder.setTraceId(`otel_${traceId2.slice(0, 16)}`);
3125
+ const args = ["trace", "convert"];
3126
+ if (agentId2 !== void 0) args.push("--agent-id", agentId2);
3127
+ args.push("-");
3128
+ const proc = spawnSync2(await resolveEngine(), args, {
3129
+ input: payload,
3130
+ timeout: CONVERT_TIMEOUT_MS,
3131
+ maxBuffer: MAX_TRACE_OUTPUT_BYTES
3132
+ });
3133
+ if (proc.error) {
3134
+ const code = proc.error.code;
3135
+ if (code === "ETIMEDOUT") {
3136
+ throw new OTLPConversionError(
3137
+ `attest-engine trace convert timed out after ${CONVERT_TIMEOUT_MS / 1e3}s`
3138
+ );
2951
3139
  }
2952
- }
2953
- let outputMessage = "";
2954
- let totalTokens;
2955
- let latencyMs;
2956
- let model;
2957
- for (const span of sorted) {
2958
- const attrs = span.attributes ?? {};
2959
- const stepType = this.classifySpan(attrs, span.name);
2960
- if (stepType === "llm_call") {
2961
- const [stepArgs, stepResult] = this.extractLlmStep(attrs);
2962
- builder.addLlmCall(span.name, {
2963
- args: stepArgs,
2964
- result: stepResult,
2965
- metadata: this.spanMetadata(span)
2966
- });
2967
- const completion = attrs["gen_ai.completion"];
2968
- if (completion) outputMessage = String(completion);
2969
- const inputTokens = Number(attrs["gen_ai.usage.input_tokens"] ?? 0);
2970
- const outputTokens = Number(attrs["gen_ai.usage.output_tokens"] ?? 0);
2971
- const spanTokens = inputTokens + outputTokens;
2972
- if (spanTokens > 0) totalTokens = (totalTokens ?? 0) + spanTokens;
2973
- if (model === void 0) {
2974
- const responseModel = attrs["gen_ai.response.model"];
2975
- const requestModel = attrs["gen_ai.request.model"];
2976
- if (responseModel) model = String(responseModel);
2977
- else if (requestModel) model = String(requestModel);
2978
- }
2979
- } else if (stepType === "tool_call") {
2980
- const [stepArgs, stepResult] = this.extractToolStep(attrs);
2981
- const toolName = String(attrs["gen_ai.tool.name"] ?? span.name);
2982
- builder.addToolCall(toolName, {
2983
- args: stepArgs,
2984
- result: stepResult,
2985
- metadata: this.spanMetadata(span)
2986
- });
3140
+ if (code === "ENOBUFS") {
3141
+ throw new OTLPConversionError(
3142
+ `attest-engine trace convert printed more than ${MAX_TRACE_OUTPUT_BYTES} bytes`
3143
+ );
2987
3144
  }
3145
+ throw proc.error;
2988
3146
  }
2989
- if (rootSpan) {
2990
- const startNs = rootSpan.startTime[0] * 1e9 + rootSpan.startTime[1];
2991
- const endNs = rootSpan.endTime[0] * 1e9 + rootSpan.endTime[1];
2992
- latencyMs = Math.floor((endNs - startNs) / 1e6);
3147
+ if (proc.status !== 0) {
3148
+ const message = proc.stderr.toString("utf8").trim();
3149
+ throw new OTLPConversionError(
3150
+ message || `attest-engine trace convert exited ${String(proc.status ?? proc.signal)}`
3151
+ );
2993
3152
  }
2994
- builder.setOutput({ message: outputMessage });
2995
- builder.setMetadata({ total_tokens: totalTokens, latency_ms: latencyMs, model });
2996
- return builder.build();
2997
- }
2998
- findRootSpan(spans) {
2999
- for (const span of spans) {
3000
- if (!span.parentSpanId) return span;
3153
+ try {
3154
+ return JSON.parse(proc.stdout.toString("utf8"));
3155
+ } catch (err) {
3156
+ throw new OTLPConversionError(
3157
+ `attest-engine trace convert printed output that is not JSON: ${String(err)}`
3158
+ );
3001
3159
  }
3002
- return spans[0];
3003
3160
  }
3004
- classifySpan(attrs, name) {
3005
- const op = String(attrs["gen_ai.operation.name"] ?? "");
3006
- if (op === "chat" || op === "completion" || op === "generate_content" || "gen_ai.completion" in attrs) {
3007
- return "llm_call";
3008
- }
3009
- if (op === "tool" || "gen_ai.tool.name" in attrs) {
3010
- return "tool_call";
3011
- }
3012
- const lower = name.toLowerCase();
3013
- if (lower.includes("completion") || lower.includes("chat")) return "llm_call";
3014
- if (lower.includes("tool")) return "tool_call";
3015
- return void 0;
3161
+ };
3162
+ function hrTimeToNanos(time, spanName) {
3163
+ if (!Number.isSafeInteger(time[0]) || !Number.isSafeInteger(time[1])) {
3164
+ throw new RangeError(`span ${JSON.stringify(spanName)} has a non-integer HrTime`);
3016
3165
  }
3017
- extractLlmStep(attrs) {
3018
- const args = {};
3019
- const result = {};
3020
- if ("gen_ai.request.model" in attrs) args.model = String(attrs["gen_ai.request.model"]);
3021
- if ("gen_ai.system" in attrs) args.system = String(attrs["gen_ai.system"]);
3022
- if ("gen_ai.prompt" in attrs) args.prompt = String(attrs["gen_ai.prompt"]);
3023
- if ("gen_ai.completion" in attrs) result.completion = String(attrs["gen_ai.completion"]);
3024
- if ("gen_ai.usage.input_tokens" in attrs) result.input_tokens = Number(attrs["gen_ai.usage.input_tokens"]);
3025
- if ("gen_ai.usage.output_tokens" in attrs) result.output_tokens = Number(attrs["gen_ai.usage.output_tokens"]);
3026
- if ("gen_ai.response.model" in attrs) result.model = String(attrs["gen_ai.response.model"]);
3027
- return [args, result];
3028
- }
3029
- extractToolStep(attrs) {
3030
- const args = {};
3031
- const result = {};
3032
- if ("gen_ai.tool.call.id" in attrs) args.call_id = String(attrs["gen_ai.tool.call.id"]);
3033
- if ("gen_ai.tool.parameters" in attrs) args.parameters = attrs["gen_ai.tool.parameters"];
3034
- if ("gen_ai.tool.output" in attrs) result.output = attrs["gen_ai.tool.output"];
3035
- return [args, result];
3036
- }
3037
- spanMetadata(span) {
3038
- const startNs = span.startTime[0] * 1e9 + span.startTime[1];
3039
- const endNs = span.endTime[0] * 1e9 + span.endTime[1];
3040
- return { duration_ms: Math.floor((endNs - startNs) / 1e6) };
3166
+ return (BigInt(time[0]) * 1000000000n + BigInt(time[1])).toString();
3167
+ }
3168
+ function anyValue(value) {
3169
+ if (value === null || value === void 0) return {};
3170
+ if (typeof value === "boolean") return { boolValue: value };
3171
+ if (typeof value === "string") return { stringValue: value };
3172
+ if (typeof value === "number") {
3173
+ if (!Number.isFinite(value)) {
3174
+ throw new RangeError(`OTel attribute value must be finite, got ${String(value)}`);
3175
+ }
3176
+ return Number.isSafeInteger(value) ? { intValue: String(value) } : { doubleValue: value };
3041
3177
  }
3042
- };
3178
+ return { arrayValue: { values: value.map((v) => anyValue(v)) } };
3179
+ }
3180
+ function spansToOtlp(spans) {
3181
+ const encoded = spans.map((span) => {
3182
+ const ctx = span.spanContext();
3183
+ const out = {
3184
+ traceId: ctx.traceId,
3185
+ spanId: ctx.spanId,
3186
+ name: span.name,
3187
+ startTimeUnixNano: hrTimeToNanos(span.startTime, span.name),
3188
+ endTimeUnixNano: hrTimeToNanos(span.endTime, span.name),
3189
+ attributes: Object.entries(span.attributes ?? {}).filter(([, v]) => v !== void 0).map(([key, v]) => ({ key, value: anyValue(v) }))
3190
+ };
3191
+ const parent = span.parentSpanContext?.spanId ?? span.parentSpanId;
3192
+ if (parent) out["parentSpanId"] = parent;
3193
+ if (span.status?.code === STATUS_CODE_ERROR) {
3194
+ out["status"] = { code: STATUS_CODE_ERROR, message: span.status.message ?? "" };
3195
+ }
3196
+ return out;
3197
+ });
3198
+ return { resourceSpans: [{ scopeSpans: [{ spans: encoded }] }] };
3199
+ }
3043
3200
 
3044
3201
  // src/adapters/langchain.ts
3045
3202
  var LangChainAdapter = class {
@@ -3162,6 +3319,7 @@ export {
3162
3319
  GeminiAdapter,
3163
3320
  LangChainAdapter,
3164
3321
  ManualAdapter,
3322
+ OTLPConversionError,
3165
3323
  OTelAdapter,
3166
3324
  OUTCOME_FAIL,
3167
3325
  OUTCOME_PASS,
@@ -3172,6 +3330,8 @@ export {
3172
3330
  ProtocolDesyncError,
3173
3331
  ProtocolDiagnosticBuffer,
3174
3332
  ProtocolError,
3333
+ REASON_BUDGET_EXHAUSTED,
3334
+ REASON_JUDGE_ABSTAINED,
3175
3335
  REASON_MISSING_EVIDENCE,
3176
3336
  REASON_PROVIDER_NOT_CONFIGURED,
3177
3337
  REASON_PROVIDER_UNAVAILABLE,
@@ -3201,6 +3361,7 @@ export {
3201
3361
  TraceTree,
3202
3362
  VERSION,
3203
3363
  activeBuilder,
3364
+ addUsage,
3204
3365
  agent,
3205
3366
  agentId,
3206
3367
  assertionId,
@@ -3213,11 +3374,13 @@ export {
3213
3374
  executePluginAssertion,
3214
3375
  extractId,
3215
3376
  extractResult,
3377
+ formatUsage,
3216
3378
  getSampleRate,
3217
3379
  isSimulationMode,
3218
3380
  layerForType,
3219
3381
  loadLabelsCSV,
3220
3382
  loadLabelsJSONL,
3383
+ parseUsage,
3221
3384
  previewLine,
3222
3385
  promptHash,
3223
3386
  renderAssertionFailure,
@@ -3226,7 +3389,10 @@ export {
3226
3389
  requireSimulationCapability,
3227
3390
  resetConfig,
3228
3391
  simulationEnvEnabled,
3392
+ sumUsage,
3229
3393
  tier,
3230
- traceId
3394
+ traceId,
3395
+ usageSummaryLine,
3396
+ zeroUsage
3231
3397
  };
3232
3398
  //# sourceMappingURL=index.js.map