@tangle-network/traces 0.23.1 → 1.0.1
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/README.md +61 -1078
- package/dist/analyst-model-call-JVUOYZ5V.js +20 -0
- package/dist/ask-2KF4KSPB.js +39 -0
- package/dist/check-YF6ZHPGG.js +322 -0
- package/dist/chunk-2XXABIRL.js +25 -0
- package/dist/chunk-4VKPN432.js +138 -0
- package/dist/chunk-6GOG5KIK.js +53 -0
- package/dist/chunk-7USMM4MA.js +5695 -0
- package/dist/chunk-CG5QHQO4.js +492 -0
- package/dist/chunk-CQ6UYZG6.js +1217 -0
- package/dist/chunk-DU534HAX.js +263 -0
- package/dist/chunk-FC4V25CG.js +503 -0
- package/dist/chunk-GLUDGUGJ.js +232 -0
- package/dist/chunk-GNRXKQEQ.js +120 -0
- package/dist/chunk-K2KH2HHD.js +228 -0
- package/dist/chunk-M3W6TZHQ.js +381 -0
- package/dist/chunk-O52XK7OB.js +19 -0
- package/dist/chunk-PYH3PL3N.js +62 -0
- package/dist/chunk-QLVP37YB.js +782 -0
- package/dist/chunk-QUHUMZ4W.js +759 -0
- package/dist/chunk-REO2JQJY.js +535 -0
- package/dist/chunk-RZL5GAWH.js +60 -0
- package/dist/chunk-TOCNGML5.js +273 -0
- package/dist/chunk-UQSAGXPH.js +208 -0
- package/dist/chunk-VFOLC4OX.js +407 -0
- package/dist/chunk-WF7PTDCZ.js +3921 -0
- package/dist/chunk-YAX72TST.js +259 -0
- package/dist/cli.js +2086 -2050
- package/dist/diff-J4RAIOXO.js +16 -0
- package/dist/improvement-LCCVGGUS.js +25 -0
- package/dist/index.d.ts +1235 -4357
- package/dist/index.js +53 -1204
- package/dist/live-XXL7VZWB.js +14 -0
- package/dist/mcp-EYIDKBEK.js +85 -0
- package/dist/report-E3G2T5TJ.js +338 -0
- package/dist/run-watch-RAOOAXOF.js +649 -0
- package/dist/supervisor-report-RMPLLBGZ.js +29 -0
- package/dist/supervisor-run-context-WO6VOTGV.js +30 -0
- package/dist/upload-3R7GRSKS.js +20 -0
- package/dist/verify-safe-C5ZXZOZ2.js +8 -0
- package/docs/analyze.md +76 -0
- package/docs/ask.md +88 -0
- package/docs/bundle.md +40 -0
- package/docs/check.md +77 -0
- package/docs/convert.md +44 -0
- package/docs/develop.md +32 -0
- package/docs/diff.md +38 -0
- package/docs/facts.md +114 -0
- package/docs/mcp.md +36 -0
- package/docs/migrate-1.0.md +33 -0
- package/docs/parsers.md +66 -0
- package/docs/release-1.0.0.md +147 -0
- package/docs/release-1.0.1.md +4 -0
- package/docs/sdk.md +77 -0
- package/docs/select.md +70 -0
- package/docs/skills.md +46 -0
- package/docs/upload.md +47 -0
- package/docs/watch-jsonl.v1.example.jsonl +5 -0
- package/docs/watch.md +66 -0
- package/examples/README.md +9 -0
- package/examples/custom-analyst.ts +65 -74
- package/install.sh +22 -2
- package/package.json +10 -9
- package/skills/inspect-agent-traces/SKILL.md +47 -110
- package/dist/chunk-7DVZCCZI.js +0 -20983
- package/docs/replay-verify.md +0 -123
- package/docs/stream-findings.svg +0 -39
- package/docs/trace-analysts.md +0 -643
- package/examples/custom-backend.ts +0 -21
- package/examples/external-engines.ts +0 -62
- package/examples/improvement-config.mjs +0 -63
- package/examples/observe-and-alert.ts +0 -19
- package/examples/register-harness.ts +0 -26
- package/skills/build-trace-analyst/SKILL.md +0 -76
- package/skills/build-trace-analyst/agents/openai.yaml +0 -4
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ATTR,
|
|
3
|
+
capText,
|
|
4
|
+
stampEnvironmentAttrs
|
|
5
|
+
} from "./chunk-UQSAGXPH.js";
|
|
6
|
+
import {
|
|
7
|
+
appendAll,
|
|
8
|
+
otlpRowToSpan,
|
|
9
|
+
readJsonl,
|
|
10
|
+
span,
|
|
11
|
+
validateOtlpSpans
|
|
12
|
+
} from "./chunk-CQ6UYZG6.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
|
+
};
|