@tangle-network/traces 0.3.0 → 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.
@@ -144,6 +144,11 @@ import { readdir, readFile, stat } from "fs/promises";
144
144
  import { homedir } from "os";
145
145
  import { basename, join } from "path";
146
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
+ }
147
152
  var AmpAdapter = class {
148
153
  harness = "amp";
149
154
  threadsRoot() {
@@ -192,25 +197,47 @@ var AmpAdapter = class {
192
197
  for (const m of messages) {
193
198
  const mid = m.messageId ?? step;
194
199
  const llmId = `llm:${mid}`;
195
- const u = m.usage;
196
- const inputTokens2 = u ? (u.inputTokens ?? 0) + (u.cacheReadInputTokens ?? 0) + (u.cacheCreationInputTokens ?? 0) || null : null;
197
- spans.push(
198
- span({
199
- traceId,
200
- spanId: llmId,
201
- parentSpanId: rootId,
202
- name: `message.${m.role ?? "unknown"}`,
203
- kind: "LLM",
204
- startTime: start,
205
- service: SERVICE,
206
- agent: SERVICE,
207
- model: u?.model ?? null,
208
- inputTokens: inputTokens2,
209
- outputTokens: u?.outputTokens ?? null,
210
- step
211
- })
212
- );
213
- step += 1;
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
+ }
214
241
  for (const b of m.content ?? []) {
215
242
  if (b.type === "tool_use" && b.name) {
216
243
  const t = span({
@@ -263,6 +290,11 @@ function inputTokens(u) {
263
290
  function asBlocks(content) {
264
291
  return Array.isArray(content) ? content : [];
265
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
+ }
266
298
  function stringifyToolResult(content) {
267
299
  if (typeof content === "string") return content;
268
300
  if (Array.isArray(content)) {
@@ -292,7 +324,8 @@ function parseClaudeStream(events, ctx) {
292
324
  model: ev.message.model ?? null,
293
325
  inputTokens: inputTokens(ev.message.usage),
294
326
  outputTokens: ev.message.usage?.output_tokens ?? null,
295
- step
327
+ step,
328
+ content: textOf2(ev.message.content) || null
296
329
  })
297
330
  );
298
331
  step += 1;
@@ -318,6 +351,24 @@ function parseClaudeStream(events, ctx) {
318
351
  step += 1;
319
352
  }
320
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
+ }
321
372
  for (const block of asBlocks(ev.message.content)) {
322
373
  if (block.type !== "tool_result" || !block.tool_use_id) continue;
323
374
  backfillResult(toolSpanByUseId.get(block.tool_use_id), ts, block.is_error === true, stringifyToolResult(block.content));
@@ -467,6 +518,10 @@ function contentToString(content) {
467
518
  }
468
519
  return "";
469
520
  }
521
+ var CONTENT_CAP3 = 8e3;
522
+ function textOf3(content) {
523
+ return contentToString(content).trim().slice(0, CONTENT_CAP3);
524
+ }
470
525
  function outputIsError(output) {
471
526
  const s = typeof output === "string" ? output : JSON.stringify(output ?? "");
472
527
  const error = /"success"\s*:\s*false|exit(?:_| )code["\s:]+[1-9]|\bcommand failed\b|\bENOENT\b|\berror:/i.test(s);
@@ -614,8 +669,27 @@ var CodexAdapter = class {
614
669
  t.end_time = ts;
615
670
  t.status = error ? { code: "ERROR", message } : { code: "OK" };
616
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
+ }
617
691
  } else if (l.type === "response_item" && l.payload?.type === "message") {
618
- const text = contentToString(l.payload.content);
692
+ const text = textOf3(l.payload.content);
619
693
  if (text) {
620
694
  spans.push(
621
695
  span({
@@ -628,7 +702,7 @@ var CodexAdapter = class {
628
702
  service: SERVICE3,
629
703
  agent: SERVICE3,
630
704
  step,
631
- content: text.slice(0, 8e3)
705
+ content: text
632
706
  })
633
707
  );
634
708
  step += 1;
@@ -770,6 +844,10 @@ import { readdir as readdir5, readFile as readFile5, stat as stat5 } from "fs/pr
770
844
  import { homedir as homedir5 } from "os";
771
845
  import { basename as basename4, join as join5 } from "path";
772
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
+ }
773
851
  function parseLines4(raw) {
774
852
  const out = [];
775
853
  for (const line of raw.split("\n")) {
@@ -856,25 +934,45 @@ var FactoryAdapter = class {
856
934
  if (l.type !== "message" || !l.message) continue;
857
935
  const ts = l.timestamp ?? (/* @__PURE__ */ new Date(0)).toISOString();
858
936
  const role = l.message.role;
859
- const llmId = `llm:${l.id ?? step}`;
860
- const text = (l.message.content ?? []).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
861
- spans.push(
862
- span({
863
- traceId,
864
- spanId: llmId,
865
- parentSpanId: rootId,
866
- name: `message.${role ?? "unknown"}`,
867
- kind: "LLM",
868
- startTime: ts,
869
- service: SERVICE5,
870
- agent: SERVICE5,
871
- model: settings.model ?? null,
872
- step,
873
- content: text ? text.slice(0, 8e3) : null
874
- })
875
- );
876
- lastLlm = llmId;
877
- step += 1;
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
+ }
878
976
  for (const b of l.message.content ?? []) {
879
977
  if (b.type === "tool_use" && b.name) {
880
978
  const t = span({
@@ -912,6 +1010,10 @@ function tokens(t) {
912
1010
  if (!t) return null;
913
1011
  return t.actual ?? t.approx ?? null;
914
1012
  }
1013
+ var CONTENT_CAP5 = 8e3;
1014
+ function textOf5(content) {
1015
+ return (content ?? "").trim().slice(0, CONTENT_CAP5);
1016
+ }
915
1017
  var ForgeAdapter = class {
916
1018
  harness = "forge";
917
1019
  aliases = ["forgecode"];
@@ -964,7 +1066,26 @@ var ForgeAdapter = class {
964
1066
  for (const raw of messages) {
965
1067
  const entry = raw.message ?? raw;
966
1068
  const ts = (/* @__PURE__ */ new Date(0)).toISOString();
967
- 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) {
968
1089
  const llmId = `llm:${step}`;
969
1090
  spans.push(
970
1091
  span({
@@ -980,7 +1101,7 @@ var ForgeAdapter = class {
980
1101
  inputTokens: tokens(raw.usage?.prompt_tokens),
981
1102
  outputTokens: tokens(raw.usage?.completion_tokens),
982
1103
  step,
983
- content: entry.text.content ? entry.text.content.slice(0, 8e3) : null
1104
+ content: textOf5(entry.text.content) || null
984
1105
  })
985
1106
  );
986
1107
  lastLlm = llmId;
@@ -1025,6 +1146,11 @@ function iso(ts, fallback) {
1025
1146
  function toolStatusError(status) {
1026
1147
  return status != null && /error|fail|cancel/i.test(status);
1027
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
+ }
1028
1154
  var GeminiFamilyAdapter = class {
1029
1155
  harness;
1030
1156
  aliases;
@@ -1101,7 +1227,26 @@ var GeminiFamilyAdapter = class {
1101
1227
  const mid = m.id ?? `m${step}`;
1102
1228
  const ts = iso(m.timestamp, start);
1103
1229
  const llmId = `llm:${mid}`;
1104
- const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content ?? "");
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
+ }
1105
1250
  spans.push(
1106
1251
  span({
1107
1252
  traceId,
@@ -1116,7 +1261,7 @@ var GeminiFamilyAdapter = class {
1116
1261
  inputTokens: m.tokens?.input ?? null,
1117
1262
  outputTokens: m.tokens?.output ?? null,
1118
1263
  step,
1119
- content: content ? content.slice(0, 8e3) : null
1264
+ content: textOf6(m.content) || null
1120
1265
  })
1121
1266
  );
1122
1267
  step += 1;
@@ -1159,6 +1304,10 @@ var SERVICE7 = "opencode";
1159
1304
  function isoOf(ms) {
1160
1305
  return new Date(ms ?? 0).toISOString();
1161
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);
1310
+ }
1162
1311
  var OpencodeAdapter = class {
1163
1312
  harness = "opencode";
1164
1313
  storage() {
@@ -1224,6 +1373,19 @@ var OpencodeAdapter = class {
1224
1373
  for (const { msg } of messages) {
1225
1374
  const mid = msg.id ?? `m${step}`;
1226
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);
1227
1389
  spans.push(
1228
1390
  span({
1229
1391
  traceId,
@@ -1238,24 +1400,29 @@ var OpencodeAdapter = class {
1238
1400
  model: msg.modelID ?? null,
1239
1401
  inputTokens: msg.tokens?.input ?? null,
1240
1402
  outputTokens: msg.tokens?.output ?? null,
1241
- step
1403
+ step,
1404
+ content: turnText || null
1242
1405
  })
1243
1406
  );
1244
1407
  step += 1;
1245
- const pdir = join8(partRoot, mid);
1246
- let partFiles;
1247
- try {
1248
- partFiles = (await readdir8(pdir)).filter((f) => f.endsWith(".json"));
1249
- } catch {
1250
- continue;
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;
1251
1424
  }
1252
- for (const pf of partFiles) {
1253
- let part;
1254
- try {
1255
- part = JSON.parse(await readFile8(join8(pdir, pf), "utf8"));
1256
- } catch {
1257
- continue;
1258
- }
1425
+ for (const part of parts) {
1259
1426
  if (part.type !== "tool" || !part.tool) continue;
1260
1427
  const err = part.state?.status === "error";
1261
1428
  spans.push(
@@ -1305,9 +1472,10 @@ function isToolBlock(b) {
1305
1472
  function isToolResultBlock(b) {
1306
1473
  return typeof b.type === "string" && /tool[_-]?result|tool[_-]?output/i.test(b.type);
1307
1474
  }
1308
- function textOf(content) {
1475
+ var CONTENT_CAP8 = 8e3;
1476
+ function textOf8(content) {
1309
1477
  if (!content) return "";
1310
- 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);
1311
1479
  }
1312
1480
  var PiAdapter = class {
1313
1481
  harness = "pi";
@@ -1375,27 +1543,48 @@ var PiAdapter = class {
1375
1543
  const mid = l.id ?? `m${step}`;
1376
1544
  const msg = l.message;
1377
1545
  const llmId = `llm:${mid}`;
1378
- const errored = !!msg.errorMessage;
1379
- spans.push(
1380
- span({
1381
- traceId,
1382
- spanId: llmId,
1383
- parentSpanId: rootId,
1384
- name: `message.${msg.role ?? "unknown"}`,
1385
- kind: "LLM",
1386
- startTime: ts,
1387
- status: errored ? "ERROR" : "OK",
1388
- statusMessage: errored ? msg.errorMessage.slice(0, 500) : void 0,
1389
- service: SERVICE8,
1390
- agent: SERVICE8,
1391
- model: msg.model ?? null,
1392
- inputTokens: msg.usage?.input ?? null,
1393
- outputTokens: msg.usage?.output ?? null,
1394
- step,
1395
- content: textOf(msg.content).slice(0, 8e3) || null
1396
- })
1397
- );
1398
- step += 1;
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
+ }
1399
1588
  for (const b of msg.content ?? []) {
1400
1589
  if (isToolBlock(b)) {
1401
1590
  const name = b.toolName ?? b.name ?? "tool";
@@ -1447,7 +1636,7 @@ function parseLines6(raw) {
1447
1636
  }
1448
1637
  return out;
1449
1638
  }
1450
- function textOf2(parts) {
1639
+ function textOf9(parts) {
1451
1640
  if (!parts) return "";
1452
1641
  return parts.filter((p) => typeof p.text === "string").map((p) => p.text).join("");
1453
1642
  }
@@ -1529,7 +1718,7 @@ var QwenAdapter = class {
1529
1718
  inputTokens: r.usageMetadata?.promptTokenCount ?? null,
1530
1719
  outputTokens: r.usageMetadata?.candidatesTokenCount ?? null,
1531
1720
  step,
1532
- content: textOf2(r.message?.parts).slice(0, 8e3) || null
1721
+ content: textOf9(r.message?.parts).slice(0, 8e3) || null
1533
1722
  })
1534
1723
  );
1535
1724
  lastLlm = llmId;
@@ -1556,6 +1745,25 @@ var QwenAdapter = class {
1556
1745
  openToolsByName.set(name, q);
1557
1746
  step += 1;
1558
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
+ }
1559
1767
  } else if (r.type === "tool_result") {
1560
1768
  const err = r.toolCallResult?.status === "error" || r.toolCallResult?.error != null;
1561
1769
  for (const p of r.message?.parts ?? []) {
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  selectAdapters,
11
11
  watchSessions,
12
12
  writeOtlpFile
13
- } from "./chunk-6NILXLUI.js";
13
+ } from "./chunk-33NBMIH4.js";
14
14
 
15
15
  // src/cli.ts
16
16
  import { stat, writeFile } from "fs/promises";
package/dist/index.js CHANGED
@@ -37,7 +37,7 @@ import {
37
37
  uploadKey,
38
38
  watchSessions,
39
39
  writeOtlpFile
40
- } from "./chunk-6NILXLUI.js";
40
+ } from "./chunk-33NBMIH4.js";
41
41
 
42
42
  // src/collect.ts
43
43
  async function collectSessions(opts = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/traces",
3
- "version": "0.3.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",