@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.
@@ -54,6 +54,7 @@ __export(index_exports, {
54
54
  GeminiAdapter: () => GeminiAdapter,
55
55
  LangChainAdapter: () => LangChainAdapter,
56
56
  ManualAdapter: () => ManualAdapter,
57
+ OTLPConversionError: () => OTLPConversionError,
57
58
  OTelAdapter: () => OTelAdapter,
58
59
  OUTCOME_FAIL: () => OUTCOME_FAIL,
59
60
  OUTCOME_PASS: () => OUTCOME_PASS,
@@ -64,6 +65,8 @@ __export(index_exports, {
64
65
  ProtocolDesyncError: () => ProtocolDesyncError,
65
66
  ProtocolDiagnosticBuffer: () => ProtocolDiagnosticBuffer,
66
67
  ProtocolError: () => ProtocolError,
68
+ REASON_BUDGET_EXHAUSTED: () => REASON_BUDGET_EXHAUSTED,
69
+ REASON_JUDGE_ABSTAINED: () => REASON_JUDGE_ABSTAINED,
67
70
  REASON_MISSING_EVIDENCE: () => REASON_MISSING_EVIDENCE,
68
71
  REASON_PROVIDER_NOT_CONFIGURED: () => REASON_PROVIDER_NOT_CONFIGURED,
69
72
  REASON_PROVIDER_UNAVAILABLE: () => REASON_PROVIDER_UNAVAILABLE,
@@ -93,6 +96,7 @@ __export(index_exports, {
93
96
  TraceTree: () => TraceTree,
94
97
  VERSION: () => VERSION,
95
98
  activeBuilder: () => activeBuilder,
99
+ addUsage: () => addUsage,
96
100
  agent: () => agent,
97
101
  agentId: () => agentId,
98
102
  assertionId: () => assertionId,
@@ -105,11 +109,13 @@ __export(index_exports, {
105
109
  executePluginAssertion: () => executePluginAssertion,
106
110
  extractId: () => extractId,
107
111
  extractResult: () => extractResult,
112
+ formatUsage: () => formatUsage,
108
113
  getSampleRate: () => getSampleRate,
109
114
  isSimulationMode: () => isSimulationMode,
110
115
  layerForType: () => layerForType,
111
116
  loadLabelsCSV: () => loadLabelsCSV,
112
117
  loadLabelsJSONL: () => loadLabelsJSONL,
118
+ parseUsage: () => parseUsage,
113
119
  previewLine: () => previewLine,
114
120
  promptHash: () => promptHash,
115
121
  renderAssertionFailure: () => renderAssertionFailure,
@@ -118,8 +124,11 @@ __export(index_exports, {
118
124
  requireSimulationCapability: () => requireSimulationCapability,
119
125
  resetConfig: () => resetConfig,
120
126
  simulationEnvEnabled: () => simulationEnvEnabled,
127
+ sumUsage: () => sumUsage,
121
128
  tier: () => tier,
122
- traceId: () => traceId
129
+ traceId: () => traceId,
130
+ usageSummaryLine: () => usageSummaryLine,
131
+ zeroUsage: () => zeroUsage
123
132
  });
124
133
  module.exports = __toCommonJS(index_exports);
125
134
 
@@ -137,6 +146,8 @@ var REASON_PROVIDER_NOT_CONFIGURED = "provider_not_configured";
137
146
  var REASON_SIMULATION = "simulation";
138
147
  var REASON_MISSING_EVIDENCE = "missing_evidence";
139
148
  var REASON_SHORT_CIRCUITED = "short_circuited";
149
+ var REASON_BUDGET_EXHAUSTED = "budget_exhausted";
150
+ var REASON_JUDGE_ABSTAINED = "judge_abstained";
140
151
  var OUTCOME_PASS = "pass";
141
152
  var OUTCOME_FAIL = "fail";
142
153
  var OUTCOME_SKIP = "skip";
@@ -227,6 +238,12 @@ var ProtocolDesyncError = class extends Error {
227
238
  this.diagnostics = diagnostics;
228
239
  }
229
240
  };
241
+ var OTLPConversionError = class extends Error {
242
+ constructor(message) {
243
+ super(message);
244
+ this.name = "OTLPConversionError";
245
+ }
246
+ };
230
247
 
231
248
  // src/proto/diagnostics.ts
232
249
  var DEFAULT_BUFFER_SIZE = 32;
@@ -333,8 +350,8 @@ function extractId(response) {
333
350
  }
334
351
 
335
352
  // src/version.ts
336
- var VERSION = "1.0.0-rc.1";
337
- var ENGINE_VERSION = "1.0.0-rc.1";
353
+ var VERSION = "1.0.0-rc.2";
354
+ var ENGINE_VERSION = "1.0.0-rc.2";
338
355
 
339
356
  // src/engine-manager.ts
340
357
  var import_node_child_process = require("child_process");
@@ -727,6 +744,76 @@ function resetConfig() {
727
744
  _sampleRate = 0;
728
745
  }
729
746
 
747
+ // src/usage.ts
748
+ var INT_FIELDS = [
749
+ "model_calls",
750
+ "judge_calls",
751
+ "input_tokens",
752
+ "output_tokens",
753
+ "cached_tokens",
754
+ "unpriced_calls",
755
+ "budget_refused"
756
+ ];
757
+ var USD_FIELDS = ["cost_usd", "retry_cost_usd"];
758
+ function zeroUsage() {
759
+ return {
760
+ model_calls: 0,
761
+ judge_calls: 0,
762
+ input_tokens: 0,
763
+ output_tokens: 0,
764
+ cached_tokens: 0,
765
+ cost_usd: 0,
766
+ retry_cost_usd: 0,
767
+ unpriced_calls: 0,
768
+ budget_refused: 0
769
+ };
770
+ }
771
+ function addUsage(a, b) {
772
+ return {
773
+ model_calls: a.model_calls + b.model_calls,
774
+ judge_calls: a.judge_calls + b.judge_calls,
775
+ input_tokens: a.input_tokens + b.input_tokens,
776
+ output_tokens: a.output_tokens + b.output_tokens,
777
+ cached_tokens: a.cached_tokens + b.cached_tokens,
778
+ cost_usd: a.cost_usd + b.cost_usd,
779
+ retry_cost_usd: a.retry_cost_usd + b.retry_cost_usd,
780
+ unpriced_calls: a.unpriced_calls + b.unpriced_calls,
781
+ budget_refused: a.budget_refused + b.budget_refused
782
+ };
783
+ }
784
+ function sumUsage(usages) {
785
+ let total = zeroUsage();
786
+ for (const u of usages) total = addUsage(total, u);
787
+ return total;
788
+ }
789
+ function parseUsage(raw) {
790
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
791
+ throw new TypeError(`evaluate_batch result: usage must be an object, got ${JSON.stringify(raw)}`);
792
+ }
793
+ const obj = raw;
794
+ const field = (name, integer) => {
795
+ const v = obj[name];
796
+ if (typeof v !== "number" || !Number.isFinite(v) || v < 0 || integer && !Number.isInteger(v)) {
797
+ const kind = integer ? "a non-negative integer" : "a non-negative number";
798
+ throw new TypeError(`evaluate_batch result: usage.${name} must be ${kind}, got ${JSON.stringify(v)}`);
799
+ }
800
+ return v;
801
+ };
802
+ const ints = Object.fromEntries(INT_FIELDS.map((f) => [f, field(f, true)]));
803
+ const usd = Object.fromEntries(USD_FIELDS.map((f) => [f, field(f, false)]));
804
+ return { ...ints, ...usd };
805
+ }
806
+ function formatUsage(u) {
807
+ 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)}`;
808
+ if (u.budget_refused > 0) line += ` \xB7 ${u.budget_refused} calls refused by budget`;
809
+ if (u.unpriced_calls > 0) line += ` \xB7 ${u.unpriced_calls} unpriced calls not in $`;
810
+ return line;
811
+ }
812
+ function usageSummaryLine(u) {
813
+ if (u.model_calls === 0 && u.budget_refused === 0) return void 0;
814
+ return `attest usage: ${formatUsage(u)}`;
815
+ }
816
+
730
817
  // src/client.ts
731
818
  var DEFAULT_TIMEOUT_MS = 3e4;
732
819
  var DEFAULT_DIAGNOSTIC_BUFFER = 32;
@@ -980,14 +1067,15 @@ var AttestClient = class {
980
1067
  }
981
1068
  const params = {
982
1069
  trace,
983
- assertions: [...assertions]
1070
+ assertions: [...assertions],
1071
+ ...options?.budget !== void 0 ? { budget: options.budget } : {}
984
1072
  };
985
1073
  const raw = await this.sendRequest(
986
1074
  "evaluate_batch",
987
1075
  params,
988
1076
  options?.timeout
989
1077
  );
990
- return raw;
1078
+ return { ...raw, usage: parseUsage(raw["usage"]) };
991
1079
  }
992
1080
  async submitPluginResult(traceId2, pluginName, assertionId2, status, score, explanation, options) {
993
1081
  const result = {
@@ -1175,11 +1263,14 @@ var AgentResult = class {
1175
1263
  assertionResults;
1176
1264
  totalCost;
1177
1265
  totalDurationMs;
1178
- constructor(trace, assertionResults = [], totalCost = 0, totalDurationMs = 0) {
1266
+ /** Attest model usage of the evaluation that produced this result. */
1267
+ usage;
1268
+ constructor(trace, assertionResults = [], totalCost = 0, totalDurationMs = 0, usage = zeroUsage()) {
1179
1269
  this.trace = trace;
1180
1270
  this.assertionResults = assertionResults.map(normalizeAssertionResult);
1181
1271
  this.totalCost = totalCost;
1182
1272
  this.totalDurationMs = totalDurationMs;
1273
+ this.usage = usage;
1183
1274
  }
1184
1275
  /** True if every assertion has status "pass". */
1185
1276
  get passed() {
@@ -1358,7 +1449,8 @@ function appendJudgeMetadata(lines, meta) {
1358
1449
  if (meta.rubric_version) {
1359
1450
  rubric = `${rubric} @ ${meta.rubric_version}`;
1360
1451
  }
1361
- lines.push(` judge: ${meta.model} / ${rubric}`);
1452
+ const judge = meta.provider ? `${meta.provider}/${meta.model}` : meta.model;
1453
+ lines.push(` judge: ${judge} / ${rubric}`);
1362
1454
  }
1363
1455
  if (meta.prompt_hash) {
1364
1456
  lines.push(` prompt: #${meta.prompt_hash}`);
@@ -1882,6 +1974,86 @@ var ExpectChain = class {
1882
1974
  soft: opts?.soft ?? false
1883
1975
  });
1884
1976
  }
1977
+ // -- Layer 3: Behavior contracts (tool calls only, retries counted) --
1978
+ /**
1979
+ * Assert how many times a tool is called, per value of argument `per` when set.
1980
+ *
1981
+ * @example
1982
+ * ```ts
1983
+ * // fails with "issue_refund called 2× for order_id=O-1042 (max 1)"
1984
+ * expect(result).toolCardinality("issue_refund", { per: "order_id", atMost: 1 });
1985
+ * ```
1986
+ */
1987
+ toolCardinality(tool, opts) {
1988
+ if (opts.atMost === void 0 && opts.atLeast === void 0) {
1989
+ throw new Error("toolCardinality needs atMost, atLeast or both");
1990
+ }
1991
+ const spec = { check: "tool_cardinality", tool };
1992
+ if (opts.per !== void 0) spec.per = opts.per;
1993
+ if (opts.atLeast !== void 0) spec.min = opts.atLeast;
1994
+ if (opts.atMost !== void 0) spec.max = opts.atMost;
1995
+ spec.soft = opts.soft ?? false;
1996
+ return this.add(TYPE_TRACE, spec);
1997
+ }
1998
+ /**
1999
+ * Assert the tool-call trajectory matches `tools`: `strict` (same sequence,
2000
+ * the default), `unordered` (same calls, any order), `subset` (no call
2001
+ * outside `tools`) or `superset` (every call in `tools` made). Use
2002
+ * {@link trajectoryPartialOrder} for ordering constraints.
2003
+ */
2004
+ trajectory(tools, opts) {
2005
+ return this.add(TYPE_TRACE, {
2006
+ check: "trajectory",
2007
+ mode: opts?.mode ?? "strict",
2008
+ tools,
2009
+ soft: opts?.soft ?? false
2010
+ });
2011
+ }
2012
+ /**
2013
+ * Assert every tool in `tools` and `order` is called and each
2014
+ * `[before, after]` pair holds as in {@link toolCalledBefore}.
2015
+ */
2016
+ trajectoryPartialOrder(order, opts) {
2017
+ const spec = {
2018
+ check: "trajectory",
2019
+ mode: "partial_order",
2020
+ order: order.map(([before, after]) => [before, after])
2021
+ };
2022
+ if (opts?.tools !== void 0) spec.tools = opts.tools;
2023
+ spec.soft = opts?.soft ?? false;
2024
+ return this.add(TYPE_TRACE, spec);
2025
+ }
2026
+ /** Assert every call to `after` has an earlier call to `before`; holds when `after` is never called. */
2027
+ toolCalledBefore(before, after, opts) {
2028
+ return this.add(TYPE_TRACE, { check: "tool_order", before, after, soft: opts?.soft ?? false });
2029
+ }
2030
+ /** Assert no block of `period` (1–4) tool calls repeats `minRepeats` (default 2) times in a row; A→B→A→B fails period 2. */
2031
+ noToolCycles(period, opts) {
2032
+ return this.add(TYPE_TRACE, {
2033
+ check: "cycle",
2034
+ period,
2035
+ min_repeats: opts?.minRepeats ?? 2,
2036
+ soft: opts?.soft ?? false
2037
+ });
2038
+ }
2039
+ /** Assert at most `maxStreak` consecutive tool calls end in error (trace v2). */
2040
+ maxToolFailureStreak(maxStreak, opts) {
2041
+ return this.add(TYPE_TRACE, { check: "failure_streak", max: maxStreak, soft: opts?.soft ?? false });
2042
+ }
2043
+ /**
2044
+ * Assert every call to `tool` has argument `arg` (a dot path) satisfying
2045
+ * `op value`; holds when `tool` is never called.
2046
+ */
2047
+ toolArgSatisfies(tool, arg, op, value, opts) {
2048
+ return this.add(TYPE_TRACE, {
2049
+ check: "arg_predicate",
2050
+ tool,
2051
+ arg,
2052
+ op,
2053
+ value,
2054
+ soft: opts?.soft ?? false
2055
+ });
2056
+ }
1885
2057
  // -- Layer 4: Content --
1886
2058
  /**
1887
2059
  * Assert the output message contains the given string.
@@ -3052,119 +3224,113 @@ var OllamaAdapter = class {
3052
3224
  };
3053
3225
 
3054
3226
  // src/adapters/otel.ts
3227
+ var import_node_child_process2 = require("child_process");
3228
+ var CONVERT_TIMEOUT_MS = 6e4;
3229
+ var MAX_TRACE_OUTPUT_BYTES = 64 * 1024 * 1024;
3230
+ var STATUS_CODE_ERROR = 2;
3055
3231
  var OTelAdapter = class _OTelAdapter {
3056
- agentId;
3057
- constructor(agentId2) {
3058
- this.agentId = agentId2;
3059
- }
3060
- static fromSpans(spans, agentId2) {
3061
- const adapter = new _OTelAdapter(agentId2);
3062
- return adapter.buildTrace(spans);
3232
+ /**
3233
+ * Build a trace from finished OpenTelemetry spans of one trace.
3234
+ *
3235
+ * @param agentId Trace `agent_id`; defaults to the first turn's `gen_ai.agent.name`.
3236
+ * @throws OTLPConversionError when the engine rejects the spans.
3237
+ */
3238
+ static async fromSpans(spans, agentId2) {
3239
+ return _OTelAdapter.fromOtlp(spansToOtlp(spans), agentId2);
3063
3240
  }
3064
- buildTrace(spans) {
3065
- const sorted = [...spans].sort((a, b) => {
3066
- const aTime = a.startTime[0] * 1e9 + a.startTime[1];
3067
- const bTime = b.startTime[0] * 1e9 + b.startTime[1];
3068
- return aTime - bTime;
3241
+ /**
3242
+ * Convert an OTLP/JSON export (`resourceSpans` → `scopeSpans` → `spans`).
3243
+ *
3244
+ * @param agentId Trace `agent_id`; defaults to the first turn's `gen_ai.agent.name`.
3245
+ * @throws OTLPConversionError when the engine rejects the export (malformed JSON, invalid
3246
+ * ids or timestamps, more than one trace, non-numeric token counts, ...).
3247
+ * @throws RangeError when a parsed export holds a non-finite number.
3248
+ * @throws OTLPConversionError when the engine times out, overflows the output buffer or
3249
+ * prints no trace.
3250
+ * @throws Error from `spawnSync` when the engine cannot start.
3251
+ */
3252
+ static async fromOtlp(otlp, agentId2) {
3253
+ const payload = typeof otlp === "string" || otlp instanceof Uint8Array ? otlp : JSON.stringify(otlp, (_key, v) => {
3254
+ if (typeof v === "number" && !Number.isFinite(v)) {
3255
+ throw new RangeError(`OTLP export holds a non-finite number: ${String(v)}`);
3256
+ }
3257
+ return v;
3069
3258
  });
3070
- const builder = new TraceBuilder(this.agentId);
3071
- const rootSpan = this.findRootSpan(sorted);
3072
- if (rootSpan) {
3073
- const traceId2 = rootSpan.spanContext().traceId;
3074
- if (traceId2) {
3075
- builder.setTraceId(`otel_${traceId2.slice(0, 16)}`);
3259
+ const args = ["trace", "convert"];
3260
+ if (agentId2 !== void 0) args.push("--agent-id", agentId2);
3261
+ args.push("-");
3262
+ const proc = (0, import_node_child_process2.spawnSync)(await resolveEngine(), args, {
3263
+ input: payload,
3264
+ timeout: CONVERT_TIMEOUT_MS,
3265
+ maxBuffer: MAX_TRACE_OUTPUT_BYTES
3266
+ });
3267
+ if (proc.error) {
3268
+ const code = proc.error.code;
3269
+ if (code === "ETIMEDOUT") {
3270
+ throw new OTLPConversionError(
3271
+ `attest-engine trace convert timed out after ${CONVERT_TIMEOUT_MS / 1e3}s`
3272
+ );
3076
3273
  }
3077
- }
3078
- let outputMessage = "";
3079
- let totalTokens;
3080
- let latencyMs;
3081
- let model;
3082
- for (const span of sorted) {
3083
- const attrs = span.attributes ?? {};
3084
- const stepType = this.classifySpan(attrs, span.name);
3085
- if (stepType === "llm_call") {
3086
- const [stepArgs, stepResult] = this.extractLlmStep(attrs);
3087
- builder.addLlmCall(span.name, {
3088
- args: stepArgs,
3089
- result: stepResult,
3090
- metadata: this.spanMetadata(span)
3091
- });
3092
- const completion = attrs["gen_ai.completion"];
3093
- if (completion) outputMessage = String(completion);
3094
- const inputTokens = Number(attrs["gen_ai.usage.input_tokens"] ?? 0);
3095
- const outputTokens = Number(attrs["gen_ai.usage.output_tokens"] ?? 0);
3096
- const spanTokens = inputTokens + outputTokens;
3097
- if (spanTokens > 0) totalTokens = (totalTokens ?? 0) + spanTokens;
3098
- if (model === void 0) {
3099
- const responseModel = attrs["gen_ai.response.model"];
3100
- const requestModel = attrs["gen_ai.request.model"];
3101
- if (responseModel) model = String(responseModel);
3102
- else if (requestModel) model = String(requestModel);
3103
- }
3104
- } else if (stepType === "tool_call") {
3105
- const [stepArgs, stepResult] = this.extractToolStep(attrs);
3106
- const toolName = String(attrs["gen_ai.tool.name"] ?? span.name);
3107
- builder.addToolCall(toolName, {
3108
- args: stepArgs,
3109
- result: stepResult,
3110
- metadata: this.spanMetadata(span)
3111
- });
3274
+ if (code === "ENOBUFS") {
3275
+ throw new OTLPConversionError(
3276
+ `attest-engine trace convert printed more than ${MAX_TRACE_OUTPUT_BYTES} bytes`
3277
+ );
3112
3278
  }
3279
+ throw proc.error;
3113
3280
  }
3114
- if (rootSpan) {
3115
- const startNs = rootSpan.startTime[0] * 1e9 + rootSpan.startTime[1];
3116
- const endNs = rootSpan.endTime[0] * 1e9 + rootSpan.endTime[1];
3117
- latencyMs = Math.floor((endNs - startNs) / 1e6);
3281
+ if (proc.status !== 0) {
3282
+ const message = proc.stderr.toString("utf8").trim();
3283
+ throw new OTLPConversionError(
3284
+ message || `attest-engine trace convert exited ${String(proc.status ?? proc.signal)}`
3285
+ );
3118
3286
  }
3119
- builder.setOutput({ message: outputMessage });
3120
- builder.setMetadata({ total_tokens: totalTokens, latency_ms: latencyMs, model });
3121
- return builder.build();
3122
- }
3123
- findRootSpan(spans) {
3124
- for (const span of spans) {
3125
- if (!span.parentSpanId) return span;
3287
+ try {
3288
+ return JSON.parse(proc.stdout.toString("utf8"));
3289
+ } catch (err) {
3290
+ throw new OTLPConversionError(
3291
+ `attest-engine trace convert printed output that is not JSON: ${String(err)}`
3292
+ );
3126
3293
  }
3127
- return spans[0];
3128
3294
  }
3129
- classifySpan(attrs, name) {
3130
- const op = String(attrs["gen_ai.operation.name"] ?? "");
3131
- if (op === "chat" || op === "completion" || op === "generate_content" || "gen_ai.completion" in attrs) {
3132
- return "llm_call";
3133
- }
3134
- if (op === "tool" || "gen_ai.tool.name" in attrs) {
3135
- return "tool_call";
3136
- }
3137
- const lower = name.toLowerCase();
3138
- if (lower.includes("completion") || lower.includes("chat")) return "llm_call";
3139
- if (lower.includes("tool")) return "tool_call";
3140
- return void 0;
3295
+ };
3296
+ function hrTimeToNanos(time, spanName) {
3297
+ if (!Number.isSafeInteger(time[0]) || !Number.isSafeInteger(time[1])) {
3298
+ throw new RangeError(`span ${JSON.stringify(spanName)} has a non-integer HrTime`);
3141
3299
  }
3142
- extractLlmStep(attrs) {
3143
- const args = {};
3144
- const result = {};
3145
- if ("gen_ai.request.model" in attrs) args.model = String(attrs["gen_ai.request.model"]);
3146
- if ("gen_ai.system" in attrs) args.system = String(attrs["gen_ai.system"]);
3147
- if ("gen_ai.prompt" in attrs) args.prompt = String(attrs["gen_ai.prompt"]);
3148
- if ("gen_ai.completion" in attrs) result.completion = String(attrs["gen_ai.completion"]);
3149
- if ("gen_ai.usage.input_tokens" in attrs) result.input_tokens = Number(attrs["gen_ai.usage.input_tokens"]);
3150
- if ("gen_ai.usage.output_tokens" in attrs) result.output_tokens = Number(attrs["gen_ai.usage.output_tokens"]);
3151
- if ("gen_ai.response.model" in attrs) result.model = String(attrs["gen_ai.response.model"]);
3152
- return [args, result];
3153
- }
3154
- extractToolStep(attrs) {
3155
- const args = {};
3156
- const result = {};
3157
- if ("gen_ai.tool.call.id" in attrs) args.call_id = String(attrs["gen_ai.tool.call.id"]);
3158
- if ("gen_ai.tool.parameters" in attrs) args.parameters = attrs["gen_ai.tool.parameters"];
3159
- if ("gen_ai.tool.output" in attrs) result.output = attrs["gen_ai.tool.output"];
3160
- return [args, result];
3161
- }
3162
- spanMetadata(span) {
3163
- const startNs = span.startTime[0] * 1e9 + span.startTime[1];
3164
- const endNs = span.endTime[0] * 1e9 + span.endTime[1];
3165
- return { duration_ms: Math.floor((endNs - startNs) / 1e6) };
3300
+ return (BigInt(time[0]) * 1000000000n + BigInt(time[1])).toString();
3301
+ }
3302
+ function anyValue(value) {
3303
+ if (value === null || value === void 0) return {};
3304
+ if (typeof value === "boolean") return { boolValue: value };
3305
+ if (typeof value === "string") return { stringValue: value };
3306
+ if (typeof value === "number") {
3307
+ if (!Number.isFinite(value)) {
3308
+ throw new RangeError(`OTel attribute value must be finite, got ${String(value)}`);
3309
+ }
3310
+ return Number.isSafeInteger(value) ? { intValue: String(value) } : { doubleValue: value };
3166
3311
  }
3167
- };
3312
+ return { arrayValue: { values: value.map((v) => anyValue(v)) } };
3313
+ }
3314
+ function spansToOtlp(spans) {
3315
+ const encoded = spans.map((span) => {
3316
+ const ctx = span.spanContext();
3317
+ const out = {
3318
+ traceId: ctx.traceId,
3319
+ spanId: ctx.spanId,
3320
+ name: span.name,
3321
+ startTimeUnixNano: hrTimeToNanos(span.startTime, span.name),
3322
+ endTimeUnixNano: hrTimeToNanos(span.endTime, span.name),
3323
+ attributes: Object.entries(span.attributes ?? {}).filter(([, v]) => v !== void 0).map(([key, v]) => ({ key, value: anyValue(v) }))
3324
+ };
3325
+ const parent = span.parentSpanContext?.spanId ?? span.parentSpanId;
3326
+ if (parent) out["parentSpanId"] = parent;
3327
+ if (span.status?.code === STATUS_CODE_ERROR) {
3328
+ out["status"] = { code: STATUS_CODE_ERROR, message: span.status.message ?? "" };
3329
+ }
3330
+ return out;
3331
+ });
3332
+ return { resourceSpans: [{ scopeSpans: [{ spans: encoded }] }] };
3333
+ }
3168
3334
 
3169
3335
  // src/adapters/langchain.ts
3170
3336
  var LangChainAdapter = class {
@@ -3288,6 +3454,7 @@ var LangChainAdapter = class {
3288
3454
  GeminiAdapter,
3289
3455
  LangChainAdapter,
3290
3456
  ManualAdapter,
3457
+ OTLPConversionError,
3291
3458
  OTelAdapter,
3292
3459
  OUTCOME_FAIL,
3293
3460
  OUTCOME_PASS,
@@ -3298,6 +3465,8 @@ var LangChainAdapter = class {
3298
3465
  ProtocolDesyncError,
3299
3466
  ProtocolDiagnosticBuffer,
3300
3467
  ProtocolError,
3468
+ REASON_BUDGET_EXHAUSTED,
3469
+ REASON_JUDGE_ABSTAINED,
3301
3470
  REASON_MISSING_EVIDENCE,
3302
3471
  REASON_PROVIDER_NOT_CONFIGURED,
3303
3472
  REASON_PROVIDER_UNAVAILABLE,
@@ -3327,6 +3496,7 @@ var LangChainAdapter = class {
3327
3496
  TraceTree,
3328
3497
  VERSION,
3329
3498
  activeBuilder,
3499
+ addUsage,
3330
3500
  agent,
3331
3501
  agentId,
3332
3502
  assertionId,
@@ -3339,11 +3509,13 @@ var LangChainAdapter = class {
3339
3509
  executePluginAssertion,
3340
3510
  extractId,
3341
3511
  extractResult,
3512
+ formatUsage,
3342
3513
  getSampleRate,
3343
3514
  isSimulationMode,
3344
3515
  layerForType,
3345
3516
  loadLabelsCSV,
3346
3517
  loadLabelsJSONL,
3518
+ parseUsage,
3347
3519
  previewLine,
3348
3520
  promptHash,
3349
3521
  renderAssertionFailure,
@@ -3352,7 +3524,10 @@ var LangChainAdapter = class {
3352
3524
  requireSimulationCapability,
3353
3525
  resetConfig,
3354
3526
  simulationEnvEnabled,
3527
+ sumUsage,
3355
3528
  tier,
3356
- traceId
3529
+ traceId,
3530
+ usageSummaryLine,
3531
+ zeroUsage
3357
3532
  });
3358
3533
  //# sourceMappingURL=index.cjs.map