@tangle-network/traces 0.2.2 → 0.4.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.
- package/dist/{chunk-4PC7MQXT.js → chunk-33NBMIH4.js} +438 -196
- package/dist/cli.js +11 -22
- package/dist/index.d.ts +82 -24
- package/dist/index.js +21 -35
- package/package.json +1 -1
|
@@ -70,6 +70,25 @@ async function analyzeSpans(spans, opts = {}) {
|
|
|
70
70
|
return { otlpPath, result };
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
// src/time.ts
|
|
74
|
+
function parseIsoToEpochMs(ts) {
|
|
75
|
+
if (!ts) return 0;
|
|
76
|
+
if (/^\d+$/.test(ts)) return Number(ts);
|
|
77
|
+
const n = Date.parse(ts);
|
|
78
|
+
return Number.isNaN(n) ? 0 : n;
|
|
79
|
+
}
|
|
80
|
+
function parseSince(value) {
|
|
81
|
+
const m = value.match(/^(\d+)\s*([mhd])$/i);
|
|
82
|
+
if (m) {
|
|
83
|
+
const unit = m[2].toLowerCase();
|
|
84
|
+
const ms = unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
|
|
85
|
+
return Date.now() - Number(m[1]) * ms;
|
|
86
|
+
}
|
|
87
|
+
const t = Date.parse(value);
|
|
88
|
+
if (Number.isNaN(t)) throw new Error(`--since: expected 30m / 2h / 7d or an ISO date, got "${value}"`);
|
|
89
|
+
return t;
|
|
90
|
+
}
|
|
91
|
+
|
|
73
92
|
// src/runtime-store.ts
|
|
74
93
|
import { InMemoryTraceStore } from "@tangle-network/agent-eval";
|
|
75
94
|
var KIND = {
|
|
@@ -79,10 +98,6 @@ var KIND = {
|
|
|
79
98
|
CHAIN: "custom",
|
|
80
99
|
SPAN: "custom"
|
|
81
100
|
};
|
|
82
|
-
function ms(iso2) {
|
|
83
|
-
const n = Date.parse(iso2);
|
|
84
|
-
return Number.isNaN(n) ? 0 : n;
|
|
85
|
-
}
|
|
86
101
|
async function toRuntimeStore(spans) {
|
|
87
102
|
const store = new InMemoryTraceStore();
|
|
88
103
|
const runIds = /* @__PURE__ */ new Set();
|
|
@@ -97,8 +112,8 @@ async function toRuntimeStore(spans) {
|
|
|
97
112
|
parentSpanId: s.parent_span_id ?? void 0,
|
|
98
113
|
runId: s.trace_id,
|
|
99
114
|
name: s.name,
|
|
100
|
-
startedAt:
|
|
101
|
-
endedAt:
|
|
115
|
+
startedAt: parseIsoToEpochMs(s.start_time),
|
|
116
|
+
endedAt: parseIsoToEpochMs(s.end_time),
|
|
102
117
|
status: s.status.code === "ERROR" ? "error" : "ok",
|
|
103
118
|
error: s.status.message,
|
|
104
119
|
attributes: s.attributes
|
|
@@ -129,6 +144,11 @@ import { readdir, readFile, stat } from "fs/promises";
|
|
|
129
144
|
import { homedir } from "os";
|
|
130
145
|
import { basename, join } from "path";
|
|
131
146
|
var SERVICE = "amp";
|
|
147
|
+
var CONTENT_CAP = 8e3;
|
|
148
|
+
function textOf(content) {
|
|
149
|
+
if (typeof content === "string") return content.slice(0, CONTENT_CAP);
|
|
150
|
+
return (Array.isArray(content) ? content : []).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n").trim().slice(0, CONTENT_CAP);
|
|
151
|
+
}
|
|
132
152
|
var AmpAdapter = class {
|
|
133
153
|
harness = "amp";
|
|
134
154
|
threadsRoot() {
|
|
@@ -177,25 +197,47 @@ var AmpAdapter = class {
|
|
|
177
197
|
for (const m of messages) {
|
|
178
198
|
const mid = m.messageId ?? step;
|
|
179
199
|
const llmId = `llm:${mid}`;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
200
|
+
if (m.role === "user") {
|
|
201
|
+
const prompt = textOf(m.content);
|
|
202
|
+
if (prompt) {
|
|
203
|
+
spans.push(
|
|
204
|
+
span({
|
|
205
|
+
traceId,
|
|
206
|
+
spanId: `${llmId}:user`,
|
|
207
|
+
parentSpanId: rootId,
|
|
208
|
+
name: "user.prompt",
|
|
209
|
+
kind: "CHAIN",
|
|
210
|
+
startTime: start,
|
|
211
|
+
service: SERVICE,
|
|
212
|
+
agent: SERVICE,
|
|
213
|
+
step,
|
|
214
|
+
content: prompt
|
|
215
|
+
})
|
|
216
|
+
);
|
|
217
|
+
step += 1;
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
const u = m.usage;
|
|
221
|
+
const inputTokens2 = u ? (u.inputTokens ?? 0) + (u.cacheReadInputTokens ?? 0) + (u.cacheCreationInputTokens ?? 0) || null : null;
|
|
222
|
+
spans.push(
|
|
223
|
+
span({
|
|
224
|
+
traceId,
|
|
225
|
+
spanId: llmId,
|
|
226
|
+
parentSpanId: rootId,
|
|
227
|
+
name: `message.${m.role ?? "unknown"}`,
|
|
228
|
+
kind: "LLM",
|
|
229
|
+
startTime: start,
|
|
230
|
+
service: SERVICE,
|
|
231
|
+
agent: SERVICE,
|
|
232
|
+
model: u?.model ?? null,
|
|
233
|
+
inputTokens: inputTokens2,
|
|
234
|
+
outputTokens: u?.outputTokens ?? null,
|
|
235
|
+
step,
|
|
236
|
+
content: textOf(m.content) || null
|
|
237
|
+
})
|
|
238
|
+
);
|
|
239
|
+
step += 1;
|
|
240
|
+
}
|
|
199
241
|
for (const b of m.content ?? []) {
|
|
200
242
|
if (b.type === "tool_use" && b.name) {
|
|
201
243
|
const t = span({
|
|
@@ -248,6 +290,11 @@ function inputTokens(u) {
|
|
|
248
290
|
function asBlocks(content) {
|
|
249
291
|
return Array.isArray(content) ? content : [];
|
|
250
292
|
}
|
|
293
|
+
var CONTENT_CAP2 = 8e3;
|
|
294
|
+
function textOf2(content) {
|
|
295
|
+
if (typeof content === "string") return content.slice(0, CONTENT_CAP2);
|
|
296
|
+
return asBlocks(content).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n").trim().slice(0, CONTENT_CAP2);
|
|
297
|
+
}
|
|
251
298
|
function stringifyToolResult(content) {
|
|
252
299
|
if (typeof content === "string") return content;
|
|
253
300
|
if (Array.isArray(content)) {
|
|
@@ -277,7 +324,8 @@ function parseClaudeStream(events, ctx) {
|
|
|
277
324
|
model: ev.message.model ?? null,
|
|
278
325
|
inputTokens: inputTokens(ev.message.usage),
|
|
279
326
|
outputTokens: ev.message.usage?.output_tokens ?? null,
|
|
280
|
-
step
|
|
327
|
+
step,
|
|
328
|
+
content: textOf2(ev.message.content) || null
|
|
281
329
|
})
|
|
282
330
|
);
|
|
283
331
|
step += 1;
|
|
@@ -303,6 +351,24 @@ function parseClaudeStream(events, ctx) {
|
|
|
303
351
|
step += 1;
|
|
304
352
|
}
|
|
305
353
|
} else if (ev.type === "user" && ev.message) {
|
|
354
|
+
const prompt = textOf2(ev.message.content);
|
|
355
|
+
if (prompt) {
|
|
356
|
+
spans.push(
|
|
357
|
+
span({
|
|
358
|
+
traceId: ctx.traceId,
|
|
359
|
+
spanId: `${ctx.idPrefix}${uid}:user`,
|
|
360
|
+
parentSpanId: ctx.rootParent,
|
|
361
|
+
name: "user.prompt",
|
|
362
|
+
kind: "CHAIN",
|
|
363
|
+
startTime: ts,
|
|
364
|
+
service: SERVICE2,
|
|
365
|
+
agent: ctx.agent,
|
|
366
|
+
step,
|
|
367
|
+
content: prompt
|
|
368
|
+
})
|
|
369
|
+
);
|
|
370
|
+
step += 1;
|
|
371
|
+
}
|
|
306
372
|
for (const block of asBlocks(ev.message.content)) {
|
|
307
373
|
if (block.type !== "tool_result" || !block.tool_use_id) continue;
|
|
308
374
|
backfillResult(toolSpanByUseId.get(block.tool_use_id), ts, block.is_error === true, stringifyToolResult(block.content));
|
|
@@ -452,6 +518,10 @@ function contentToString(content) {
|
|
|
452
518
|
}
|
|
453
519
|
return "";
|
|
454
520
|
}
|
|
521
|
+
var CONTENT_CAP3 = 8e3;
|
|
522
|
+
function textOf3(content) {
|
|
523
|
+
return contentToString(content).trim().slice(0, CONTENT_CAP3);
|
|
524
|
+
}
|
|
455
525
|
function outputIsError(output) {
|
|
456
526
|
const s = typeof output === "string" ? output : JSON.stringify(output ?? "");
|
|
457
527
|
const error = /"success"\s*:\s*false|exit(?:_| )code["\s:]+[1-9]|\bcommand failed\b|\bENOENT\b|\berror:/i.test(s);
|
|
@@ -599,8 +669,27 @@ var CodexAdapter = class {
|
|
|
599
669
|
t.end_time = ts;
|
|
600
670
|
t.status = error ? { code: "ERROR", message } : { code: "OK" };
|
|
601
671
|
}
|
|
672
|
+
} else if (l.type === "response_item" && l.payload?.type === "message" && l.payload.role === "user") {
|
|
673
|
+
const prompt = textOf3(l.payload.content);
|
|
674
|
+
if (prompt) {
|
|
675
|
+
spans.push(
|
|
676
|
+
span({
|
|
677
|
+
traceId,
|
|
678
|
+
spanId: `msg:${step}:user`,
|
|
679
|
+
parentSpanId: rootId,
|
|
680
|
+
name: "user.prompt",
|
|
681
|
+
kind: "CHAIN",
|
|
682
|
+
startTime: ts,
|
|
683
|
+
service: SERVICE3,
|
|
684
|
+
agent: SERVICE3,
|
|
685
|
+
step,
|
|
686
|
+
content: prompt
|
|
687
|
+
})
|
|
688
|
+
);
|
|
689
|
+
step += 1;
|
|
690
|
+
}
|
|
602
691
|
} else if (l.type === "response_item" && l.payload?.type === "message") {
|
|
603
|
-
const text =
|
|
692
|
+
const text = textOf3(l.payload.content);
|
|
604
693
|
if (text) {
|
|
605
694
|
spans.push(
|
|
606
695
|
span({
|
|
@@ -613,7 +702,7 @@ var CodexAdapter = class {
|
|
|
613
702
|
service: SERVICE3,
|
|
614
703
|
agent: SERVICE3,
|
|
615
704
|
step,
|
|
616
|
-
content: text
|
|
705
|
+
content: text
|
|
617
706
|
})
|
|
618
707
|
);
|
|
619
708
|
step += 1;
|
|
@@ -755,6 +844,10 @@ import { readdir as readdir5, readFile as readFile5, stat as stat5 } from "fs/pr
|
|
|
755
844
|
import { homedir as homedir5 } from "os";
|
|
756
845
|
import { basename as basename4, join as join5 } from "path";
|
|
757
846
|
var SERVICE5 = "factory";
|
|
847
|
+
var CONTENT_CAP4 = 8e3;
|
|
848
|
+
function textOf4(content) {
|
|
849
|
+
return (content ?? []).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("").trim().slice(0, CONTENT_CAP4);
|
|
850
|
+
}
|
|
758
851
|
function parseLines4(raw) {
|
|
759
852
|
const out = [];
|
|
760
853
|
for (const line of raw.split("\n")) {
|
|
@@ -841,25 +934,45 @@ var FactoryAdapter = class {
|
|
|
841
934
|
if (l.type !== "message" || !l.message) continue;
|
|
842
935
|
const ts = l.timestamp ?? (/* @__PURE__ */ new Date(0)).toISOString();
|
|
843
936
|
const role = l.message.role;
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
937
|
+
const text = textOf4(l.message.content);
|
|
938
|
+
if (role === "user") {
|
|
939
|
+
if (text) {
|
|
940
|
+
spans.push(
|
|
941
|
+
span({
|
|
942
|
+
traceId,
|
|
943
|
+
spanId: `user:${l.id ?? step}`,
|
|
944
|
+
parentSpanId: rootId,
|
|
945
|
+
name: "user.prompt",
|
|
946
|
+
kind: "CHAIN",
|
|
947
|
+
startTime: ts,
|
|
948
|
+
service: SERVICE5,
|
|
949
|
+
agent: SERVICE5,
|
|
950
|
+
step,
|
|
951
|
+
content: text
|
|
952
|
+
})
|
|
953
|
+
);
|
|
954
|
+
step += 1;
|
|
955
|
+
}
|
|
956
|
+
} else {
|
|
957
|
+
const llmId = `llm:${l.id ?? step}`;
|
|
958
|
+
spans.push(
|
|
959
|
+
span({
|
|
960
|
+
traceId,
|
|
961
|
+
spanId: llmId,
|
|
962
|
+
parentSpanId: rootId,
|
|
963
|
+
name: `message.${role ?? "unknown"}`,
|
|
964
|
+
kind: "LLM",
|
|
965
|
+
startTime: ts,
|
|
966
|
+
service: SERVICE5,
|
|
967
|
+
agent: SERVICE5,
|
|
968
|
+
model: settings.model ?? null,
|
|
969
|
+
step,
|
|
970
|
+
content: text || null
|
|
971
|
+
})
|
|
972
|
+
);
|
|
973
|
+
lastLlm = llmId;
|
|
974
|
+
step += 1;
|
|
975
|
+
}
|
|
863
976
|
for (const b of l.message.content ?? []) {
|
|
864
977
|
if (b.type === "tool_use" && b.name) {
|
|
865
978
|
const t = span({
|
|
@@ -897,6 +1010,10 @@ function tokens(t) {
|
|
|
897
1010
|
if (!t) return null;
|
|
898
1011
|
return t.actual ?? t.approx ?? null;
|
|
899
1012
|
}
|
|
1013
|
+
var CONTENT_CAP5 = 8e3;
|
|
1014
|
+
function textOf5(content) {
|
|
1015
|
+
return (content ?? "").trim().slice(0, CONTENT_CAP5);
|
|
1016
|
+
}
|
|
900
1017
|
var ForgeAdapter = class {
|
|
901
1018
|
harness = "forge";
|
|
902
1019
|
aliases = ["forgecode"];
|
|
@@ -949,7 +1066,26 @@ var ForgeAdapter = class {
|
|
|
949
1066
|
for (const raw of messages) {
|
|
950
1067
|
const entry = raw.message ?? raw;
|
|
951
1068
|
const ts = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
952
|
-
if (entry.text) {
|
|
1069
|
+
if (entry.text && entry.text.role === "user") {
|
|
1070
|
+
const prompt = textOf5(entry.text.content);
|
|
1071
|
+
if (prompt) {
|
|
1072
|
+
spans.push(
|
|
1073
|
+
span({
|
|
1074
|
+
traceId,
|
|
1075
|
+
spanId: `user:${step}`,
|
|
1076
|
+
parentSpanId: rootId,
|
|
1077
|
+
name: "user.prompt",
|
|
1078
|
+
kind: "CHAIN",
|
|
1079
|
+
startTime: ts,
|
|
1080
|
+
service: SERVICE6,
|
|
1081
|
+
agent: SERVICE6,
|
|
1082
|
+
step,
|
|
1083
|
+
content: prompt
|
|
1084
|
+
})
|
|
1085
|
+
);
|
|
1086
|
+
step += 1;
|
|
1087
|
+
}
|
|
1088
|
+
} else if (entry.text) {
|
|
953
1089
|
const llmId = `llm:${step}`;
|
|
954
1090
|
spans.push(
|
|
955
1091
|
span({
|
|
@@ -965,7 +1101,7 @@ var ForgeAdapter = class {
|
|
|
965
1101
|
inputTokens: tokens(raw.usage?.prompt_tokens),
|
|
966
1102
|
outputTokens: tokens(raw.usage?.completion_tokens),
|
|
967
1103
|
step,
|
|
968
|
-
content: entry.text.content
|
|
1104
|
+
content: textOf5(entry.text.content) || null
|
|
969
1105
|
})
|
|
970
1106
|
);
|
|
971
1107
|
lastLlm = llmId;
|
|
@@ -1010,6 +1146,11 @@ function iso(ts, fallback) {
|
|
|
1010
1146
|
function toolStatusError(status) {
|
|
1011
1147
|
return status != null && /error|fail|cancel/i.test(status);
|
|
1012
1148
|
}
|
|
1149
|
+
var CONTENT_CAP6 = 8e3;
|
|
1150
|
+
function textOf6(content) {
|
|
1151
|
+
const s = typeof content === "string" ? content : JSON.stringify(content ?? "");
|
|
1152
|
+
return s.trim().slice(0, CONTENT_CAP6);
|
|
1153
|
+
}
|
|
1013
1154
|
var GeminiFamilyAdapter = class {
|
|
1014
1155
|
harness;
|
|
1015
1156
|
aliases;
|
|
@@ -1086,7 +1227,26 @@ var GeminiFamilyAdapter = class {
|
|
|
1086
1227
|
const mid = m.id ?? `m${step}`;
|
|
1087
1228
|
const ts = iso(m.timestamp, start);
|
|
1088
1229
|
const llmId = `llm:${mid}`;
|
|
1089
|
-
|
|
1230
|
+
if (m.type === "user") {
|
|
1231
|
+
const prompt = textOf6(m.content);
|
|
1232
|
+
if (prompt) {
|
|
1233
|
+
spans.push(
|
|
1234
|
+
span({
|
|
1235
|
+
traceId,
|
|
1236
|
+
spanId: `${llmId}:user`,
|
|
1237
|
+
parentSpanId: rootId,
|
|
1238
|
+
name: "user.prompt",
|
|
1239
|
+
kind: "CHAIN",
|
|
1240
|
+
startTime: ts,
|
|
1241
|
+
service: this.service,
|
|
1242
|
+
agent: this.service,
|
|
1243
|
+
step,
|
|
1244
|
+
content: prompt
|
|
1245
|
+
})
|
|
1246
|
+
);
|
|
1247
|
+
step += 1;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1090
1250
|
spans.push(
|
|
1091
1251
|
span({
|
|
1092
1252
|
traceId,
|
|
@@ -1101,7 +1261,7 @@ var GeminiFamilyAdapter = class {
|
|
|
1101
1261
|
inputTokens: m.tokens?.input ?? null,
|
|
1102
1262
|
outputTokens: m.tokens?.output ?? null,
|
|
1103
1263
|
step,
|
|
1104
|
-
content: content
|
|
1264
|
+
content: textOf6(m.content) || null
|
|
1105
1265
|
})
|
|
1106
1266
|
);
|
|
1107
1267
|
step += 1;
|
|
@@ -1141,8 +1301,12 @@ import { readdir as readdir8, readFile as readFile8, stat as stat8 } from "fs/pr
|
|
|
1141
1301
|
import { homedir as homedir8 } from "os";
|
|
1142
1302
|
import { join as join8 } from "path";
|
|
1143
1303
|
var SERVICE7 = "opencode";
|
|
1144
|
-
function isoOf(
|
|
1145
|
-
return new Date(
|
|
1304
|
+
function isoOf(ms) {
|
|
1305
|
+
return new Date(ms ?? 0).toISOString();
|
|
1306
|
+
}
|
|
1307
|
+
var CONTENT_CAP7 = 8e3;
|
|
1308
|
+
function textOf7(parts) {
|
|
1309
|
+
return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("\n").trim().slice(0, CONTENT_CAP7);
|
|
1146
1310
|
}
|
|
1147
1311
|
var OpencodeAdapter = class {
|
|
1148
1312
|
harness = "opencode";
|
|
@@ -1209,6 +1373,19 @@ var OpencodeAdapter = class {
|
|
|
1209
1373
|
for (const { msg } of messages) {
|
|
1210
1374
|
const mid = msg.id ?? `m${step}`;
|
|
1211
1375
|
const llmId = `llm:${mid}`;
|
|
1376
|
+
const pdir = join8(partRoot, mid);
|
|
1377
|
+
const parts = [];
|
|
1378
|
+
try {
|
|
1379
|
+
const partFiles = (await readdir8(pdir)).filter((f) => f.endsWith(".json"));
|
|
1380
|
+
for (const pf of partFiles) {
|
|
1381
|
+
try {
|
|
1382
|
+
parts.push(JSON.parse(await readFile8(join8(pdir, pf), "utf8")));
|
|
1383
|
+
} catch {
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
} catch {
|
|
1387
|
+
}
|
|
1388
|
+
const turnText = textOf7(parts);
|
|
1212
1389
|
spans.push(
|
|
1213
1390
|
span({
|
|
1214
1391
|
traceId,
|
|
@@ -1223,24 +1400,29 @@ var OpencodeAdapter = class {
|
|
|
1223
1400
|
model: msg.modelID ?? null,
|
|
1224
1401
|
inputTokens: msg.tokens?.input ?? null,
|
|
1225
1402
|
outputTokens: msg.tokens?.output ?? null,
|
|
1226
|
-
step
|
|
1403
|
+
step,
|
|
1404
|
+
content: turnText || null
|
|
1227
1405
|
})
|
|
1228
1406
|
);
|
|
1229
1407
|
step += 1;
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1408
|
+
if (msg.role === "user" && turnText) {
|
|
1409
|
+
spans.push(
|
|
1410
|
+
span({
|
|
1411
|
+
traceId,
|
|
1412
|
+
spanId: `${llmId}:user`,
|
|
1413
|
+
parentSpanId: rootId,
|
|
1414
|
+
name: "user.prompt",
|
|
1415
|
+
kind: "CHAIN",
|
|
1416
|
+
startTime: isoOf(msg.time?.created),
|
|
1417
|
+
service: SERVICE7,
|
|
1418
|
+
agent: SERVICE7,
|
|
1419
|
+
step,
|
|
1420
|
+
content: turnText
|
|
1421
|
+
})
|
|
1422
|
+
);
|
|
1423
|
+
step += 1;
|
|
1236
1424
|
}
|
|
1237
|
-
for (const
|
|
1238
|
-
let part;
|
|
1239
|
-
try {
|
|
1240
|
-
part = JSON.parse(await readFile8(join8(pdir, pf), "utf8"));
|
|
1241
|
-
} catch {
|
|
1242
|
-
continue;
|
|
1243
|
-
}
|
|
1425
|
+
for (const part of parts) {
|
|
1244
1426
|
if (part.type !== "tool" || !part.tool) continue;
|
|
1245
1427
|
const err = part.state?.status === "error";
|
|
1246
1428
|
spans.push(
|
|
@@ -1290,9 +1472,10 @@ function isToolBlock(b) {
|
|
|
1290
1472
|
function isToolResultBlock(b) {
|
|
1291
1473
|
return typeof b.type === "string" && /tool[_-]?result|tool[_-]?output/i.test(b.type);
|
|
1292
1474
|
}
|
|
1293
|
-
|
|
1475
|
+
var CONTENT_CAP8 = 8e3;
|
|
1476
|
+
function textOf8(content) {
|
|
1294
1477
|
if (!content) return "";
|
|
1295
|
-
return content.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
|
|
1478
|
+
return content.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("").trim().slice(0, CONTENT_CAP8);
|
|
1296
1479
|
}
|
|
1297
1480
|
var PiAdapter = class {
|
|
1298
1481
|
harness = "pi";
|
|
@@ -1360,27 +1543,48 @@ var PiAdapter = class {
|
|
|
1360
1543
|
const mid = l.id ?? `m${step}`;
|
|
1361
1544
|
const msg = l.message;
|
|
1362
1545
|
const llmId = `llm:${mid}`;
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1546
|
+
if (msg.role === "user") {
|
|
1547
|
+
const prompt = textOf8(msg.content);
|
|
1548
|
+
if (prompt) {
|
|
1549
|
+
spans.push(
|
|
1550
|
+
span({
|
|
1551
|
+
traceId,
|
|
1552
|
+
spanId: `${mid}:user`,
|
|
1553
|
+
parentSpanId: rootId,
|
|
1554
|
+
name: "user.prompt",
|
|
1555
|
+
kind: "CHAIN",
|
|
1556
|
+
startTime: ts,
|
|
1557
|
+
service: SERVICE8,
|
|
1558
|
+
agent: SERVICE8,
|
|
1559
|
+
step,
|
|
1560
|
+
content: prompt
|
|
1561
|
+
})
|
|
1562
|
+
);
|
|
1563
|
+
step += 1;
|
|
1564
|
+
}
|
|
1565
|
+
} else {
|
|
1566
|
+
const errored = !!msg.errorMessage;
|
|
1567
|
+
spans.push(
|
|
1568
|
+
span({
|
|
1569
|
+
traceId,
|
|
1570
|
+
spanId: llmId,
|
|
1571
|
+
parentSpanId: rootId,
|
|
1572
|
+
name: `message.${msg.role ?? "unknown"}`,
|
|
1573
|
+
kind: "LLM",
|
|
1574
|
+
startTime: ts,
|
|
1575
|
+
status: errored ? "ERROR" : "OK",
|
|
1576
|
+
statusMessage: errored ? msg.errorMessage.slice(0, 500) : void 0,
|
|
1577
|
+
service: SERVICE8,
|
|
1578
|
+
agent: SERVICE8,
|
|
1579
|
+
model: msg.model ?? null,
|
|
1580
|
+
inputTokens: msg.usage?.input ?? null,
|
|
1581
|
+
outputTokens: msg.usage?.output ?? null,
|
|
1582
|
+
step,
|
|
1583
|
+
content: textOf8(msg.content) || null
|
|
1584
|
+
})
|
|
1585
|
+
);
|
|
1586
|
+
step += 1;
|
|
1587
|
+
}
|
|
1384
1588
|
for (const b of msg.content ?? []) {
|
|
1385
1589
|
if (isToolBlock(b)) {
|
|
1386
1590
|
const name = b.toolName ?? b.name ?? "tool";
|
|
@@ -1432,7 +1636,7 @@ function parseLines6(raw) {
|
|
|
1432
1636
|
}
|
|
1433
1637
|
return out;
|
|
1434
1638
|
}
|
|
1435
|
-
function
|
|
1639
|
+
function textOf9(parts) {
|
|
1436
1640
|
if (!parts) return "";
|
|
1437
1641
|
return parts.filter((p) => typeof p.text === "string").map((p) => p.text).join("");
|
|
1438
1642
|
}
|
|
@@ -1514,7 +1718,7 @@ var QwenAdapter = class {
|
|
|
1514
1718
|
inputTokens: r.usageMetadata?.promptTokenCount ?? null,
|
|
1515
1719
|
outputTokens: r.usageMetadata?.candidatesTokenCount ?? null,
|
|
1516
1720
|
step,
|
|
1517
|
-
content:
|
|
1721
|
+
content: textOf9(r.message?.parts).slice(0, 8e3) || null
|
|
1518
1722
|
})
|
|
1519
1723
|
);
|
|
1520
1724
|
lastLlm = llmId;
|
|
@@ -1541,6 +1745,25 @@ var QwenAdapter = class {
|
|
|
1541
1745
|
openToolsByName.set(name, q);
|
|
1542
1746
|
step += 1;
|
|
1543
1747
|
}
|
|
1748
|
+
} else if (r.type === "user") {
|
|
1749
|
+
const prompt = textOf9(r.message?.parts).slice(0, 8e3);
|
|
1750
|
+
if (prompt) {
|
|
1751
|
+
spans.push(
|
|
1752
|
+
span({
|
|
1753
|
+
traceId,
|
|
1754
|
+
spanId: `user:${r.uuid ?? step}`,
|
|
1755
|
+
parentSpanId: rootId,
|
|
1756
|
+
name: "user.prompt",
|
|
1757
|
+
kind: "CHAIN",
|
|
1758
|
+
startTime: ts,
|
|
1759
|
+
service: SERVICE9,
|
|
1760
|
+
agent: SERVICE9,
|
|
1761
|
+
step,
|
|
1762
|
+
content: prompt
|
|
1763
|
+
})
|
|
1764
|
+
);
|
|
1765
|
+
step += 1;
|
|
1766
|
+
}
|
|
1544
1767
|
} else if (r.type === "tool_result") {
|
|
1545
1768
|
const err = r.toolCallResult?.status === "error" || r.toolCallResult?.error != null;
|
|
1546
1769
|
for (const p of r.message?.parts ?? []) {
|
|
@@ -1585,9 +1808,45 @@ function resolveAdapter(harness) {
|
|
|
1585
1808
|
function knownHarnesses() {
|
|
1586
1809
|
return [...BY_NAME.keys()].sort();
|
|
1587
1810
|
}
|
|
1811
|
+
function selectAdapters(opts) {
|
|
1812
|
+
if (opts.adapters && opts.adapters.length > 0) return [...opts.adapters];
|
|
1813
|
+
if (opts.all || !opts.harnesses || opts.harnesses.length === 0) return [...ADAPTERS];
|
|
1814
|
+
return opts.harnesses.map((h) => {
|
|
1815
|
+
const a = resolveAdapter(h);
|
|
1816
|
+
if (!a) throw new Error(`unknown harness "${h}". Known: ${knownHarnesses().join(", ")}`);
|
|
1817
|
+
return a;
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
// src/session-source.ts
|
|
1822
|
+
async function* scanSessions(opts) {
|
|
1823
|
+
for (const adapter of selectAdapters(opts)) {
|
|
1824
|
+
if (opts.signal?.aborted) return;
|
|
1825
|
+
let refs;
|
|
1826
|
+
try {
|
|
1827
|
+
refs = await adapter.locate({ cwd: opts.cwd, sinceMs: opts.sinceMs });
|
|
1828
|
+
} catch (err) {
|
|
1829
|
+
opts.onError?.(err);
|
|
1830
|
+
continue;
|
|
1831
|
+
}
|
|
1832
|
+
if (opts.last && opts.last > 0) refs = refs.slice(0, opts.last);
|
|
1833
|
+
for (const ref of refs) {
|
|
1834
|
+
if (opts.signal?.aborted) return;
|
|
1835
|
+
let spans;
|
|
1836
|
+
try {
|
|
1837
|
+
spans = await adapter.parse(ref);
|
|
1838
|
+
} catch (err) {
|
|
1839
|
+
opts.onError?.(err, ref);
|
|
1840
|
+
continue;
|
|
1841
|
+
}
|
|
1842
|
+
if (spans.length === 0) continue;
|
|
1843
|
+
yield { adapter, ref, spans };
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1588
1847
|
|
|
1589
1848
|
// src/observer.ts
|
|
1590
|
-
var sleep = (
|
|
1849
|
+
var sleep = (ms, signal) => new Promise((resolve) => {
|
|
1591
1850
|
const onAbort = () => {
|
|
1592
1851
|
clearTimeout(t);
|
|
1593
1852
|
resolve();
|
|
@@ -1595,19 +1854,9 @@ var sleep = (ms2, signal) => new Promise((resolve) => {
|
|
|
1595
1854
|
const t = setTimeout(() => {
|
|
1596
1855
|
signal?.removeEventListener("abort", onAbort);
|
|
1597
1856
|
resolve();
|
|
1598
|
-
},
|
|
1857
|
+
}, ms);
|
|
1599
1858
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1600
1859
|
});
|
|
1601
|
-
function adaptersFor(opts) {
|
|
1602
|
-
if (opts.adapters && opts.adapters.length > 0) return opts.adapters;
|
|
1603
|
-
if (opts.all || !opts.harnesses || opts.harnesses.length === 0) return [...listAdapters()];
|
|
1604
|
-
const out = [];
|
|
1605
|
-
for (const h of opts.harnesses) {
|
|
1606
|
-
const a = resolveAdapter(h);
|
|
1607
|
-
if (a && !out.includes(a)) out.push(a);
|
|
1608
|
-
}
|
|
1609
|
-
return out;
|
|
1610
|
-
}
|
|
1611
1860
|
function loopKey(sessionId, toolName, argHash) {
|
|
1612
1861
|
return `${sessionId}\0${toolName}\0${argHash}`;
|
|
1613
1862
|
}
|
|
@@ -1615,57 +1864,50 @@ async function watchSessions(opts) {
|
|
|
1615
1864
|
const windowMs = opts.windowMs ?? 30 * 6e4;
|
|
1616
1865
|
const intervalMs = Math.max(250, opts.intervalMs ?? 5e3);
|
|
1617
1866
|
const minLoop = opts.minLoopOccurrences ?? 3;
|
|
1618
|
-
const adapters = adaptersFor(opts);
|
|
1619
1867
|
const seen = /* @__PURE__ */ new Map();
|
|
1620
1868
|
while (!opts.signal?.aborted) {
|
|
1621
|
-
const sinceMs = Date.now() - windowMs;
|
|
1622
1869
|
let sessionCount = 0;
|
|
1623
|
-
for (const adapter of
|
|
1624
|
-
|
|
1625
|
-
|
|
1870
|
+
for await (const { adapter, ref, spans } of scanSessions({
|
|
1871
|
+
adapters: opts.adapters,
|
|
1872
|
+
all: opts.all,
|
|
1873
|
+
harnesses: opts.harnesses,
|
|
1874
|
+
cwd: opts.cwd,
|
|
1875
|
+
sinceMs: Date.now() - windowMs,
|
|
1876
|
+
signal: opts.signal,
|
|
1877
|
+
onError: opts.onError
|
|
1878
|
+
})) {
|
|
1879
|
+
let report;
|
|
1626
1880
|
try {
|
|
1627
|
-
|
|
1881
|
+
report = await runPipelines(spans, { minLoopOccurrences: minLoop });
|
|
1628
1882
|
} catch (err) {
|
|
1629
|
-
opts.onError?.(err);
|
|
1883
|
+
opts.onError?.(err, ref);
|
|
1630
1884
|
continue;
|
|
1631
1885
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
let report;
|
|
1886
|
+
sessionCount += 1;
|
|
1887
|
+
if (opts.onReport) {
|
|
1635
1888
|
try {
|
|
1636
|
-
|
|
1637
|
-
if (spans.length === 0) continue;
|
|
1638
|
-
report = await runPipelines(spans, { minLoopOccurrences: minLoop });
|
|
1889
|
+
await opts.onReport(ref, report);
|
|
1639
1890
|
} catch (err) {
|
|
1640
1891
|
opts.onError?.(err, ref);
|
|
1641
|
-
continue;
|
|
1642
1892
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
toolName: f.toolName,
|
|
1662
|
-
argHash: f.argHash,
|
|
1663
|
-
occurrences: f.occurrences,
|
|
1664
|
-
windowMs: f.windowMs
|
|
1665
|
-
});
|
|
1666
|
-
} catch (err) {
|
|
1667
|
-
opts.onError?.(err, ref);
|
|
1668
|
-
}
|
|
1893
|
+
}
|
|
1894
|
+
for (const f of report.stuckLoops.findings) {
|
|
1895
|
+
const key = loopKey(ref.sessionId, f.toolName, f.argHash);
|
|
1896
|
+
if (f.occurrences > (seen.get(key) ?? 0)) {
|
|
1897
|
+
seen.set(key, f.occurrences);
|
|
1898
|
+
if (opts.onLoop) {
|
|
1899
|
+
try {
|
|
1900
|
+
await opts.onLoop({
|
|
1901
|
+
harness: adapter.harness,
|
|
1902
|
+
sessionId: ref.sessionId,
|
|
1903
|
+
cwd: ref.cwd,
|
|
1904
|
+
toolName: f.toolName,
|
|
1905
|
+
argHash: f.argHash,
|
|
1906
|
+
occurrences: f.occurrences,
|
|
1907
|
+
windowMs: f.windowMs
|
|
1908
|
+
});
|
|
1909
|
+
} catch (err) {
|
|
1910
|
+
opts.onError?.(err, ref);
|
|
1669
1911
|
}
|
|
1670
1912
|
}
|
|
1671
1913
|
}
|
|
@@ -1681,6 +1923,28 @@ async function watchSessions(opts) {
|
|
|
1681
1923
|
}
|
|
1682
1924
|
}
|
|
1683
1925
|
|
|
1926
|
+
// src/attributes.ts
|
|
1927
|
+
var ATTR = {
|
|
1928
|
+
/** = the trace/session id. Wire contract (dedup linkage). */
|
|
1929
|
+
SESSION_ID: "tangle.sessionId",
|
|
1930
|
+
/** "cli" marks a CLI upload so the platform can drop a re-upload of a session
|
|
1931
|
+
* that also streamed live. Wire contract. */
|
|
1932
|
+
INGEST_SOURCE: "tangle.ingest_source",
|
|
1933
|
+
HARNESS: "tangle.harness",
|
|
1934
|
+
CWD: "tangle.cwd",
|
|
1935
|
+
GIT_BRANCH: "tangle.git_branch",
|
|
1936
|
+
HOST: "tangle.host",
|
|
1937
|
+
/** Basename of the session file. (Renamed from the ambiguous `tangle.source`,
|
|
1938
|
+
* which collided with `tangle.ingest_source`.) */
|
|
1939
|
+
SESSION_FILE: "tangle.session_file",
|
|
1940
|
+
UPLOADED_AT: "tangle.uploaded_at",
|
|
1941
|
+
UPLOADER: "tangle.uploader",
|
|
1942
|
+
REDACTION_VERSION: "redaction.version",
|
|
1943
|
+
REDACTION_COUNT: "redaction.count"
|
|
1944
|
+
};
|
|
1945
|
+
var INGEST_SOURCE_CLI = "cli";
|
|
1946
|
+
var DEFAULT_HARNESS = "claude-code";
|
|
1947
|
+
|
|
1684
1948
|
// src/redact.ts
|
|
1685
1949
|
import { DEFAULT_REDACTION_RULES, redactValue } from "@tangle-network/agent-eval/traces";
|
|
1686
1950
|
var CODING_REDACTION_RULES = [
|
|
@@ -1759,42 +2023,19 @@ import { hostedClientFromEnv } from "@tangle-network/agent-eval/hosted";
|
|
|
1759
2023
|
import { REDACTION_VERSION } from "@tangle-network/agent-eval/traces";
|
|
1760
2024
|
var require2 = createRequire(import.meta.url);
|
|
1761
2025
|
var TRACES_VERSION = require2("../package.json").version;
|
|
1762
|
-
function
|
|
1763
|
-
if (opts.all) return [...listAdapters()];
|
|
1764
|
-
const a = resolveAdapter(opts.harness ?? "claude-code");
|
|
1765
|
-
if (!a) throw new Error(`unknown harness "${opts.harness}"`);
|
|
1766
|
-
return [a];
|
|
1767
|
-
}
|
|
1768
|
-
async function planUpload(opts) {
|
|
2026
|
+
async function planUpload(opts = {}) {
|
|
1769
2027
|
const state = await loadState();
|
|
1770
2028
|
const items = [];
|
|
1771
|
-
for (const
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
try {
|
|
1782
|
-
raw = await adapter.parse(ref);
|
|
1783
|
-
} catch (err) {
|
|
1784
|
-
opts.onError?.(err, ref);
|
|
1785
|
-
continue;
|
|
1786
|
-
}
|
|
1787
|
-
if (raw.length === 0) continue;
|
|
1788
|
-
const { spans, report } = redactSpans(raw);
|
|
1789
|
-
const hash = sessionHash(spans);
|
|
1790
|
-
items.push({
|
|
1791
|
-
ref,
|
|
1792
|
-
spans,
|
|
1793
|
-
redaction: report,
|
|
1794
|
-
hash,
|
|
1795
|
-
isNew: !alreadyUploaded(state, ref.harness, ref.sessionId, hash)
|
|
1796
|
-
});
|
|
1797
|
-
}
|
|
2029
|
+
for await (const { ref, spans: raw } of scanSessions(opts)) {
|
|
2030
|
+
const { spans, report } = redactSpans(raw);
|
|
2031
|
+
const hash = sessionHash(spans);
|
|
2032
|
+
items.push({
|
|
2033
|
+
ref,
|
|
2034
|
+
spans,
|
|
2035
|
+
redaction: report,
|
|
2036
|
+
hash,
|
|
2037
|
+
isNew: !alreadyUploaded(state, ref.harness, ref.sessionId, hash)
|
|
2038
|
+
});
|
|
1798
2039
|
}
|
|
1799
2040
|
return { items, state, sinceMs: opts.sinceMs };
|
|
1800
2041
|
}
|
|
@@ -1810,26 +2051,20 @@ async function gitBranch(cwd) {
|
|
|
1810
2051
|
}
|
|
1811
2052
|
async function sessionMeta(item, uploadedAt) {
|
|
1812
2053
|
const meta = {
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2054
|
+
[ATTR.HARNESS]: item.ref.harness,
|
|
2055
|
+
[ATTR.SESSION_FILE]: basename8(item.ref.path),
|
|
2056
|
+
[ATTR.HOST]: hostname(),
|
|
2057
|
+
[ATTR.UPLOADED_AT]: uploadedAt,
|
|
2058
|
+
[ATTR.UPLOADER]: `tangle-traces@${TRACES_VERSION}`,
|
|
2059
|
+
[ATTR.REDACTION_VERSION]: REDACTION_VERSION,
|
|
2060
|
+
[ATTR.REDACTION_COUNT]: item.redaction.redactionCount
|
|
1820
2061
|
};
|
|
1821
|
-
if (item.ref.cwd) meta[
|
|
2062
|
+
if (item.ref.cwd) meta[ATTR.CWD] = item.ref.cwd;
|
|
1822
2063
|
const branch = await gitBranch(item.ref.cwd);
|
|
1823
|
-
if (branch) meta[
|
|
2064
|
+
if (branch) meta[ATTR.GIT_BRANCH] = branch;
|
|
1824
2065
|
return meta;
|
|
1825
2066
|
}
|
|
1826
|
-
|
|
1827
|
-
if (!ts) return 0;
|
|
1828
|
-
if (/^\d+$/.test(ts)) return Number(ts);
|
|
1829
|
-
const n = Date.parse(ts);
|
|
1830
|
-
return Number.isNaN(n) ? 0 : n;
|
|
1831
|
-
}
|
|
1832
|
-
var msToNano = (iso2) => epochMs(iso2) * 1e6;
|
|
2067
|
+
var msToNano = (iso2) => parseIsoToEpochMs(iso2) * 1e6;
|
|
1833
2068
|
function toTraceSpanEvents(spans, rootMeta) {
|
|
1834
2069
|
return spans.map((s) => {
|
|
1835
2070
|
const attributes = {};
|
|
@@ -1837,8 +2072,8 @@ function toTraceSpanEvents(spans, rootMeta) {
|
|
|
1837
2072
|
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") attributes[k] = v;
|
|
1838
2073
|
else if (v != null) attributes[k] = JSON.stringify(v);
|
|
1839
2074
|
}
|
|
1840
|
-
attributes[
|
|
1841
|
-
attributes[
|
|
2075
|
+
attributes[ATTR.SESSION_ID] = s.trace_id;
|
|
2076
|
+
attributes[ATTR.INGEST_SOURCE] = INGEST_SOURCE_CLI;
|
|
1842
2077
|
if (s.parent_span_id === null) Object.assign(attributes, rootMeta);
|
|
1843
2078
|
const startNano = msToNano(s.start_time);
|
|
1844
2079
|
return {
|
|
@@ -1907,6 +2142,8 @@ export {
|
|
|
1907
2142
|
serializeSpans,
|
|
1908
2143
|
writeOtlpFile,
|
|
1909
2144
|
analyzeSpans,
|
|
2145
|
+
parseIsoToEpochMs,
|
|
2146
|
+
parseSince,
|
|
1910
2147
|
toRuntimeStore,
|
|
1911
2148
|
runPipelines,
|
|
1912
2149
|
AmpAdapter,
|
|
@@ -1922,7 +2159,12 @@ export {
|
|
|
1922
2159
|
listAdapters,
|
|
1923
2160
|
resolveAdapter,
|
|
1924
2161
|
knownHarnesses,
|
|
2162
|
+
selectAdapters,
|
|
2163
|
+
scanSessions,
|
|
1925
2164
|
watchSessions,
|
|
2165
|
+
ATTR,
|
|
2166
|
+
INGEST_SOURCE_CLI,
|
|
2167
|
+
DEFAULT_HARNESS,
|
|
1926
2168
|
CODING_REDACTION_RULES,
|
|
1927
2169
|
TRACES_REDACTION_RULES,
|
|
1928
2170
|
redactSpans,
|
package/dist/cli.js
CHANGED
|
@@ -3,13 +3,14 @@ import {
|
|
|
3
3
|
analyzeSpans,
|
|
4
4
|
executeUpload,
|
|
5
5
|
knownHarnesses,
|
|
6
|
-
|
|
6
|
+
parseSince,
|
|
7
7
|
planUpload,
|
|
8
8
|
resolveAdapter,
|
|
9
9
|
runPipelines,
|
|
10
|
+
selectAdapters,
|
|
10
11
|
watchSessions,
|
|
11
12
|
writeOtlpFile
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-33NBMIH4.js";
|
|
13
14
|
|
|
14
15
|
// src/cli.ts
|
|
15
16
|
import { stat, writeFile } from "fs/promises";
|
|
@@ -163,18 +164,6 @@ function parseArgs(argv) {
|
|
|
163
164
|
}
|
|
164
165
|
return a;
|
|
165
166
|
}
|
|
166
|
-
function parseSince(s) {
|
|
167
|
-
if (!s) return Date.now() - 24 * 60 * 60 * 1e3;
|
|
168
|
-
const m = s.match(/^(\d+)\s*([mhd])$/i);
|
|
169
|
-
if (m) {
|
|
170
|
-
const unit = m[2].toLowerCase();
|
|
171
|
-
const ms = unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
|
|
172
|
-
return Date.now() - Number(m[1]) * ms;
|
|
173
|
-
}
|
|
174
|
-
const t = Date.parse(s);
|
|
175
|
-
if (Number.isNaN(t)) throw new Error(`--since: expected 30m / 2h / 7d or an ISO date, got "${s}"`);
|
|
176
|
-
return t;
|
|
177
|
-
}
|
|
178
167
|
async function confirm(question) {
|
|
179
168
|
if (!process.stdin.isTTY) return false;
|
|
180
169
|
const rl = (await import("readline/promises")).createInterface({
|
|
@@ -189,12 +178,7 @@ async function confirm(question) {
|
|
|
189
178
|
}
|
|
190
179
|
}
|
|
191
180
|
function adaptersFor(args) {
|
|
192
|
-
|
|
193
|
-
const adapter = resolveAdapter(args.harness);
|
|
194
|
-
if (!adapter) {
|
|
195
|
-
throw new Error(`unknown harness "${args.harness}". Known: ${knownHarnesses().join(", ")}`);
|
|
196
|
-
}
|
|
197
|
-
return [adapter];
|
|
181
|
+
return selectAdapters({ all: args.all, harnesses: [args.harness] });
|
|
198
182
|
}
|
|
199
183
|
async function discover(args) {
|
|
200
184
|
const sinceMs = args.since ? Date.parse(args.since) : void 0;
|
|
@@ -304,9 +288,14 @@ async function cmdWatch(args) {
|
|
|
304
288
|
});
|
|
305
289
|
}
|
|
306
290
|
async function cmdUpload(args) {
|
|
307
|
-
const sinceMs = parseSince(args.since);
|
|
291
|
+
const sinceMs = args.since ? parseSince(args.since) : Date.now() - 24 * 60 * 60 * 1e3;
|
|
308
292
|
const all = args.all || !args.harnessExplicit;
|
|
309
|
-
const plan = await planUpload({
|
|
293
|
+
const plan = await planUpload({
|
|
294
|
+
all,
|
|
295
|
+
harnesses: args.harnessExplicit ? [args.harness] : void 0,
|
|
296
|
+
cwd: args.cwd,
|
|
297
|
+
sinceMs
|
|
298
|
+
});
|
|
310
299
|
const newItems = plan.items.filter((i) => i.isNew);
|
|
311
300
|
const byRule = {};
|
|
312
301
|
let totalRedactions = 0;
|
package/dist/index.d.ts
CHANGED
|
@@ -118,6 +118,49 @@ interface HarnessTraceAdapter {
|
|
|
118
118
|
parse(ref: SessionRef): Promise<OtlpSpan[]>;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Span-attribute keys the upload path stamps. The CLI is the producer; the
|
|
123
|
+
* Tangle Intelligence Platform is the consumer.
|
|
124
|
+
*
|
|
125
|
+
* `SESSION_ID` and `INGEST_SOURCE` are a cross-repo WIRE CONTRACT — the platform
|
|
126
|
+
* keys its cross-source dedup on them (`session.id`/`tangle.sessionId` linkage +
|
|
127
|
+
* `tangle.ingest_source === "cli"`). Do NOT change their string VALUES without a
|
|
128
|
+
* coordinated platform release. The rest are stored-but-not-parsed metadata,
|
|
129
|
+
* free to evolve.
|
|
130
|
+
*
|
|
131
|
+
* `SESSION_ID` stays camelCase because it must match agent-eval's
|
|
132
|
+
* `parseConversationLinkage` key list; the metadata keys are snake_case.
|
|
133
|
+
*/
|
|
134
|
+
declare const ATTR: {
|
|
135
|
+
/** = the trace/session id. Wire contract (dedup linkage). */
|
|
136
|
+
readonly SESSION_ID: "tangle.sessionId";
|
|
137
|
+
/** "cli" marks a CLI upload so the platform can drop a re-upload of a session
|
|
138
|
+
* that also streamed live. Wire contract. */
|
|
139
|
+
readonly INGEST_SOURCE: "tangle.ingest_source";
|
|
140
|
+
readonly HARNESS: "tangle.harness";
|
|
141
|
+
readonly CWD: "tangle.cwd";
|
|
142
|
+
readonly GIT_BRANCH: "tangle.git_branch";
|
|
143
|
+
readonly HOST: "tangle.host";
|
|
144
|
+
/** Basename of the session file. (Renamed from the ambiguous `tangle.source`,
|
|
145
|
+
* which collided with `tangle.ingest_source`.) */
|
|
146
|
+
readonly SESSION_FILE: "tangle.session_file";
|
|
147
|
+
readonly UPLOADED_AT: "tangle.uploaded_at";
|
|
148
|
+
readonly UPLOADER: "tangle.uploader";
|
|
149
|
+
readonly REDACTION_VERSION: "redaction.version";
|
|
150
|
+
readonly REDACTION_COUNT: "redaction.count";
|
|
151
|
+
};
|
|
152
|
+
/** `tangle.ingest_source` value for CLI-uploaded traces. Wire contract. */
|
|
153
|
+
declare const INGEST_SOURCE_CLI = "cli";
|
|
154
|
+
/** Harness used when none is specified on a single-harness command. */
|
|
155
|
+
declare const DEFAULT_HARNESS = "claude-code";
|
|
156
|
+
|
|
157
|
+
/** Parse an ISO-8601 timestamp or an epoch-millis string to epoch ms.
|
|
158
|
+
* Returns 0 for empty/unparseable input (callers treat 0 as "unknown"). */
|
|
159
|
+
declare function parseIsoToEpochMs(ts: string): number;
|
|
160
|
+
/** Parse a `--since` window: `30m` / `2h` / `7d` (relative to now) or an ISO
|
|
161
|
+
* date, → epoch ms. Throws on a malformed value (fail-loud, not silent). */
|
|
162
|
+
declare function parseSince(value: string): number;
|
|
163
|
+
|
|
121
164
|
/**
|
|
122
165
|
* Harness adapter registry — resolves a harness id (or alias) to the
|
|
123
166
|
* adapter that knows its on-disk session format.
|
|
@@ -134,6 +177,41 @@ interface HarnessTraceAdapter {
|
|
|
134
177
|
declare function listAdapters(): readonly HarnessTraceAdapter[];
|
|
135
178
|
declare function resolveAdapter(harness: string): HarnessTraceAdapter | undefined;
|
|
136
179
|
declare function knownHarnesses(): string[];
|
|
180
|
+
/** Options for selecting which adapters a command/SDK call operates on. */
|
|
181
|
+
interface AdapterSelection {
|
|
182
|
+
/** Use these adapters verbatim (e.g. a caller's own harness) — wins over all. */
|
|
183
|
+
adapters?: readonly HarnessTraceAdapter[];
|
|
184
|
+
/** Every known harness. Implied when no `adapters`/`harnesses` are given. */
|
|
185
|
+
all?: boolean;
|
|
186
|
+
/** Specific harness ids/aliases. Unknown ids throw (fail-loud). */
|
|
187
|
+
harnesses?: readonly string[];
|
|
188
|
+
}
|
|
189
|
+
/** Resolve a selection to the adapters to operate on. Precedence:
|
|
190
|
+
* explicit `adapters` → named `harnesses` → everything (`all` or unspecified).
|
|
191
|
+
* A named harness that doesn't resolve throws rather than silently dropping. */
|
|
192
|
+
declare function selectAdapters(opts: AdapterSelection): HarnessTraceAdapter[];
|
|
193
|
+
|
|
194
|
+
interface ScanOptions extends AdapterSelection {
|
|
195
|
+
/** Filter by working directory (exact/prefix). */
|
|
196
|
+
cwd?: string;
|
|
197
|
+
/** Only sessions modified at/after this epoch ms. */
|
|
198
|
+
sinceMs?: number;
|
|
199
|
+
/** Cap to the most-recent N sessions per harness. */
|
|
200
|
+
last?: number;
|
|
201
|
+
/** Cancel the scan; iteration stops between sessions. */
|
|
202
|
+
signal?: AbortSignal;
|
|
203
|
+
/** Per-adapter locate/parse failure (the scan continues). */
|
|
204
|
+
onError?: (error: unknown, ref?: SessionRef) => void;
|
|
205
|
+
}
|
|
206
|
+
interface ScannedSession {
|
|
207
|
+
adapter: HarnessTraceAdapter;
|
|
208
|
+
ref: SessionRef;
|
|
209
|
+
/** Non-empty parsed spans for the session. */
|
|
210
|
+
spans: OtlpSpan[];
|
|
211
|
+
}
|
|
212
|
+
/** Yield every non-empty session across the selected adapters. Locate/parse
|
|
213
|
+
* errors route to `onError` and skip that adapter/session, never aborting. */
|
|
214
|
+
declare function scanSessions(opts: ScanOptions): AsyncGenerator<ScannedSession>;
|
|
137
215
|
|
|
138
216
|
/**
|
|
139
217
|
* Claude Code adapter — `~/.claude/projects/<encoded-cwd>/<session>.jsonl`.
|
|
@@ -537,22 +615,9 @@ declare function redactSpans(spans: readonly OtlpSpan[], rules?: RedactionRule[]
|
|
|
537
615
|
* Redaction is ON by default — the safe posture when traces leave the box.
|
|
538
616
|
*/
|
|
539
617
|
|
|
540
|
-
interface CollectOptions {
|
|
541
|
-
/** Harness ids/aliases. Omit (or `all: true`) → every harness. */
|
|
542
|
-
harnesses?: string[];
|
|
543
|
-
all?: boolean;
|
|
544
|
-
/** Collect from these adapters instead of the built-in registry. */
|
|
545
|
-
adapters?: HarnessTraceAdapter[];
|
|
546
|
-
/** Filter by working directory (exact/prefix). */
|
|
547
|
-
cwd?: string;
|
|
548
|
-
/** Only sessions modified at/after this epoch ms. */
|
|
549
|
-
sinceMs?: number;
|
|
550
|
-
/** Cap to the most-recent N sessions per harness. */
|
|
551
|
-
last?: number;
|
|
618
|
+
interface CollectOptions extends ScanOptions {
|
|
552
619
|
/** Redact PII/secrets (default true). Set false to get raw spans. */
|
|
553
620
|
redact?: boolean;
|
|
554
|
-
/** Called on a per-adapter locate/parse failure (collection continues). */
|
|
555
|
-
onError?: (error: unknown, ref?: SessionRef) => void;
|
|
556
621
|
}
|
|
557
622
|
interface SessionBatch {
|
|
558
623
|
ref: SessionRef;
|
|
@@ -617,16 +682,9 @@ interface UploadPlan {
|
|
|
617
682
|
state: UploadState;
|
|
618
683
|
sinceMs?: number;
|
|
619
684
|
}
|
|
620
|
-
|
|
621
|
-
harness?: string;
|
|
622
|
-
all?: boolean;
|
|
623
|
-
cwd?: string;
|
|
624
|
-
sinceMs?: number;
|
|
625
|
-
/** Called on a per-adapter locate/parse failure (planning continues). */
|
|
626
|
-
onError?: (error: unknown, ref?: SessionRef) => void;
|
|
627
|
-
}
|
|
685
|
+
type PlanOptions = ScanOptions;
|
|
628
686
|
/** Read-only: select sessions in the window, redact, and mark which are new. */
|
|
629
|
-
declare function planUpload(opts
|
|
687
|
+
declare function planUpload(opts?: PlanOptions): Promise<UploadPlan>;
|
|
630
688
|
/** Map redacted OtlpSpan[] → the hosted TraceSpanEvent[] wire shape, attaching
|
|
631
689
|
* session metadata to the root span(s). */
|
|
632
690
|
declare function toTraceSpanEvents(spans: readonly OtlpSpan[], rootMeta: Record<string, string | number | boolean>): TraceSpanEvent[];
|
|
@@ -656,4 +714,4 @@ interface ExecuteOptions {
|
|
|
656
714
|
/** Send the NEW items (or, on dryRun, write the redacted OTLP that would send). */
|
|
657
715
|
declare function executeUpload(plan: UploadPlan, opts?: ExecuteOptions): Promise<UploadResult>;
|
|
658
716
|
|
|
659
|
-
export { AmpAdapter, type AnalyzeOptions, type AnalyzeResult, CODING_REDACTION_RULES, ClaudeAdapter, CodexAdapter, type CollectOptions, CopilotAdapter, type ExecuteOptions, FactoryAdapter, ForgeAdapter, GeminiAdapter, type HarnessTraceAdapter, type LocateOptions, type ObservedLoop, type ObserverOptions, OpencodeAdapter, type OtlpSpan, type OtlpSpanKind, type OtlpStatusCode, PiAdapter, type PipelineOptions, type PipelineReport, type PlanOptions, QwenAdapter, type RuntimeTrace, type SessionBatch, type SessionRef, type SpanInput, type SpanRedaction, TRACES_REDACTION_RULES, type UploadBackend, type UploadItem, type UploadPlan, type UploadRecord, type UploadResult, type UploadState, alreadyUploaded, analyzeSpans, collectSessions, executeUpload, knownHarnesses, listAdapters, loadState, planUpload, redactSpans, resolveAdapter, runPipelines, saveState, serializeSpans, sessionHash, span, toRuntimeStore, toTraceSpanEvents, uploadKey, watchSessions, writeOtlpFile };
|
|
717
|
+
export { ATTR, AmpAdapter, type AnalyzeOptions, type AnalyzeResult, CODING_REDACTION_RULES, ClaudeAdapter, CodexAdapter, type CollectOptions, CopilotAdapter, DEFAULT_HARNESS, type ExecuteOptions, FactoryAdapter, ForgeAdapter, GeminiAdapter, type HarnessTraceAdapter, INGEST_SOURCE_CLI, type LocateOptions, type ObservedLoop, type ObserverOptions, OpencodeAdapter, type OtlpSpan, type OtlpSpanKind, type OtlpStatusCode, PiAdapter, type PipelineOptions, type PipelineReport, type PlanOptions, QwenAdapter, type RuntimeTrace, type ScanOptions, type ScannedSession, type SessionBatch, type SessionRef, type SpanInput, type SpanRedaction, TRACES_REDACTION_RULES, type UploadBackend, type UploadItem, type UploadPlan, type UploadRecord, type UploadResult, type UploadState, alreadyUploaded, analyzeSpans, collectSessions, executeUpload, knownHarnesses, listAdapters, loadState, parseIsoToEpochMs, parseSince, planUpload, redactSpans, resolveAdapter, runPipelines, saveState, scanSessions, selectAdapters, serializeSpans, sessionHash, span, toRuntimeStore, toTraceSpanEvents, uploadKey, watchSessions, writeOtlpFile };
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ATTR,
|
|
2
3
|
AmpAdapter,
|
|
3
4
|
CODING_REDACTION_RULES,
|
|
4
5
|
ClaudeAdapter,
|
|
5
6
|
CodexAdapter,
|
|
6
7
|
CopilotAdapter,
|
|
8
|
+
DEFAULT_HARNESS,
|
|
7
9
|
FactoryAdapter,
|
|
8
10
|
ForgeAdapter,
|
|
9
11
|
GeminiAdapter,
|
|
12
|
+
INGEST_SOURCE_CLI,
|
|
10
13
|
OpencodeAdapter,
|
|
11
14
|
PiAdapter,
|
|
12
15
|
QwenAdapter,
|
|
@@ -17,11 +20,15 @@ import {
|
|
|
17
20
|
knownHarnesses,
|
|
18
21
|
listAdapters,
|
|
19
22
|
loadState,
|
|
23
|
+
parseIsoToEpochMs,
|
|
24
|
+
parseSince,
|
|
20
25
|
planUpload,
|
|
21
26
|
redactSpans,
|
|
22
27
|
resolveAdapter,
|
|
23
28
|
runPipelines,
|
|
24
29
|
saveState,
|
|
30
|
+
scanSessions,
|
|
31
|
+
selectAdapters,
|
|
25
32
|
serializeSpans,
|
|
26
33
|
sessionHash,
|
|
27
34
|
span,
|
|
@@ -30,45 +37,17 @@ import {
|
|
|
30
37
|
uploadKey,
|
|
31
38
|
watchSessions,
|
|
32
39
|
writeOtlpFile
|
|
33
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-33NBMIH4.js";
|
|
34
41
|
|
|
35
42
|
// src/collect.ts
|
|
36
|
-
function adaptersFor(opts) {
|
|
37
|
-
if (opts.adapters && opts.adapters.length > 0) return opts.adapters;
|
|
38
|
-
if (opts.all || !opts.harnesses || opts.harnesses.length === 0) return [...listAdapters()];
|
|
39
|
-
const out = [];
|
|
40
|
-
for (const h of opts.harnesses) {
|
|
41
|
-
const a = resolveAdapter(h);
|
|
42
|
-
if (a && !out.includes(a)) out.push(a);
|
|
43
|
-
}
|
|
44
|
-
return out;
|
|
45
|
-
}
|
|
46
43
|
async function collectSessions(opts = {}) {
|
|
47
44
|
const out = [];
|
|
48
|
-
for (const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (opts.last && opts.last > 0) refs = refs.slice(0, opts.last);
|
|
57
|
-
for (const ref of refs) {
|
|
58
|
-
let spans;
|
|
59
|
-
try {
|
|
60
|
-
spans = await adapter.parse(ref);
|
|
61
|
-
} catch (err) {
|
|
62
|
-
opts.onError?.(err, ref);
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
if (spans.length === 0) continue;
|
|
66
|
-
if (opts.redact === false) {
|
|
67
|
-
out.push({ ref, spans });
|
|
68
|
-
} else {
|
|
69
|
-
const r = redactSpans(spans);
|
|
70
|
-
out.push({ ref, spans: r.spans, redaction: r.report });
|
|
71
|
-
}
|
|
45
|
+
for await (const { ref, spans } of scanSessions(opts)) {
|
|
46
|
+
if (opts.redact === false) {
|
|
47
|
+
out.push({ ref, spans });
|
|
48
|
+
} else {
|
|
49
|
+
const r = redactSpans(spans);
|
|
50
|
+
out.push({ ref, spans: r.spans, redaction: r.report });
|
|
72
51
|
}
|
|
73
52
|
}
|
|
74
53
|
return out;
|
|
@@ -83,16 +62,19 @@ import {
|
|
|
83
62
|
import { createHostedClient, hostedClientFromEnv } from "@tangle-network/agent-eval/hosted";
|
|
84
63
|
import { DEFAULT_REDACTION_RULES, redactString, redactValue } from "@tangle-network/agent-eval/traces";
|
|
85
64
|
export {
|
|
65
|
+
ATTR,
|
|
86
66
|
AmpAdapter,
|
|
87
67
|
AnalystRegistry,
|
|
88
68
|
CODING_REDACTION_RULES,
|
|
89
69
|
ClaudeAdapter,
|
|
90
70
|
CodexAdapter,
|
|
91
71
|
CopilotAdapter,
|
|
72
|
+
DEFAULT_HARNESS,
|
|
92
73
|
DEFAULT_REDACTION_RULES,
|
|
93
74
|
FactoryAdapter,
|
|
94
75
|
ForgeAdapter,
|
|
95
76
|
GeminiAdapter,
|
|
77
|
+
INGEST_SOURCE_CLI,
|
|
96
78
|
OpencodeAdapter,
|
|
97
79
|
PiAdapter,
|
|
98
80
|
QwenAdapter,
|
|
@@ -108,6 +90,8 @@ export {
|
|
|
108
90
|
listAdapters,
|
|
109
91
|
loadState,
|
|
110
92
|
makeFinding,
|
|
93
|
+
parseIsoToEpochMs,
|
|
94
|
+
parseSince,
|
|
111
95
|
planUpload,
|
|
112
96
|
redactSpans,
|
|
113
97
|
redactString,
|
|
@@ -115,6 +99,8 @@ export {
|
|
|
115
99
|
resolveAdapter,
|
|
116
100
|
runPipelines,
|
|
117
101
|
saveState,
|
|
102
|
+
scanSessions,
|
|
103
|
+
selectAdapters,
|
|
118
104
|
serializeSpans,
|
|
119
105
|
sessionHash,
|
|
120
106
|
span,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/traces",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Point it at your coding-agent session traces (Claude Code, Codex, OpenCode, Gemini, Pi, …) and get failure-mode + efficiency findings. CLI + SDK over the @tangle-network/agent-eval analyst suite — observe live sessions, run your own analysts, redact, and upload to the Tangle Intelligence Platform.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|