@tangle-network/traces 0.23.0 → 1.0.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 (74) hide show
  1. package/README.md +61 -1078
  2. package/dist/analyst-model-call-JVUOYZ5V.js +20 -0
  3. package/dist/ask-XLS3VANY.js +39 -0
  4. package/dist/check-YF6ZHPGG.js +322 -0
  5. package/dist/chunk-3I4PJ5Q7.js +25 -0
  6. package/dist/chunk-3T62R7ON.js +5695 -0
  7. package/dist/chunk-4VKPN432.js +138 -0
  8. package/dist/chunk-6CLC7DKP.js +492 -0
  9. package/dist/chunk-7CBV5RJ2.js +535 -0
  10. package/dist/chunk-B62DOFGI.js +273 -0
  11. package/dist/chunk-CL3OQOWX.js +407 -0
  12. package/dist/chunk-DLGAKCQJ.js +782 -0
  13. package/dist/chunk-DU534HAX.js +263 -0
  14. package/dist/chunk-GLUDGUGJ.js +232 -0
  15. package/dist/chunk-J2PM5SVN.js +503 -0
  16. package/dist/chunk-M3W6TZHQ.js +381 -0
  17. package/dist/chunk-NH7HCDLB.js +759 -0
  18. package/dist/chunk-NZ5Y7UEL.js +1217 -0
  19. package/dist/chunk-O52XK7OB.js +19 -0
  20. package/dist/chunk-PYH3PL3N.js +62 -0
  21. package/dist/chunk-RZL5GAWH.js +60 -0
  22. package/dist/chunk-TR6O4S4T.js +208 -0
  23. package/dist/chunk-TW43MR5D.js +259 -0
  24. package/dist/chunk-TYLFUX42.js +228 -0
  25. package/dist/chunk-UOLRCQHC.js +120 -0
  26. package/dist/chunk-UVE5ANVV.js +3921 -0
  27. package/dist/chunk-V2OCGX57.js +53 -0
  28. package/dist/cli.js +2086 -2050
  29. package/dist/diff-ZZ6KYBPP.js +16 -0
  30. package/dist/improvement-OJQ6365E.js +25 -0
  31. package/dist/index.d.ts +1235 -4357
  32. package/dist/index.js +53 -1204
  33. package/dist/live-Y64AJ6MA.js +14 -0
  34. package/dist/mcp-ZPGTJQYQ.js +85 -0
  35. package/dist/report-JAMXRTCW.js +338 -0
  36. package/dist/run-watch-PLWTIRSV.js +649 -0
  37. package/dist/supervisor-report-RMPLLBGZ.js +29 -0
  38. package/dist/supervisor-run-context-WO6VOTGV.js +30 -0
  39. package/dist/upload-R6NMKSGQ.js +20 -0
  40. package/dist/verify-safe-C5ZXZOZ2.js +8 -0
  41. package/docs/analyze.md +76 -0
  42. package/docs/ask.md +88 -0
  43. package/docs/bundle.md +40 -0
  44. package/docs/check.md +77 -0
  45. package/docs/convert.md +44 -0
  46. package/docs/develop.md +32 -0
  47. package/docs/diff.md +38 -0
  48. package/docs/facts.md +114 -0
  49. package/docs/mcp.md +36 -0
  50. package/docs/migrate-1.0.md +33 -0
  51. package/docs/parsers.md +66 -0
  52. package/docs/release-1.0.0.md +147 -0
  53. package/docs/sdk.md +77 -0
  54. package/docs/select.md +70 -0
  55. package/docs/skills.md +46 -0
  56. package/docs/upload.md +47 -0
  57. package/docs/watch-jsonl.v1.example.jsonl +5 -0
  58. package/docs/watch.md +66 -0
  59. package/examples/README.md +9 -0
  60. package/examples/custom-analyst.ts +65 -74
  61. package/install.sh +22 -2
  62. package/package.json +8 -7
  63. package/skills/inspect-agent-traces/SKILL.md +47 -110
  64. package/dist/chunk-COSWFESO.js +0 -20979
  65. package/docs/replay-verify.md +0 -123
  66. package/docs/stream-findings.svg +0 -39
  67. package/docs/trace-analysts.md +0 -643
  68. package/examples/custom-backend.ts +0 -21
  69. package/examples/external-engines.ts +0 -62
  70. package/examples/improvement-config.mjs +0 -63
  71. package/examples/observe-and-alert.ts +0 -19
  72. package/examples/register-harness.ts +0 -26
  73. package/skills/build-trace-analyst/SKILL.md +0 -76
  74. package/skills/build-trace-analyst/agents/openai.yaml +0 -4
@@ -0,0 +1,138 @@
1
+ // src/verify-safe.ts
2
+ import { lstat, readdir, readFile } from "fs/promises";
3
+ import { basename, extname, join, relative } from "path";
4
+ import {
5
+ assessShareSafety,
6
+ classifyKey,
7
+ combineVerdicts
8
+ } from "@tangle-network/agent-eval/traces";
9
+ var PROFILE = "share";
10
+ var JSON_LINES = /* @__PURE__ */ new Set([".jsonl", ".ndjson"]);
11
+ async function verifySafe(target, options = {}) {
12
+ const assess = (value) => assessShareSafety(numbersAreNotContent(value), { profile: PROFILE, knownSecrets: options.knownSecrets });
13
+ const verdicts = [];
14
+ const unreadable = [];
15
+ const files = await listFiles(target, unreadable);
16
+ for (const file of files) {
17
+ const label = relative(target, file) || basename(file);
18
+ let bytes;
19
+ try {
20
+ bytes = await readFile(file);
21
+ } catch (error) {
22
+ unreadable.push(`${label}: ${error instanceof Error ? error.message : String(error)}`);
23
+ continue;
24
+ }
25
+ const text = decodeUtf8(bytes);
26
+ if (text === void 0) {
27
+ unreadable.push(`${label}: not UTF-8 text`);
28
+ continue;
29
+ }
30
+ const extension = extname(file).toLowerCase();
31
+ if (JSON_LINES.has(extension)) {
32
+ text.split("\n").forEach((line, index) => {
33
+ if (!line.trim()) return;
34
+ const parsed = parseJson(line);
35
+ if (parsed === void 0) unreadable.push(`${label}:${index + 1}: not JSON`);
36
+ else verdicts.push(located(assess(parsed.value), `${label}:${index + 1}`));
37
+ });
38
+ } else if (extension === ".json") {
39
+ const parsed = parseJson(text);
40
+ if (parsed === void 0) unreadable.push(`${label}: not JSON`);
41
+ else verdicts.push(located(assess(parsed.value), label));
42
+ } else {
43
+ verdicts.push(located(assess(text), label));
44
+ }
45
+ }
46
+ if (files.length === 0 && unreadable.length === 0) unreadable.push(`${target}: no files to check`);
47
+ return { ...combineVerdicts(PROFILE, verdicts, unreadable), target, files: files.length };
48
+ }
49
+ function onlyNumbers(value) {
50
+ if (value === null || typeof value === "number" || typeof value === "boolean") return true;
51
+ if (Array.isArray(value)) return value.every(onlyNumbers);
52
+ if (typeof value === "object") return Object.values(value).every(onlyNumbers);
53
+ return false;
54
+ }
55
+ function noFlaggedKeys(value) {
56
+ if (Array.isArray(value)) return value.every(noFlaggedKeys);
57
+ if (value === null || typeof value !== "object") return true;
58
+ return Object.entries(value).every(([key, item]) => {
59
+ const category = classifyKey(key);
60
+ return (category === void 0 || category === "raw-content") && noFlaggedKeys(item);
61
+ });
62
+ }
63
+ function numbersAreNotContent(value) {
64
+ if (Array.isArray(value)) return value.map(numbersAreNotContent);
65
+ if (value === null || typeof value !== "object") return value;
66
+ const out = {};
67
+ for (const [key, item] of Object.entries(value)) {
68
+ out[key] = classifyKey(key) === "raw-content" && item !== null && typeof item === "object" && onlyNumbers(item) && noFlaggedKeys(item) ? 0 : numbersAreNotContent(item);
69
+ }
70
+ return out;
71
+ }
72
+ async function listFiles(target, unreadable) {
73
+ let stat;
74
+ try {
75
+ stat = await lstat(target);
76
+ } catch (error) {
77
+ unreadable.push(`${target}: ${error instanceof Error ? error.message : String(error)}`);
78
+ return [];
79
+ }
80
+ if (stat.isFile()) return [target];
81
+ if (!stat.isDirectory()) {
82
+ unreadable.push(`${target}: not a regular file or directory`);
83
+ return [];
84
+ }
85
+ const out = [];
86
+ const walk = async (dir) => {
87
+ for (const entry of (await readdir(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
88
+ const path = join(dir, entry.name);
89
+ if (entry.isDirectory()) await walk(path);
90
+ else if (entry.isFile()) out.push(path);
91
+ else unreadable.push(`${relative(target, path)}: not a regular file`);
92
+ }
93
+ };
94
+ await walk(target);
95
+ return out;
96
+ }
97
+ function decodeUtf8(bytes) {
98
+ if (bytes.includes(0)) return void 0;
99
+ try {
100
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
101
+ } catch {
102
+ return void 0;
103
+ }
104
+ }
105
+ function parseJson(text) {
106
+ try {
107
+ return { value: JSON.parse(text) };
108
+ } catch {
109
+ return void 0;
110
+ }
111
+ }
112
+ function located(verdict, where) {
113
+ return {
114
+ ...verdict,
115
+ findings: verdict.findings.map((finding) => ({
116
+ ...finding,
117
+ paths: finding.paths.map((path) => `${where}#${path}`)
118
+ })),
119
+ unreadable: verdict.unreadable.map((entry) => `${where}#${entry}`)
120
+ };
121
+ }
122
+ function formatVerdict(result) {
123
+ const lines = [`${result.status} ${result.target} (${result.files} file(s), share profile)`];
124
+ for (const finding of result.findings) {
125
+ lines.push(
126
+ ` ${finding.severity.padEnd(7)} ${finding.category}/${finding.detector} x${finding.count} ${finding.paths.join(", ")}`
127
+ );
128
+ }
129
+ for (const entry of result.unreadable) lines.push(` unread ${entry}`);
130
+ if (result.status === "UNSAFE") lines.push("Refuse to share: remove what each finding names, then run traces check <path> --share-safe again.");
131
+ if (result.status === "UNKNOWN") lines.push("Refuse to share: part of the input could not be read, so its safety is unknown.");
132
+ return lines.join("\n");
133
+ }
134
+
135
+ export {
136
+ verifySafe,
137
+ formatVerdict
138
+ };
@@ -0,0 +1,492 @@
1
+ import {
2
+ ATTR,
3
+ capText,
4
+ stampEnvironmentAttrs
5
+ } from "./chunk-TR6O4S4T.js";
6
+ import {
7
+ appendAll,
8
+ otlpRowToSpan,
9
+ readJsonl,
10
+ span,
11
+ validateOtlpSpans
12
+ } from "./chunk-NZ5Y7UEL.js";
13
+ import {
14
+ redactSpans
15
+ } from "./chunk-RZL5GAWH.js";
16
+ import {
17
+ toolIoAttributes
18
+ } from "./chunk-DU534HAX.js";
19
+
20
+ // src/file-export.ts
21
+ import { createHash } from "crypto";
22
+ import { readFile } from "fs/promises";
23
+ function isObject(value) {
24
+ return value != null && typeof value === "object" && !Array.isArray(value);
25
+ }
26
+ function stringValue(value) {
27
+ return typeof value === "string" && value.length > 0 ? value : void 0;
28
+ }
29
+ function objectValue(value) {
30
+ return isObject(value) ? value : void 0;
31
+ }
32
+ function stableJson(value) {
33
+ return JSON.stringify(value, (_key, v) => {
34
+ if (!isObject(v)) return v;
35
+ return Object.keys(v).sort().reduce((acc, key) => {
36
+ acc[key] = v[key];
37
+ return acc;
38
+ }, {});
39
+ });
40
+ }
41
+ function hashId(value, chars = 16) {
42
+ return createHash("sha256").update(stableJson(value)).digest("hex").slice(0, chars);
43
+ }
44
+ function isoTime(value) {
45
+ if (typeof value === "string") {
46
+ const trimmed = value.trim();
47
+ const normalized = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(trimmed) ? `${trimmed.replace(" ", "T")}Z` : trimmed;
48
+ const ms = Date.parse(normalized);
49
+ return Number.isFinite(ms) ? new Date(ms).toISOString() : void 0;
50
+ }
51
+ if (typeof value === "number" && Number.isFinite(value)) {
52
+ const ms = value < 1e10 ? value * 1e3 : value;
53
+ return new Date(ms).toISOString();
54
+ }
55
+ return void 0;
56
+ }
57
+ function firstTime(obj, keys) {
58
+ for (const key of keys) {
59
+ const value = isoTime(obj[key]);
60
+ if (value) return value;
61
+ }
62
+ return void 0;
63
+ }
64
+ function firstTimeDeep(value, keys, depth = 5) {
65
+ if (depth < 0) return void 0;
66
+ if (isObject(value)) {
67
+ const direct = firstTime(value, keys);
68
+ if (direct) return direct;
69
+ for (const child of Object.values(value)) {
70
+ const found = firstTimeDeep(child, keys, depth - 1);
71
+ if (found) return found;
72
+ }
73
+ } else if (Array.isArray(value)) {
74
+ for (const child of value) {
75
+ const found = firstTimeDeep(child, keys, depth - 1);
76
+ if (found) return found;
77
+ }
78
+ }
79
+ return void 0;
80
+ }
81
+ function copyDefined(target, entries) {
82
+ for (const [key, value] of Object.entries(entries)) {
83
+ if (value != null && value !== "") target[key] = value;
84
+ }
85
+ }
86
+ function parseJsonRows(text) {
87
+ const trimmed = text.trim();
88
+ if (!trimmed) throw new Error("input file is empty");
89
+ if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
90
+ try {
91
+ const parsed = JSON.parse(trimmed);
92
+ if (Array.isArray(parsed)) return { rows: parsed };
93
+ if (isObject(parsed)) {
94
+ const events = parsed.events;
95
+ if (Array.isArray(events)) return { rows: events, wrapper: parsed };
96
+ return { rows: [parsed] };
97
+ }
98
+ throw new Error("input JSON must be an object, array, or JSONL rows");
99
+ } catch (error) {
100
+ const hasMultipleRows = /\r?\n/.test(trimmed);
101
+ if (!hasMultipleRows) throw error;
102
+ }
103
+ }
104
+ return {
105
+ rows: trimmed.split(/\r?\n/).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line))
106
+ };
107
+ }
108
+ function isOpenInferenceRow(row) {
109
+ if (!isObject(row)) return false;
110
+ return typeof row.trace_id === "string" && typeof row.span_id === "string" && (row.parent_span_id == null || typeof row.parent_span_id === "string") && typeof row.name === "string" && typeof row.start_time === "string" && typeof row.end_time === "string" && isObject(row.status) && isObject(row.attributes) && (row.resource === void 0 || isObject(row.resource)) && (row.scope === void 0 || isObject(row.scope)) && (row.links === void 0 || Array.isArray(row.links));
111
+ }
112
+ function eventType(row) {
113
+ return stringValue(row.type) ?? stringValue(row.event) ?? stringValue(row.name) ?? stringValue(row.kind);
114
+ }
115
+ function looksLikeSandboxEvent(row) {
116
+ if (!isObject(row)) return false;
117
+ const type = eventType(row)?.toLowerCase();
118
+ return type === "start" || type === "raw" || type === "result" || type === "done" || type === "error";
119
+ }
120
+ function detectEvidenceFormat(rows) {
121
+ if (rows.length === 0 || rows.every(isOpenInferenceRow)) return void 0;
122
+ if (rows.some(looksLikeSandboxEvent)) return "sandbox-events";
123
+ return void 0;
124
+ }
125
+ function detectFormat(rows, requested) {
126
+ if (requested !== "auto") return requested;
127
+ if (rows.length === 0) throw new Error("input contains no rows");
128
+ if (rows.every(isOpenInferenceRow)) return "openinference";
129
+ if (rows.some(looksLikeSandboxEvent)) return "sandbox-events";
130
+ throw new Error("could not detect input format: expected OpenInference span rows or sandbox events");
131
+ }
132
+ function requireOpenInferenceRows(rows) {
133
+ if (!rows.every(isOpenInferenceRow)) throw new Error("openinference input must contain only complete OpenInference span rows");
134
+ return rows;
135
+ }
136
+ function requireObjectRows(rows) {
137
+ if (!rows.every(isObject)) throw new Error("sandbox-events input must contain only JSON object events");
138
+ return rows;
139
+ }
140
+ function findStringKey(value, keys, depth = 4) {
141
+ if (depth < 0) return void 0;
142
+ if (isObject(value)) {
143
+ for (const key of keys) {
144
+ const found = stringValue(value[key]);
145
+ if (found) return found;
146
+ }
147
+ for (const child of Object.values(value)) {
148
+ const found = findStringKey(child, keys, depth - 1);
149
+ if (found) return found;
150
+ }
151
+ } else if (Array.isArray(value)) {
152
+ for (const child of value) {
153
+ const found = findStringKey(child, keys, depth - 1);
154
+ if (found) return found;
155
+ }
156
+ }
157
+ return void 0;
158
+ }
159
+ function eventValue(row, keys) {
160
+ const scopes = [row, objectValue(row.data), objectValue(row.payload)];
161
+ for (const scope of scopes) {
162
+ if (!scope) continue;
163
+ for (const key of keys) {
164
+ if (scope[key] !== void 0) return scope[key];
165
+ }
166
+ }
167
+ return void 0;
168
+ }
169
+ function eventTimestamp(row) {
170
+ return firstTimeDeep(row, [
171
+ "timestamp",
172
+ "start_time",
173
+ "startTime",
174
+ "startedAt",
175
+ "createdAt",
176
+ "created_at",
177
+ "created",
178
+ "time"
179
+ ]);
180
+ }
181
+ function eventEndTimestamp(row, fallback) {
182
+ return firstTimeDeep(row, [
183
+ "end_time",
184
+ "endTime",
185
+ "completedAt",
186
+ "completed_at",
187
+ "finishedAt",
188
+ "updatedAt",
189
+ "updated_at",
190
+ "updated"
191
+ ]) ?? fallback;
192
+ }
193
+ function eventStatus(row, type) {
194
+ const lowered = type.toLowerCase();
195
+ const errorMessage = stringValue(row.error) ?? findStringKey(row, ["error", "errorMessage", "error_message"], 3);
196
+ if (lowered.includes("error") || errorMessage) {
197
+ const message = errorMessage ?? stringValue(row.message);
198
+ return { status: "ERROR", ...message ? { message: capText(message) } : {} };
199
+ }
200
+ return { status: "OK" };
201
+ }
202
+ function eventKind(row, type) {
203
+ const lowered = type.toLowerCase();
204
+ if (lowered === "result") return { kind: "CHAIN" };
205
+ const tool = findStringKey(row, ["tool", "toolName", "tool_name"], 4);
206
+ if (tool || lowered.includes("tool") || stableJson(row).includes("tool-invocation")) return { kind: "TOOL", tool };
207
+ if (lowered.includes("llm") || lowered === "token") return { kind: "LLM" };
208
+ return { kind: "CHAIN" };
209
+ }
210
+ function sandboxEventsEnvironment(context, rows) {
211
+ const ready = rows.find((row) => eventType(row) === "runtime.ready");
212
+ const readyData = ready && isObject(ready.data) ? ready.data : void 0;
213
+ const pick = (keys) => findStringKey(context, keys, 2) ?? (readyData ? findStringKey(readyData, keys, 2) : void 0);
214
+ const image = pick(["image"]);
215
+ const imageDigest = pick(["imageDigest", "image_digest"]);
216
+ const sandboxId = pick(["sandboxId", "sandbox_id"]);
217
+ const cwd = pick(["cwd", "workspaceRoot", "workspace_root"]);
218
+ if (!image && !imageDigest && !sandboxId && !cwd) return null;
219
+ return {
220
+ ...image ? { image } : {},
221
+ ...imageDigest ? { imageDigest } : {},
222
+ ...sandboxId ? { sandboxId } : {},
223
+ cwd: cwd ?? null
224
+ };
225
+ }
226
+ function llmEventUsage(row) {
227
+ const num = (keys) => {
228
+ for (const key of keys) {
229
+ const direct = numberValue(row[key]);
230
+ if (direct !== void 0) return direct;
231
+ for (const nest of ["data", "usage", "tokenUsage"]) {
232
+ const container = row[nest];
233
+ if (!isObject(container)) continue;
234
+ const value = numberValue(container[key]);
235
+ if (value !== void 0) return value;
236
+ for (const inner of ["usage", "tokenUsage"]) {
237
+ const deep = container[inner];
238
+ if (isObject(deep)) {
239
+ const deepValue = numberValue(deep[key]);
240
+ if (deepValue !== void 0) return deepValue;
241
+ }
242
+ }
243
+ }
244
+ }
245
+ return void 0;
246
+ };
247
+ const model = findStringKey(row, ["model"], 3);
248
+ const inputTokens = num(["prompt_tokens", "input_tokens", "inputTokens"]);
249
+ const outputTokens = num(["completion_tokens", "output_tokens", "outputTokens"]);
250
+ const costUsd = num(["totalCostUsd", "total_cost_usd", "costUsd", "cost_usd"]);
251
+ return {
252
+ ...model ? { model } : {},
253
+ ...inputTokens !== void 0 ? { inputTokens } : {},
254
+ ...outputTokens !== void 0 ? { outputTokens } : {},
255
+ ...costUsd !== void 0 ? { costUsd } : {}
256
+ };
257
+ }
258
+ function toolPart(row) {
259
+ const data = objectValue(row.data);
260
+ const part = objectValue(data?.part) ?? objectValue(row.part) ?? objectValue(objectValue(data?.event)?.part);
261
+ return part && part.type === "tool" && stringValue(part.id) ? part : void 0;
262
+ }
263
+ function foldToolPart(into, part) {
264
+ const state = objectValue(part.state);
265
+ const time = objectValue(state?.time);
266
+ into.tool = stringValue(part.tool) ?? into.tool;
267
+ const input = state?.input;
268
+ if (input !== void 0 && !(isObject(input) && Object.keys(input).length === 0)) into.input = input;
269
+ if (state?.output !== void 0) into.output = state.output;
270
+ into.error = stringValue(state?.error) ?? into.error;
271
+ const start = isoTime(time?.start);
272
+ if (start && (into.start === void 0 || start < into.start)) into.start = start;
273
+ into.end = isoTime(time?.end) ?? into.end;
274
+ }
275
+ function sandboxEventsToSpans(rows, wrapper) {
276
+ const context = wrapper ?? {};
277
+ const sessionId = findStringKey(context, ["session_id", "sessionId"], 3) ?? findStringKey(rows, ["session_id", "sessionId"], 4);
278
+ const traceId = findStringKey(context, ["trace_id", "traceId"], 3) ?? findStringKey(rows, ["trace_id", "traceId"], 4) ?? sessionId ?? findStringKey(context, ["run_id", "runId"], 3) ?? findStringKey(rows, ["run_id", "runId"], 4) ?? `sandbox:${hashId(rows, 32)}`;
279
+ const service = findStringKey(context, ["service", "harness", "source"], 2) ?? "sandbox-opencode";
280
+ const times = rows.flatMap((row) => {
281
+ const time = eventTimestamp(row);
282
+ return time ? [time] : [];
283
+ }).sort();
284
+ const rootId = `events:${hashId({ traceId, rows: rows.length })}`;
285
+ const hasError = rows.some((row) => eventStatus(row, eventType(row) ?? "event").status === "ERROR");
286
+ const rootStart = times[0] ?? (/* @__PURE__ */ new Date(0)).toISOString();
287
+ const rootEnd = times[times.length - 1] ?? rootStart;
288
+ const spans = [
289
+ span({
290
+ traceId,
291
+ spanId: rootId,
292
+ parentSpanId: null,
293
+ name: "sandbox.events",
294
+ kind: "AGENT",
295
+ startTime: rootStart,
296
+ endTime: rootEnd,
297
+ status: hasError ? "ERROR" : "OK",
298
+ service,
299
+ agent: service,
300
+ extra: {
301
+ "traces.source_format": "sandbox-events",
302
+ "traces.event_count": rows.length
303
+ }
304
+ })
305
+ ];
306
+ const lastEventOfPart = /* @__PURE__ */ new Map();
307
+ const firstTimeOfPart = /* @__PURE__ */ new Map();
308
+ const errorOfPart = /* @__PURE__ */ new Map();
309
+ const stateOfPart = /* @__PURE__ */ new Map();
310
+ rows.forEach((row, index) => {
311
+ const toolPartOfRow = toolPart(row);
312
+ if (toolPartOfRow === void 0) return;
313
+ const part = stringValue(toolPartOfRow.id);
314
+ lastEventOfPart.set(part, index);
315
+ const state = stateOfPart.get(part) ?? {};
316
+ foldToolPart(state, toolPartOfRow);
317
+ stateOfPart.set(part, state);
318
+ const time = eventTimestamp(row);
319
+ const first = firstTimeOfPart.get(part);
320
+ if (time && (first === void 0 || time < first)) firstTimeOfPart.set(part, time);
321
+ const partStatus = stringValue(objectValue(toolPartOfRow.state)?.status);
322
+ const status = partStatus === "error" || partStatus === "failed" ? { status: "ERROR", ...state.error ? { message: capText(state.error) } : {} } : eventStatus(row, eventType(row) ?? "event");
323
+ if (status.status === "ERROR") errorOfPart.set(part, status);
324
+ });
325
+ const mergedToolEvents = rows.filter((row, index) => {
326
+ const part = stringValue(toolPart(row)?.id);
327
+ return part !== void 0 && lastEventOfPart.get(part) !== index;
328
+ }).length;
329
+ if (mergedToolEvents > 0) spans[0].attributes["traces.event.tool_part_updates_merged"] = mergedToolEvents;
330
+ let previousTime = rootStart;
331
+ rows.forEach((row, index) => {
332
+ const part = stringValue(toolPart(row)?.id);
333
+ if (part !== void 0 && lastEventOfPart.get(part) !== index) return;
334
+ const partState = part !== void 0 ? stateOfPart.get(part) : void 0;
335
+ const type = eventType(row) ?? "event";
336
+ const time = partState?.start ?? (part !== void 0 ? firstTimeOfPart.get(part) : void 0) ?? eventTimestamp(row) ?? previousTime;
337
+ previousTime = time;
338
+ const { status, message } = (part !== void 0 ? errorOfPart.get(part) : void 0) ?? eventStatus(row, type);
339
+ const { kind, tool } = partState ? { kind: "TOOL", tool: partState.tool } : eventKind(row, type);
340
+ const name = kind === "TOOL" && tool ? `tool.${tool}` : `event.${type}`;
341
+ const toolInput = kind === "TOOL" ? partState?.input ?? eventValue(row, ["toolInput", "tool_input", "input", "args", "arguments", "full_command"]) : void 0;
342
+ const toolOutput = kind === "TOOL" ? partState?.output ?? partState?.error ?? eventValue(row, ["toolOutput", "tool_output", "output", "result"]) : void 0;
343
+ const invocations = type === "result" ? objectValue(row.data)?.toolInvocations : void 0;
344
+ const extra = {
345
+ "traces.source_format": "sandbox-events",
346
+ "traces.event.type": type,
347
+ "traces.event.index": index,
348
+ ...kind === "TOOL" ? toolIoAttributes({ input: toolInput, output: toolOutput, argsCaptured: toolInput !== void 0 }) : {},
349
+ // The Sandbox's own count of the run's tool calls, beside the tool spans counted from their events.
350
+ ...Array.isArray(invocations) ? { "traces.event.tool_invocations": invocations.length } : {}
351
+ };
352
+ copyDefined(extra, {
353
+ "traces.event.part_id": part,
354
+ "traces.event.id": stringValue(row.id),
355
+ "traces.event.raw_type": stringValue(row.type),
356
+ "traces.event.raw_event": stringValue(row.event)
357
+ });
358
+ spans.push(span({
359
+ traceId,
360
+ spanId: `event:${index}:${hashId(row)}`,
361
+ parentSpanId: rootId,
362
+ name,
363
+ kind,
364
+ startTime: time,
365
+ endTime: partState?.end !== void 0 && partState.end >= time ? partState.end : eventEndTimestamp(row, time),
366
+ status,
367
+ statusMessage: message,
368
+ service,
369
+ agent: service,
370
+ tool: kind === "TOOL" ? tool ?? type : null,
371
+ step: index,
372
+ content: capText(stableJson(row)),
373
+ extra,
374
+ ...kind === "LLM" ? llmEventUsage(row) : {}
375
+ }));
376
+ });
377
+ stampEnvironmentAttrs(spans, sandboxEventsEnvironment(context, rows) ?? void 0);
378
+ if (!sessionId) return spans;
379
+ return spans.map((item) => ({
380
+ ...item,
381
+ attributes: { ...item.attributes, [ATTR.SESSION_ID]: sessionId }
382
+ }));
383
+ }
384
+ function numberValue(value) {
385
+ if (typeof value === "number" && Number.isFinite(value)) return value;
386
+ if (typeof value === "string" && value.trim().length > 0) {
387
+ const parsed = Number(value);
388
+ return Number.isFinite(parsed) ? parsed : void 0;
389
+ }
390
+ return void 0;
391
+ }
392
+ var OPEN_INFERENCE_STATUS_CODES = /* @__PURE__ */ new Set([
393
+ "OK",
394
+ "ERROR",
395
+ "UNSET",
396
+ "STATUS_CODE_OK",
397
+ "STATUS_CODE_ERROR",
398
+ "STATUS_CODE_UNSET"
399
+ ]);
400
+ function openInferenceToSpans(rows) {
401
+ return rows.map((row) => {
402
+ const status = objectValue(row.status);
403
+ const code = status?.code;
404
+ if (code != null && !(typeof code === "string" && OPEN_INFERENCE_STATUS_CODES.has(code))) {
405
+ throw new TypeError("OpenInference status.code must be OK, ERROR, UNSET, or its STATUS_CODE_* form");
406
+ }
407
+ const traceId = stringValue(row.trace_id);
408
+ const converted = otlpRowToSpan(row, traceId ?? "openinference");
409
+ if (!converted.span) {
410
+ throw new TypeError(`openinference row is not a usable span: ${converted.issue?.detail ?? "unknown reason"}`);
411
+ }
412
+ return converted.span;
413
+ });
414
+ }
415
+ function normalizeAttributes(attributes) {
416
+ const normalized = {};
417
+ for (const [key, value] of Object.entries(attributes ?? {})) {
418
+ if (!key || value == null) continue;
419
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
420
+ normalized[key] = value;
421
+ } else if (Array.isArray(value) && value.every(
422
+ (item) => typeof item === "string" || typeof item === "number" || typeof item === "boolean"
423
+ )) {
424
+ normalized[key] = value;
425
+ } else {
426
+ normalized[key] = stableJson(value);
427
+ }
428
+ }
429
+ return normalized;
430
+ }
431
+ function withExportAttributes(spans, attributes) {
432
+ const normalized = normalizeAttributes(attributes);
433
+ if (Object.keys(normalized).length === 0) return [...spans];
434
+ return spans.map((s) => ({ ...s, attributes: { ...s.attributes, ...normalized } }));
435
+ }
436
+ function exportTraceEvidenceRows(rows, opts = {}, wrapper) {
437
+ const format = detectFormat(rows, opts.format ?? "auto");
438
+ const converted = format === "openinference" ? openInferenceToSpans(requireOpenInferenceRows(rows)) : sandboxEventsToSpans(requireObjectRows(rows), wrapper);
439
+ const spans = withExportAttributes(converted, opts.attributes);
440
+ if (spans.length === 0) throw new Error(`no spans exported from ${format} input`);
441
+ const redacted = redactSpans(spans);
442
+ const validated = validateOtlpSpans(redacted.spans, `${format} export`);
443
+ return {
444
+ format,
445
+ spans: validated,
446
+ redactionCount: redacted.report.redactionCount,
447
+ redactionsByDetector: redacted.report.byDetector
448
+ };
449
+ }
450
+ function exportTraceEvidenceText(text, opts = {}) {
451
+ const parsed = parseJsonRows(text);
452
+ return exportTraceEvidenceRows(parsed.rows, opts, parsed.wrapper);
453
+ }
454
+ async function exportTraceEvidenceFile(inputPath, opts = {}) {
455
+ if (/\.(?:jsonl|ndjson)$/i.test(inputPath)) {
456
+ let format;
457
+ const spans = [];
458
+ const sandboxRows = [];
459
+ let redactionCount = 0;
460
+ const redactionsByDetector = {};
461
+ for await (const row of readJsonl(inputPath)) {
462
+ format ??= detectFormat([row], opts.format ?? "auto");
463
+ if (format === "sandbox-events") {
464
+ sandboxRows.push(row);
465
+ continue;
466
+ }
467
+ const converted = exportTraceEvidenceRows([row], { ...opts, format });
468
+ appendAll(spans, converted.spans);
469
+ redactionCount += converted.redactionCount;
470
+ for (const [rule, count] of Object.entries(converted.redactionsByDetector)) {
471
+ redactionsByDetector[rule] = (redactionsByDetector[rule] ?? 0) + count;
472
+ }
473
+ }
474
+ if (!format) throw new Error("input file is empty");
475
+ if (format === "sandbox-events") {
476
+ return exportTraceEvidenceRows(sandboxRows, { ...opts, format });
477
+ }
478
+ return {
479
+ format,
480
+ spans: validateOtlpSpans(spans, `${format} file export`),
481
+ redactionCount,
482
+ redactionsByDetector
483
+ };
484
+ }
485
+ const text = await readFile(inputPath, "utf8");
486
+ return exportTraceEvidenceText(text, opts);
487
+ }
488
+
489
+ export {
490
+ detectEvidenceFormat,
491
+ exportTraceEvidenceFile
492
+ };