@tangle-network/traces 0.9.5 → 0.9.7
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-S4TEBT7Q.js → chunk-NHXPTFKC.js} +296 -108
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -920,10 +920,19 @@ function classifyReaction(text) {
|
|
|
920
920
|
var CORRECTIVE_REACTIONS = ["correction", "frustration", "jargon", "structure"];
|
|
921
921
|
|
|
922
922
|
// src/adapters/claude.ts
|
|
923
|
+
import { createHash as createHash4 } from "crypto";
|
|
923
924
|
import { readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
924
925
|
import { homedir as homedir2 } from "os";
|
|
925
|
-
import { basename as basename2, join as join2 } from "path";
|
|
926
|
+
import { basename as basename2, join as join2, relative } from "path";
|
|
927
|
+
import {
|
|
928
|
+
applyLlmSpanOtlpAttributes as applyLlmSpanOtlpAttributes2,
|
|
929
|
+
LLM_CACHED_TOKENS,
|
|
930
|
+
LLM_CACHE_WRITE_TOKENS,
|
|
931
|
+
LLM_INPUT_TOKENS,
|
|
932
|
+
LLM_OUTPUT_TOKENS
|
|
933
|
+
} from "@tangle-network/agent-eval/trace-attributes";
|
|
926
934
|
var SERVICE2 = "claude-code";
|
|
935
|
+
var EPOCH = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
927
936
|
function asBlocks(content) {
|
|
928
937
|
return Array.isArray(content) ? content : [];
|
|
929
938
|
}
|
|
@@ -941,61 +950,181 @@ function stringifyToolResult(content) {
|
|
|
941
950
|
return "";
|
|
942
951
|
}
|
|
943
952
|
function createClaudeStream(startStep) {
|
|
944
|
-
return {
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
+
return {
|
|
954
|
+
spans: [],
|
|
955
|
+
toolSpanByUseId: /* @__PURE__ */ new Map(),
|
|
956
|
+
llmSpanByMessageId: /* @__PURE__ */ new Map(),
|
|
957
|
+
llmContentByMessageId: /* @__PURE__ */ new Map(),
|
|
958
|
+
toolCountByMessageId: /* @__PURE__ */ new Map(),
|
|
959
|
+
step: startStep,
|
|
960
|
+
sawUserTurn: false
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
var ClaudeEventConflictError = class extends Error {
|
|
964
|
+
sourcePath;
|
|
965
|
+
eventId;
|
|
966
|
+
constructor(sourcePath, eventId) {
|
|
967
|
+
super(`${sourcePath}: Claude transcript event ${JSON.stringify(eventId)} has conflicting payloads`);
|
|
968
|
+
this.name = "ClaudeEventConflictError";
|
|
969
|
+
this.sourcePath = sourcePath;
|
|
970
|
+
this.eventId = eventId;
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
function projectToolResult(toolUseId, isError, output) {
|
|
974
|
+
return {
|
|
975
|
+
toolUseId,
|
|
976
|
+
isError,
|
|
977
|
+
attributes: toolIoAttributes({ output }),
|
|
978
|
+
message: stringifyToolResult(output).slice(0, 500)
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
function projectClaudeEvent(event) {
|
|
982
|
+
const timestamp = event.timestamp ?? EPOCH;
|
|
983
|
+
if (event.type === "assistant" && event.message) {
|
|
984
|
+
const tools = [];
|
|
985
|
+
for (const block of asBlocks(event.message.content)) {
|
|
986
|
+
if (block.type !== "tool_use" || !block.name) continue;
|
|
987
|
+
tools.push({ id: block.id || null, name: block.name, attributes: toolIoAttributes({ input: block.input }) });
|
|
988
|
+
}
|
|
989
|
+
return {
|
|
990
|
+
kind: "assistant",
|
|
991
|
+
timestamp,
|
|
992
|
+
messageId: event.message.id ?? null,
|
|
993
|
+
model: event.message.model ?? null,
|
|
994
|
+
inputTokens: event.message.usage?.input_tokens ?? null,
|
|
995
|
+
outputTokens: event.message.usage?.output_tokens ?? null,
|
|
996
|
+
cachedInputTokens: event.message.usage?.cache_read_input_tokens ?? null,
|
|
997
|
+
cacheWriteInputTokens: event.message.usage?.cache_creation_input_tokens ?? null,
|
|
998
|
+
content: textOf2(event.message.content) || null,
|
|
999
|
+
tools
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
if (event.type === "user" && event.message) {
|
|
1003
|
+
const results = [];
|
|
1004
|
+
for (const block of asBlocks(event.message.content)) {
|
|
1005
|
+
if (block.type !== "tool_result" || !block.tool_use_id) continue;
|
|
1006
|
+
results.push(projectToolResult(block.tool_use_id, block.is_error === true, block.content));
|
|
1007
|
+
}
|
|
1008
|
+
const prompt = textOf2(event.message.content);
|
|
1009
|
+
return {
|
|
1010
|
+
kind: "user",
|
|
1011
|
+
timestamp,
|
|
1012
|
+
prompt: prompt || null,
|
|
1013
|
+
...prompt ? { isSidechain: event.isSidechain === true, userType: event.userType ?? null } : {},
|
|
1014
|
+
results
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
if (event.type === "attachment" && event.attachment?.type === "tool_result" && event.attachment.toolUseID) {
|
|
1018
|
+
return {
|
|
1019
|
+
kind: "attachment",
|
|
1020
|
+
timestamp,
|
|
1021
|
+
result: projectToolResult(
|
|
1022
|
+
event.attachment.toolUseID,
|
|
1023
|
+
typeof event.attachment.exitCode === "number" && event.attachment.exitCode !== 0,
|
|
1024
|
+
event.attachment.stderr ?? ""
|
|
1025
|
+
)
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
return { kind: "ignored" };
|
|
1029
|
+
}
|
|
1030
|
+
function fingerprintClaudeEvent(event) {
|
|
1031
|
+
return createHash4("sha256").update(JSON.stringify(event)).digest("hex");
|
|
1032
|
+
}
|
|
1033
|
+
function consumeDistinctClaudeEvent(event, ctx, state, seen, sourcePath) {
|
|
1034
|
+
const projection = projectClaudeEvent(event);
|
|
1035
|
+
if (event.uuid) {
|
|
1036
|
+
const fingerprint = fingerprintClaudeEvent(projection);
|
|
1037
|
+
const previous = seen.get(event.uuid);
|
|
1038
|
+
if (previous !== void 0) {
|
|
1039
|
+
if (previous !== fingerprint) {
|
|
1040
|
+
throw new ClaudeEventConflictError(sourcePath, event.uuid);
|
|
1041
|
+
}
|
|
1042
|
+
return false;
|
|
1043
|
+
}
|
|
1044
|
+
seen.set(event.uuid, fingerprint);
|
|
1045
|
+
}
|
|
1046
|
+
consumeClaudeEvent(projection, event.uuid ?? `step${state.step}`, ctx, state);
|
|
1047
|
+
return true;
|
|
1048
|
+
}
|
|
1049
|
+
function consumeClaudeEvent(event, uid, ctx, state) {
|
|
1050
|
+
if (event.kind === "assistant") {
|
|
1051
|
+
const messageId = event.messageId ?? uid;
|
|
1052
|
+
let llmSpan = state.llmSpanByMessageId.get(messageId);
|
|
1053
|
+
if (!llmSpan) {
|
|
1054
|
+
llmSpan = span({
|
|
953
1055
|
traceId: ctx.traceId,
|
|
954
|
-
spanId:
|
|
1056
|
+
spanId: `${ctx.idPrefix}${uid}`,
|
|
955
1057
|
parentSpanId: ctx.rootParent,
|
|
956
1058
|
name: "llm.turn",
|
|
957
1059
|
kind: "LLM",
|
|
958
|
-
startTime:
|
|
1060
|
+
startTime: event.timestamp,
|
|
959
1061
|
service: SERVICE2,
|
|
960
1062
|
agent: ctx.agent,
|
|
961
|
-
model:
|
|
962
|
-
inputTokens:
|
|
963
|
-
outputTokens:
|
|
964
|
-
cachedInputTokens:
|
|
965
|
-
cacheWriteInputTokens:
|
|
966
|
-
step: state.step
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1063
|
+
model: event.model,
|
|
1064
|
+
inputTokens: event.inputTokens,
|
|
1065
|
+
outputTokens: event.outputTokens,
|
|
1066
|
+
cachedInputTokens: event.cachedInputTokens,
|
|
1067
|
+
cacheWriteInputTokens: event.cacheWriteInputTokens,
|
|
1068
|
+
step: state.step
|
|
1069
|
+
});
|
|
1070
|
+
state.spans.push(llmSpan);
|
|
1071
|
+
state.llmSpanByMessageId.set(messageId, llmSpan);
|
|
1072
|
+
state.step += 1;
|
|
1073
|
+
} else {
|
|
1074
|
+
includeSpanTimestamp(llmSpan, event.timestamp);
|
|
1075
|
+
}
|
|
1076
|
+
applyLlmSpanOtlpAttributes2(llmSpan.attributes, {
|
|
1077
|
+
model: event.model ?? void 0,
|
|
1078
|
+
inputTokens: maxCapturedUsage(
|
|
1079
|
+
llmSpan.attributes[LLM_INPUT_TOKENS],
|
|
1080
|
+
event.inputTokens
|
|
1081
|
+
),
|
|
1082
|
+
outputTokens: maxCapturedUsage(
|
|
1083
|
+
llmSpan.attributes[LLM_OUTPUT_TOKENS],
|
|
1084
|
+
event.outputTokens
|
|
1085
|
+
),
|
|
1086
|
+
cachedTokens: maxCapturedUsage(
|
|
1087
|
+
llmSpan.attributes[LLM_CACHED_TOKENS],
|
|
1088
|
+
event.cachedInputTokens
|
|
1089
|
+
),
|
|
1090
|
+
cacheWriteTokens: maxCapturedUsage(
|
|
1091
|
+
llmSpan.attributes[LLM_CACHE_WRITE_TOKENS],
|
|
1092
|
+
event.cacheWriteInputTokens
|
|
1093
|
+
)
|
|
1094
|
+
});
|
|
1095
|
+
mergeMessageContent(llmSpan, messageId, event.content, state);
|
|
1096
|
+
for (const tool of event.tools) {
|
|
1097
|
+
const existingTool = tool.id ? state.toolSpanByUseId.get(tool.id) : void 0;
|
|
1098
|
+
if (existingTool) {
|
|
1099
|
+
Object.assign(existingTool.attributes, tool.attributes);
|
|
1100
|
+
includeSpanTimestamp(existingTool, event.timestamp);
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
const toolIdx = state.toolCountByMessageId.get(messageId) ?? 0;
|
|
974
1104
|
const toolSpan = span({
|
|
975
1105
|
traceId: ctx.traceId,
|
|
976
|
-
spanId: `${
|
|
977
|
-
parentSpanId:
|
|
978
|
-
name: `tool.${
|
|
1106
|
+
spanId: `${llmSpan.span_id}:tool:${toolIdx}`,
|
|
1107
|
+
parentSpanId: llmSpan.span_id,
|
|
1108
|
+
name: `tool.${tool.name}`,
|
|
979
1109
|
kind: "TOOL",
|
|
980
|
-
startTime:
|
|
1110
|
+
startTime: event.timestamp,
|
|
981
1111
|
service: SERVICE2,
|
|
982
1112
|
agent: ctx.agent,
|
|
983
|
-
tool:
|
|
1113
|
+
tool: tool.name,
|
|
984
1114
|
step: state.step,
|
|
985
|
-
extra:
|
|
1115
|
+
extra: tool.attributes
|
|
986
1116
|
});
|
|
987
1117
|
state.spans.push(toolSpan);
|
|
988
|
-
if (
|
|
989
|
-
toolIdx
|
|
1118
|
+
if (tool.id) state.toolSpanByUseId.set(tool.id, toolSpan);
|
|
1119
|
+
state.toolCountByMessageId.set(messageId, toolIdx + 1);
|
|
990
1120
|
state.step += 1;
|
|
991
1121
|
}
|
|
992
|
-
} else if (
|
|
993
|
-
|
|
994
|
-
if (prompt) {
|
|
1122
|
+
} else if (event.kind === "user") {
|
|
1123
|
+
if (event.prompt) {
|
|
995
1124
|
const actor = claudeActor({
|
|
996
|
-
text: prompt,
|
|
997
|
-
isSidechain:
|
|
998
|
-
userType:
|
|
1125
|
+
text: event.prompt,
|
|
1126
|
+
isSidechain: event.isSidechain,
|
|
1127
|
+
userType: event.userType ?? null,
|
|
999
1128
|
isFirstUserTurn: !state.sawUserTurn
|
|
1000
1129
|
});
|
|
1001
1130
|
state.sawUserTurn = true;
|
|
@@ -1004,41 +1133,95 @@ function consumeClaudeEvent(ev, ctx, state) {
|
|
|
1004
1133
|
traceId: ctx.traceId,
|
|
1005
1134
|
spanId: `${ctx.idPrefix}${uid}:user`,
|
|
1006
1135
|
parentSpanId: ctx.rootParent,
|
|
1007
|
-
startTime:
|
|
1136
|
+
startTime: event.timestamp,
|
|
1008
1137
|
service: SERVICE2,
|
|
1009
1138
|
agent: ctx.agent,
|
|
1010
1139
|
step: state.step,
|
|
1011
|
-
content: prompt,
|
|
1140
|
+
content: event.prompt,
|
|
1012
1141
|
actor
|
|
1013
1142
|
})
|
|
1014
1143
|
);
|
|
1015
1144
|
state.step += 1;
|
|
1016
1145
|
}
|
|
1017
|
-
for (const
|
|
1018
|
-
|
|
1019
|
-
backfillResult(
|
|
1020
|
-
state.toolSpanByUseId.get(block.tool_use_id),
|
|
1021
|
-
ts,
|
|
1022
|
-
block.is_error === true,
|
|
1023
|
-
block.content
|
|
1024
|
-
);
|
|
1146
|
+
for (const result of event.results) {
|
|
1147
|
+
backfillResult(state.toolSpanByUseId.get(result.toolUseId), event.timestamp, result);
|
|
1025
1148
|
}
|
|
1026
|
-
} else if (
|
|
1027
|
-
|
|
1028
|
-
backfillResult(state.toolSpanByUseId.get(ev.attachment.toolUseID), ts, err, ev.attachment.stderr ?? "");
|
|
1149
|
+
} else if (event.kind === "attachment") {
|
|
1150
|
+
backfillResult(state.toolSpanByUseId.get(event.result.toolUseId), event.timestamp, event.result);
|
|
1029
1151
|
}
|
|
1030
1152
|
}
|
|
1153
|
+
function maxCapturedUsage(current, next) {
|
|
1154
|
+
const captured = typeof current === "number" && Number.isFinite(current) ? current : void 0;
|
|
1155
|
+
if (next == null) return captured;
|
|
1156
|
+
return captured === void 0 ? next : Math.max(captured, next);
|
|
1157
|
+
}
|
|
1158
|
+
function includeSpanTimestamp(target, timestamp) {
|
|
1159
|
+
if (!timestamp) return;
|
|
1160
|
+
const time = Date.parse(timestamp);
|
|
1161
|
+
if (!Number.isFinite(time)) return;
|
|
1162
|
+
const start = Date.parse(target.start_time);
|
|
1163
|
+
const end = Date.parse(target.end_time);
|
|
1164
|
+
if (!Number.isFinite(start) || time < start) target.start_time = timestamp;
|
|
1165
|
+
if (!Number.isFinite(end) || time > end) target.end_time = timestamp;
|
|
1166
|
+
}
|
|
1167
|
+
function mergeMessageContent(llmSpan, messageId, content, state) {
|
|
1168
|
+
if (!content) return;
|
|
1169
|
+
const parts = state.llmContentByMessageId.get(messageId) ?? /* @__PURE__ */ new Set();
|
|
1170
|
+
if (parts.has(content)) return;
|
|
1171
|
+
parts.add(content);
|
|
1172
|
+
state.llmContentByMessageId.set(messageId, parts);
|
|
1173
|
+
llmSpan.attributes.content = capText([...parts].join("\n"));
|
|
1174
|
+
}
|
|
1031
1175
|
function finishClaudeStream(state) {
|
|
1032
1176
|
return { spans: state.spans, toolSpanByUseId: state.toolSpanByUseId, nextStep: state.step };
|
|
1033
1177
|
}
|
|
1034
|
-
function
|
|
1178
|
+
function setRootTimeBounds(root, spans) {
|
|
1179
|
+
let firstTimestamp;
|
|
1180
|
+
let lastTimestamp;
|
|
1181
|
+
for (const item of spans) {
|
|
1182
|
+
if (item === root) continue;
|
|
1183
|
+
for (const source of [item.start_time, item.end_time]) {
|
|
1184
|
+
if (!source) continue;
|
|
1185
|
+
const value = Date.parse(source);
|
|
1186
|
+
if (!Number.isFinite(value)) continue;
|
|
1187
|
+
if (!firstTimestamp || value < firstTimestamp.value) firstTimestamp = { value, source };
|
|
1188
|
+
if (!lastTimestamp || value > lastTimestamp.value) lastTimestamp = { value, source };
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
if (firstTimestamp) root.start_time = firstTimestamp.source;
|
|
1192
|
+
if (lastTimestamp) root.end_time = lastTimestamp.source;
|
|
1193
|
+
}
|
|
1194
|
+
function backfillResult(s, endTime, result) {
|
|
1035
1195
|
if (!s) return;
|
|
1036
1196
|
s.end_time = endTime;
|
|
1037
|
-
const code = isError ? "ERROR" : "OK";
|
|
1197
|
+
const code = result.isError ? "ERROR" : "OK";
|
|
1038
1198
|
s.status = { code };
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1199
|
+
Object.assign(s.attributes, result.attributes);
|
|
1200
|
+
if (result.isError && result.message) s.status.message = result.message;
|
|
1201
|
+
}
|
|
1202
|
+
async function listSubagentFiles(root) {
|
|
1203
|
+
const pending = [root];
|
|
1204
|
+
const files = [];
|
|
1205
|
+
while (pending.length > 0) {
|
|
1206
|
+
const dir = pending.pop();
|
|
1207
|
+
if (!dir) continue;
|
|
1208
|
+
let entries;
|
|
1209
|
+
try {
|
|
1210
|
+
entries = await readdir3(dir, { withFileTypes: true });
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
if (isMissingPathError(error)) continue;
|
|
1213
|
+
throw error;
|
|
1214
|
+
}
|
|
1215
|
+
for (const entry of entries) {
|
|
1216
|
+
const path = join2(dir, entry.name);
|
|
1217
|
+
if (entry.isDirectory()) {
|
|
1218
|
+
pending.push(path);
|
|
1219
|
+
} else if (entry.isFile() && /^agent-.*\.jsonl$/.test(entry.name)) {
|
|
1220
|
+
files.push(path);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
return files.sort();
|
|
1042
1225
|
}
|
|
1043
1226
|
var ClaudeAdapter = class {
|
|
1044
1227
|
harness = "claude-code";
|
|
@@ -1096,17 +1279,10 @@ var ClaudeAdapter = class {
|
|
|
1096
1279
|
};
|
|
1097
1280
|
const state = createClaudeStream(ctx.startStep);
|
|
1098
1281
|
let discoveredTraceId;
|
|
1099
|
-
|
|
1100
|
-
let lastTimestamp;
|
|
1101
|
-
let sawEvent = false;
|
|
1282
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1102
1283
|
for await (const event of readJsonl(ref.path, sessionJsonlOptions(ref, options))) {
|
|
1103
|
-
if (!sawEvent) {
|
|
1104
|
-
firstTimestamp = event.timestamp;
|
|
1105
|
-
sawEvent = true;
|
|
1106
|
-
}
|
|
1107
|
-
lastTimestamp = event.timestamp;
|
|
1108
1284
|
if (!discoveredTraceId && event.sessionId) discoveredTraceId = event.sessionId;
|
|
1109
|
-
|
|
1285
|
+
if (!consumeDistinctClaudeEvent(event, ctx, state, seen, ref.path)) continue;
|
|
1110
1286
|
}
|
|
1111
1287
|
const main = finishClaudeStream(state);
|
|
1112
1288
|
const traceId = discoveredTraceId ?? sourceTraceId;
|
|
@@ -1115,63 +1291,75 @@ var ClaudeAdapter = class {
|
|
|
1115
1291
|
item.trace_id = traceId;
|
|
1116
1292
|
if (item.parent_span_id === sourceRootId) item.parent_span_id = rootId;
|
|
1117
1293
|
}
|
|
1118
|
-
const
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
})
|
|
1130
|
-
];
|
|
1294
|
+
const root = span({
|
|
1295
|
+
traceId,
|
|
1296
|
+
spanId: rootId,
|
|
1297
|
+
parentSpanId: null,
|
|
1298
|
+
name: "session",
|
|
1299
|
+
kind: "AGENT",
|
|
1300
|
+
startTime: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
1301
|
+
service: SERVICE2,
|
|
1302
|
+
agent: SERVICE2
|
|
1303
|
+
});
|
|
1304
|
+
const spans = [root];
|
|
1131
1305
|
appendAll(spans, main.spans);
|
|
1132
1306
|
await this.foldSubagents(ref, traceId, main, spans, options);
|
|
1307
|
+
setRootTimeBounds(root, spans);
|
|
1133
1308
|
return spans;
|
|
1134
1309
|
}
|
|
1135
|
-
/** Parse
|
|
1310
|
+
/** Parse nested `subagents/.../agent-*.jsonl`, parenting each under its Agent call. */
|
|
1136
1311
|
async foldSubagents(ref, traceId, main, out, options) {
|
|
1137
1312
|
const subDir = join2(ref.path.replace(/\.jsonl$/, ""), "subagents");
|
|
1138
|
-
|
|
1139
|
-
try {
|
|
1140
|
-
files = await readdir3(subDir);
|
|
1141
|
-
} catch (error) {
|
|
1142
|
-
if (isMissingPathError(error)) return;
|
|
1143
|
-
throw error;
|
|
1144
|
-
}
|
|
1313
|
+
const files = await listSubagentFiles(subDir);
|
|
1145
1314
|
let step = main.nextStep;
|
|
1315
|
+
const parsedAgents = [];
|
|
1146
1316
|
for (const file of files) {
|
|
1147
|
-
|
|
1148
|
-
const
|
|
1149
|
-
const metaPath = join2(subDir, `${hash2}.meta.json`);
|
|
1317
|
+
const sourceKey = relative(subDir, file).replace(/\.jsonl$/, "").split(/[\\/]/).map((segment) => encodeURIComponent(segment)).join(":");
|
|
1318
|
+
const metaPath = file.replace(/\.jsonl$/, ".meta.json");
|
|
1150
1319
|
let meta = {};
|
|
1151
1320
|
try {
|
|
1152
1321
|
meta = await readJsonFile(metaPath);
|
|
1153
1322
|
} catch (error) {
|
|
1154
1323
|
if (!isMissingJsonSource(error)) throw error;
|
|
1155
1324
|
}
|
|
1156
|
-
const parent = meta.toolUseId && main.toolSpanByUseId.get(meta.toolUseId)?.span_id || `root:${traceId}`;
|
|
1157
1325
|
const ctx = {
|
|
1158
1326
|
traceId,
|
|
1159
1327
|
agent: meta.agentType ? `subagent:${meta.agentType}` : "subagent",
|
|
1160
1328
|
startStep: step,
|
|
1161
|
-
idPrefix: `${
|
|
1162
|
-
rootParent:
|
|
1329
|
+
idPrefix: `${sourceKey}:`,
|
|
1330
|
+
rootParent: `root:${traceId}`
|
|
1163
1331
|
};
|
|
1164
1332
|
const state = createClaudeStream(ctx.startStep);
|
|
1333
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1165
1334
|
for await (const event of readJsonl(
|
|
1166
|
-
|
|
1335
|
+
file,
|
|
1167
1336
|
sessionJsonlOptions(ref, options)
|
|
1168
1337
|
)) {
|
|
1169
|
-
|
|
1338
|
+
consumeDistinctClaudeEvent(event, ctx, state, seen, file);
|
|
1170
1339
|
}
|
|
1171
1340
|
const parsed = finishClaudeStream(state);
|
|
1172
|
-
|
|
1341
|
+
parsedAgents.push({
|
|
1342
|
+
agentId: basename2(file, ".jsonl").replace(/^agent-/, ""),
|
|
1343
|
+
meta,
|
|
1344
|
+
parsed
|
|
1345
|
+
});
|
|
1173
1346
|
step = parsed.nextStep;
|
|
1174
1347
|
}
|
|
1348
|
+
const byAgentId = /* @__PURE__ */ new Map();
|
|
1349
|
+
for (const agent of parsedAgents) {
|
|
1350
|
+
if (byAgentId.has(agent.agentId)) {
|
|
1351
|
+
throw new Error(`Duplicate Claude subagent id: ${agent.agentId}`);
|
|
1352
|
+
}
|
|
1353
|
+
byAgentId.set(agent.agentId, agent.parsed);
|
|
1354
|
+
}
|
|
1355
|
+
for (const agent of parsedAgents) {
|
|
1356
|
+
const parentTools = agent.meta.parentAgentId ? byAgentId.get(agent.meta.parentAgentId)?.toolSpanByUseId : main.toolSpanByUseId;
|
|
1357
|
+
const parent = agent.meta.toolUseId && parentTools?.get(agent.meta.toolUseId)?.span_id || `root:${traceId}`;
|
|
1358
|
+
for (const item of agent.parsed.spans) {
|
|
1359
|
+
if (item.parent_span_id === `root:${traceId}`) item.parent_span_id = parent;
|
|
1360
|
+
}
|
|
1361
|
+
appendAll(out, agent.parsed.spans);
|
|
1362
|
+
}
|
|
1175
1363
|
}
|
|
1176
1364
|
};
|
|
1177
1365
|
|
|
@@ -3483,22 +3671,22 @@ async function applyRedactor(spans, redactor) {
|
|
|
3483
3671
|
}
|
|
3484
3672
|
|
|
3485
3673
|
// src/file-export.ts
|
|
3486
|
-
import { createHash as
|
|
3674
|
+
import { createHash as createHash5 } from "crypto";
|
|
3487
3675
|
import { mkdtemp as mkdtemp2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
3488
3676
|
import { tmpdir as tmpdir2 } from "os";
|
|
3489
3677
|
import { join as join13 } from "path";
|
|
3490
3678
|
import {
|
|
3491
3679
|
firstNumberAttr,
|
|
3492
3680
|
LLM_CACHED_TOKEN_ATTR_KEYS,
|
|
3493
|
-
LLM_CACHED_TOKENS,
|
|
3681
|
+
LLM_CACHED_TOKENS as LLM_CACHED_TOKENS2,
|
|
3494
3682
|
LLM_CACHE_WRITE_TOKEN_ATTR_KEYS,
|
|
3495
|
-
LLM_CACHE_WRITE_TOKENS,
|
|
3683
|
+
LLM_CACHE_WRITE_TOKENS as LLM_CACHE_WRITE_TOKENS2,
|
|
3496
3684
|
LLM_COST_ATTR_KEYS,
|
|
3497
3685
|
LLM_COST_USD,
|
|
3498
3686
|
LLM_INPUT_TOKEN_ATTR_KEYS,
|
|
3499
|
-
LLM_INPUT_TOKENS,
|
|
3687
|
+
LLM_INPUT_TOKENS as LLM_INPUT_TOKENS2,
|
|
3500
3688
|
LLM_OUTPUT_TOKEN_ATTR_KEYS,
|
|
3501
|
-
LLM_OUTPUT_TOKENS,
|
|
3689
|
+
LLM_OUTPUT_TOKENS as LLM_OUTPUT_TOKENS2,
|
|
3502
3690
|
LLM_REASONING_TOKEN_ATTR_KEYS,
|
|
3503
3691
|
LLM_REASONING_TOKENS
|
|
3504
3692
|
} from "@tangle-network/agent-eval/trace-attributes";
|
|
@@ -3521,7 +3709,7 @@ function stableJson(value) {
|
|
|
3521
3709
|
});
|
|
3522
3710
|
}
|
|
3523
3711
|
function hashId(value, chars = 16) {
|
|
3524
|
-
return
|
|
3712
|
+
return createHash5("sha256").update(stableJson(value)).digest("hex").slice(0, chars);
|
|
3525
3713
|
}
|
|
3526
3714
|
function isoTime(value) {
|
|
3527
3715
|
if (typeof value === "string") {
|
|
@@ -3887,11 +4075,11 @@ var COMPUTED_SPAN_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
|
3887
4075
|
"tool.name",
|
|
3888
4076
|
"llm.input_tokens",
|
|
3889
4077
|
"llm.output_tokens",
|
|
3890
|
-
|
|
3891
|
-
|
|
4078
|
+
LLM_INPUT_TOKENS2,
|
|
4079
|
+
LLM_OUTPUT_TOKENS2,
|
|
3892
4080
|
LLM_REASONING_TOKENS,
|
|
3893
|
-
|
|
3894
|
-
|
|
4081
|
+
LLM_CACHED_TOKENS2,
|
|
4082
|
+
LLM_CACHE_WRITE_TOKENS2,
|
|
3895
4083
|
LLM_COST_USD,
|
|
3896
4084
|
"step",
|
|
3897
4085
|
"content"
|
|
@@ -5760,7 +5948,7 @@ async function saveReport(path, report) {
|
|
|
5760
5948
|
}
|
|
5761
5949
|
|
|
5762
5950
|
// src/live.ts
|
|
5763
|
-
import { createHash as
|
|
5951
|
+
import { createHash as createHash6 } from "crypto";
|
|
5764
5952
|
var VERIFY_RE = /\b(test|tests|vitest|jest|pytest|go test|cargo test|tsc|typecheck|lint|biome|eslint|build|check:invariants|preflight)\b/i;
|
|
5765
5953
|
var CHANGE_RE = /\b(apply_patch|edit|write|create|delete|rename|replace|update|git apply|sed -i|perl -pi|tee\s|cat\s+>)/i;
|
|
5766
5954
|
var CLAIM_DONE_RE = /\b(done|fixed|complete|completed|implemented|merged|verified|all green|tests pass|ready|works now)\b/i;
|
|
@@ -5780,7 +5968,7 @@ var sleep = (ms, signal) => new Promise((resolve2) => {
|
|
|
5780
5968
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
5781
5969
|
});
|
|
5782
5970
|
function hash(parts) {
|
|
5783
|
-
return
|
|
5971
|
+
return createHash6("sha256").update(parts.join("\0")).digest("hex").slice(0, 16);
|
|
5784
5972
|
}
|
|
5785
5973
|
function asString(value) {
|
|
5786
5974
|
if (typeof value === "string" && value.trim()) return value;
|
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -399,7 +399,7 @@ declare function stampSpanWorkdirRepoAttrs(spans: readonly OtlpSpan[]): Promise<
|
|
|
399
399
|
* (token trajectory) and `message.content[]` tool_use blocks; tool results
|
|
400
400
|
* arrive as `tool_result` blocks in the following user message or as a
|
|
401
401
|
* `tool_result` attachment. Subagent runs live in a sibling
|
|
402
|
-
* `<session>/subagents
|
|
402
|
+
* nested `<session>/subagents/.../agent-*.jsonl` files with a `.meta.json` carrying the
|
|
403
403
|
* spawning `toolUseId`, so we parent each subagent under its `Agent` call.
|
|
404
404
|
*
|
|
405
405
|
* Shared by the claudish / openclaw / nanoclaw forks via aliases — they
|
|
@@ -412,7 +412,7 @@ declare class ClaudeAdapter implements HarnessTraceAdapter {
|
|
|
412
412
|
private root;
|
|
413
413
|
locate(opts?: LocateOptions): Promise<SessionRef[]>;
|
|
414
414
|
parse(ref: SessionRef, options?: ParseOptions): Promise<OtlpSpan[]>;
|
|
415
|
-
/** Parse
|
|
415
|
+
/** Parse nested `subagents/.../agent-*.jsonl`, parenting each under its Agent call. */
|
|
416
416
|
private foldSubagents;
|
|
417
417
|
}
|
|
418
418
|
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/traces",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7",
|
|
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",
|