@tracemarketplace/shared 0.0.6 → 0.0.9
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/chunker.d.ts.map +1 -1
- package/dist/chunker.js +14 -2
- package/dist/chunker.js.map +1 -1
- package/dist/extractor-claude-code.test.d.ts +2 -0
- package/dist/extractor-claude-code.test.d.ts.map +1 -0
- package/dist/extractor-claude-code.test.js +290 -0
- package/dist/extractor-claude-code.test.js.map +1 -0
- package/dist/extractor-codex.test.d.ts +2 -0
- package/dist/extractor-codex.test.d.ts.map +1 -0
- package/dist/extractor-codex.test.js +212 -0
- package/dist/extractor-codex.test.js.map +1 -0
- package/dist/extractor-cursor.test.d.ts +2 -0
- package/dist/extractor-cursor.test.d.ts.map +1 -0
- package/dist/extractor-cursor.test.js +120 -0
- package/dist/extractor-cursor.test.js.map +1 -0
- package/dist/extractors/claude-code.d.ts.map +1 -1
- package/dist/extractors/claude-code.js +172 -73
- package/dist/extractors/claude-code.js.map +1 -1
- package/dist/extractors/codex.d.ts.map +1 -1
- package/dist/extractors/codex.js +63 -35
- package/dist/extractors/codex.js.map +1 -1
- package/dist/extractors/common.d.ts +14 -0
- package/dist/extractors/common.d.ts.map +1 -0
- package/dist/extractors/common.js +100 -0
- package/dist/extractors/common.js.map +1 -0
- package/dist/extractors/cursor.d.ts.map +1 -1
- package/dist/extractors/cursor.js +205 -45
- package/dist/extractors/cursor.js.map +1 -1
- package/dist/hash.d.ts.map +1 -1
- package/dist/hash.js +35 -2
- package/dist/hash.js.map +1 -1
- package/dist/hash.test.js +29 -2
- package/dist/hash.test.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/redact.d.ts +12 -0
- package/dist/redact.d.ts.map +1 -1
- package/dist/redact.js +120 -38
- package/dist/redact.js.map +1 -1
- package/dist/redact.test.d.ts +2 -0
- package/dist/redact.test.d.ts.map +1 -0
- package/dist/redact.test.js +96 -0
- package/dist/redact.test.js.map +1 -0
- package/dist/turn-actors.d.ts +3 -0
- package/dist/turn-actors.d.ts.map +1 -0
- package/dist/turn-actors.js +57 -0
- package/dist/turn-actors.js.map +1 -0
- package/dist/turn-actors.test.d.ts +2 -0
- package/dist/turn-actors.test.d.ts.map +1 -0
- package/dist/turn-actors.test.js +65 -0
- package/dist/turn-actors.test.js.map +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +4 -0
- package/dist/utils.js.map +1 -1
- package/dist/validators.d.ts +24 -0
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +3 -0
- package/dist/validators.js.map +1 -1
- package/package.json +5 -1
- package/src/chunker.ts +17 -2
- package/src/extractor-claude-code.test.ts +326 -0
- package/src/extractor-codex.test.ts +225 -0
- package/src/extractor-cursor.test.ts +141 -0
- package/src/extractors/claude-code.ts +180 -69
- package/src/extractors/codex.ts +69 -38
- package/src/extractors/common.ts +139 -0
- package/src/extractors/cursor.ts +294 -52
- package/src/hash.test.ts +31 -2
- package/src/hash.ts +38 -3
- package/src/index.ts +1 -0
- package/src/redact.test.ts +100 -0
- package/src/redact.ts +175 -58
- package/src/turn-actors.test.ts +71 -0
- package/src/turn-actors.ts +71 -0
- package/src/types.ts +6 -0
- package/src/utils.ts +3 -1
- package/src/validators.ts +3 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import Database from "better-sqlite3";
|
|
5
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
6
|
+
import { extractCursor } from "./extractors/cursor.js";
|
|
7
|
+
const tempDirs = [];
|
|
8
|
+
function makeCursorDb(sessionId, headers, values) {
|
|
9
|
+
const dir = mkdtempSync(join(tmpdir(), "tracemp-cursor-"));
|
|
10
|
+
tempDirs.push(dir);
|
|
11
|
+
const dbPath = join(dir, "state.vscdb");
|
|
12
|
+
const db = new Database(dbPath);
|
|
13
|
+
db.exec("CREATE TABLE cursorDiskKV (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
14
|
+
db.prepare("INSERT INTO cursorDiskKV (key, value) VALUES (?, ?)").run(`composerData:${sessionId}`, JSON.stringify({
|
|
15
|
+
_v: 14,
|
|
16
|
+
createdAt: Date.parse("2026-03-21T00:00:00.000Z"),
|
|
17
|
+
fullConversationHeadersOnly: headers,
|
|
18
|
+
}));
|
|
19
|
+
const insert = db.prepare("INSERT INTO cursorDiskKV (key, value) VALUES (?, ?)");
|
|
20
|
+
for (const [key, value] of values) {
|
|
21
|
+
insert.run(key, JSON.stringify(value));
|
|
22
|
+
}
|
|
23
|
+
db.close();
|
|
24
|
+
return dbPath;
|
|
25
|
+
}
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
while (tempDirs.length > 0) {
|
|
28
|
+
rmSync(tempDirs.pop(), { force: true, recursive: true });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
describe("extractCursor", () => {
|
|
32
|
+
it("supports the modern bubbleId:<session>:<bubble> storage format", async () => {
|
|
33
|
+
const sessionId = "cursor-modern";
|
|
34
|
+
const dbPath = makeCursorDb(sessionId, [
|
|
35
|
+
{ bubbleId: "user-1", type: 1 },
|
|
36
|
+
{ bubbleId: "assistant-1", type: 2 },
|
|
37
|
+
], [
|
|
38
|
+
[
|
|
39
|
+
`bubbleId:${sessionId}:user-1`,
|
|
40
|
+
{
|
|
41
|
+
type: 1,
|
|
42
|
+
text: "Help me debug the repo",
|
|
43
|
+
createdAt: "2026-03-21T00:00:01.000Z",
|
|
44
|
+
tokenCount: { inputTokens: 0, outputTokens: 0 },
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
`bubbleId:${sessionId}:assistant-1`,
|
|
49
|
+
{
|
|
50
|
+
type: 2,
|
|
51
|
+
text: "I will inspect src/main.ts",
|
|
52
|
+
thinking: { text: "Start with the entrypoint." },
|
|
53
|
+
createdAt: "2026-03-21T00:00:02.000Z",
|
|
54
|
+
modelName: "gpt-5.4",
|
|
55
|
+
tokenCount: { inputTokens: 200, outputTokens: 80 },
|
|
56
|
+
relevantFiles: [{ path: "src/main.ts" }],
|
|
57
|
+
workspaceUris: ["file:///Users/test/project"],
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
]);
|
|
61
|
+
const trace = await extractCursor(dbPath, sessionId, "test@example.com");
|
|
62
|
+
expect(trace.turn_count).toBe(2);
|
|
63
|
+
expect(trace.source_version).toBe("14");
|
|
64
|
+
expect(trace.total_input_tokens).toBe(200);
|
|
65
|
+
expect(trace.total_output_tokens).toBe(80);
|
|
66
|
+
expect(trace.has_thinking_blocks).toBe(true);
|
|
67
|
+
expect(trace.turns[0]).toMatchObject({
|
|
68
|
+
role: "user",
|
|
69
|
+
content: [{ type: "text", text: "Help me debug the repo" }],
|
|
70
|
+
});
|
|
71
|
+
expect(trace.turns[1].role).toBe("assistant");
|
|
72
|
+
expect(trace.turns[1].model).toBe("gpt-5.4");
|
|
73
|
+
expect(trace.turns[1].content.map((block) => block.type)).toEqual([
|
|
74
|
+
"thinking",
|
|
75
|
+
"text",
|
|
76
|
+
]);
|
|
77
|
+
expect(trace.turns[1].usage).toMatchObject({
|
|
78
|
+
input_tokens: 200,
|
|
79
|
+
output_tokens: 80,
|
|
80
|
+
});
|
|
81
|
+
expect(trace.env_state?.open_files_in_editor).toEqual(expect.arrayContaining(["src/main.ts", "/Users/test/project"]));
|
|
82
|
+
});
|
|
83
|
+
it("falls back to the legacy agentKv:blob:<bubble> storage format", async () => {
|
|
84
|
+
const sessionId = "cursor-legacy";
|
|
85
|
+
const dbPath = makeCursorDb(sessionId, [
|
|
86
|
+
{ bubbleId: "user-1", type: 1 },
|
|
87
|
+
{ bubbleId: "assistant-1", type: 2 },
|
|
88
|
+
], [
|
|
89
|
+
[
|
|
90
|
+
`agentKv:blob:user-1`,
|
|
91
|
+
{
|
|
92
|
+
role: "user",
|
|
93
|
+
type: "user",
|
|
94
|
+
text: "Open app.py",
|
|
95
|
+
createdAt: "2026-03-21T01:00:01.000Z",
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
`agentKv:blob:assistant-1`,
|
|
100
|
+
{
|
|
101
|
+
role: "assistant",
|
|
102
|
+
message: "Opened the file.",
|
|
103
|
+
createdAt: "2026-03-21T01:00:02.000Z",
|
|
104
|
+
usage: { promptTokens: 55, completionTokens: 21 },
|
|
105
|
+
context: { openFiles: [{ path: "/tmp/app.py" }] },
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
]);
|
|
109
|
+
const trace = await extractCursor(dbPath, sessionId, "test@example.com");
|
|
110
|
+
expect(trace.turn_count).toBe(2);
|
|
111
|
+
expect(trace.total_input_tokens).toBe(55);
|
|
112
|
+
expect(trace.total_output_tokens).toBe(21);
|
|
113
|
+
expect(trace.turns[1]).toMatchObject({
|
|
114
|
+
role: "assistant",
|
|
115
|
+
content: [{ type: "text", text: "Opened the file." }],
|
|
116
|
+
});
|
|
117
|
+
expect(trace.env_state?.open_files_in_editor).toEqual(["/tmp/app.py"]);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
//# sourceMappingURL=extractor-cursor.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractor-cursor.test.js","sourceRoot":"","sources":["../src/extractor-cursor.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC5B,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,MAAM,QAAQ,GAAa,EAAE,CAAC;AAE9B,SAAS,YAAY,CACnB,SAAiB,EACjB,OAAkD,EAClD,MAAgC;IAEhC,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC3D,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEnB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IACxC,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,EAAE,CAAC,IAAI,CAAC,uEAAuE,CAAC,CAAC;IAEjF,EAAE,CAAC,OAAO,CAAC,qDAAqD,CAAC,CAAC,GAAG,CACnE,gBAAgB,SAAS,EAAE,EAC3B,IAAI,CAAC,SAAS,CAAC;QACb,EAAE,EAAE,EAAE;QACN,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,0BAA0B,CAAC;QACjD,2BAA2B,EAAE,OAAO;KACrC,CAAC,CACH,CAAC;IAEF,MAAM,MAAM,GAAG,EAAE,CAAC,OAAO,CAAC,qDAAqD,CAAC,CAAC;IACjF,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;QAClC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACzC,CAAC;IAED,EAAE,CAAC,KAAK,EAAE,CAAC;IACX,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,CAAC,GAAG,EAAE;IACb,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QAC9E,MAAM,SAAS,GAAG,eAAe,CAAC;QAClC,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,EAAE;YACrC,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE;YAC/B,EAAE,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE;SACrC,EAAE;YACD;gBACE,YAAY,SAAS,SAAS;gBAC9B;oBACE,IAAI,EAAE,CAAC;oBACP,IAAI,EAAE,wBAAwB;oBAC9B,SAAS,EAAE,0BAA0B;oBACrC,UAAU,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE;iBAChD;aACF;YACD;gBACE,YAAY,SAAS,cAAc;gBACnC;oBACE,IAAI,EAAE,CAAC;oBACP,IAAI,EAAE,4BAA4B;oBAClC,QAAQ,EAAE,EAAE,IAAI,EAAE,4BAA4B,EAAE;oBAChD,SAAS,EAAE,0BAA0B;oBACrC,SAAS,EAAE,SAAS;oBACpB,UAAU,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE,EAAE;oBAClD,aAAa,EAAE,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;oBACxC,aAAa,EAAE,CAAC,4BAA4B,CAAC;iBAC9C;aACF;SACF,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,kBAAkB,CAAC,CAAC;QAEzE,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3C,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;YACnC,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAwB,EAAE,CAAC;SAC5D,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC9C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;YAChE,UAAU;YACV,MAAM;SACP,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC;YACzC,YAAY,EAAE,GAAG;YACjB,aAAa,EAAE,EAAE;SAClB,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC,OAAO,CACnD,MAAM,CAAC,eAAe,CAAC,CAAC,aAAa,EAAE,qBAAqB,CAAC,CAAC,CAC/D,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC7E,MAAM,SAAS,GAAG,eAAe,CAAC;QAClC,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,EAAE;YACrC,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE;YAC/B,EAAE,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE;SACrC,EAAE;YACD;gBACE,qBAAqB;gBACrB;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,aAAa;oBACnB,SAAS,EAAE,0BAA0B;iBACtC;aACF;YACD;gBACE,0BAA0B;gBAC1B;oBACE,IAAI,EAAE,WAAW;oBACjB,OAAO,EAAE,kBAAkB;oBAC3B,SAAS,EAAE,0BAA0B;oBACrC,KAAK,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE;oBACjD,OAAO,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,EAAE;iBAClD;aACF;SACF,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,kBAAkB,CAAC,CAAC;QAEzE,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;YACnC,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC;SACtD,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC,OAAO,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-code.d.ts","sourceRoot":"","sources":["../../src/extractors/claude-code.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,eAAe,EAIhB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"claude-code.d.ts","sourceRoot":"","sources":["../../src/extractors/claude-code.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,eAAe,EAIhB,MAAM,aAAa,CAAC;AAGrB,wBAAsB,iBAAiB,CACrC,eAAe,EAAE,MAAM,EACvB,WAAW,SAAY,GACtB,OAAO,CAAC,eAAe,CAAC,CAiR1B"}
|
|
@@ -2,6 +2,7 @@ import { createReadStream } from "fs";
|
|
|
2
2
|
import { createInterface } from "readline";
|
|
3
3
|
import { randomUUID } from "crypto";
|
|
4
4
|
import { hashString, computeContentHash } from "../hash.js";
|
|
5
|
+
import { deriveTurnActors } from "../turn-actors.js";
|
|
5
6
|
export async function extractClaudeCode(sessionFilePath, submittedBy = "unknown") {
|
|
6
7
|
const lines = [];
|
|
7
8
|
const rl = createInterface({
|
|
@@ -17,95 +18,193 @@ export async function extractClaudeCode(sessionFilePath, submittedBy = "unknown"
|
|
|
17
18
|
catch { }
|
|
18
19
|
}
|
|
19
20
|
const sessionId = sessionFilePath.split("/").pop()?.replace(".jsonl", "") ?? randomUUID();
|
|
21
|
+
// Build uuid→entry map for parent lookups
|
|
22
|
+
const uuidMap = new Map();
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
if (line.uuid)
|
|
25
|
+
uuidMap.set(line.uuid, line);
|
|
26
|
+
}
|
|
27
|
+
// Filter to lines that carry message content (skip progress, snapshots, etc.)
|
|
28
|
+
const SKIP_TYPES = new Set([
|
|
29
|
+
"progress",
|
|
30
|
+
"file-history-snapshot",
|
|
31
|
+
"system",
|
|
32
|
+
"last-prompt",
|
|
33
|
+
]);
|
|
34
|
+
const entries = lines.filter((l) => !SKIP_TYPES.has(l.type) && l.message);
|
|
35
|
+
// Claude Code splits a single logical turn into multiple chained JSONL entries:
|
|
36
|
+
//
|
|
37
|
+
// 1. Assistant turns: thinking → tool_use → text each parent the next
|
|
38
|
+
// (assistant→assistant parentage). Merge into one turn.
|
|
39
|
+
//
|
|
40
|
+
// 2. User tool_results for parallel tool calls: each result is a separate entry
|
|
41
|
+
// whose parentUuid points to a different entry in the same assistant chain.
|
|
42
|
+
// Merge into one user turn.
|
|
43
|
+
// For assistant entries: walk up until the parent is NOT an assistant entry.
|
|
44
|
+
function getAssistantRoot(entry) {
|
|
45
|
+
let cur = entry;
|
|
46
|
+
while (cur.message?.role === "assistant") {
|
|
47
|
+
const parent = uuidMap.get(cur.parentUuid);
|
|
48
|
+
if (!parent?.message || parent.message.role !== "assistant")
|
|
49
|
+
break;
|
|
50
|
+
cur = parent;
|
|
51
|
+
}
|
|
52
|
+
return cur;
|
|
53
|
+
}
|
|
54
|
+
// For a user entry: return the uuid of the assistant group root that it responds to
|
|
55
|
+
// (null if its parent is not an assistant).
|
|
56
|
+
function parentAssistantRootUuid(entry) {
|
|
57
|
+
const parent = uuidMap.get(entry.parentUuid);
|
|
58
|
+
if (!parent?.message || parent.message.role !== "assistant")
|
|
59
|
+
return null;
|
|
60
|
+
return getAssistantRoot(parent).uuid ?? null;
|
|
61
|
+
}
|
|
62
|
+
// Group entries in file order.
|
|
63
|
+
// Key: root uuid for assistant groups; entry uuid for user groups.
|
|
64
|
+
const groupMap = new Map();
|
|
65
|
+
const rootOrder = [];
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
const role = entry.message?.role;
|
|
68
|
+
if (role === "assistant") {
|
|
69
|
+
const root = getAssistantRoot(entry);
|
|
70
|
+
const rootUuid = root.uuid ?? randomUUID();
|
|
71
|
+
if (!groupMap.has(rootUuid)) {
|
|
72
|
+
groupMap.set(rootUuid, [root]);
|
|
73
|
+
rootOrder.push(rootUuid);
|
|
74
|
+
}
|
|
75
|
+
else if (entry !== root) {
|
|
76
|
+
groupMap.get(rootUuid).push(entry);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// User entry: merge with the previous user group if they share the same
|
|
81
|
+
// parent assistant root (i.e., parallel tool results for the same tool call batch).
|
|
82
|
+
const myParentRoot = parentAssistantRootUuid(entry);
|
|
83
|
+
const lastKey = rootOrder[rootOrder.length - 1];
|
|
84
|
+
const lastGroup = lastKey ? groupMap.get(lastKey) : undefined;
|
|
85
|
+
const lastRole = lastGroup?.[0]?.message?.role;
|
|
86
|
+
if (myParentRoot &&
|
|
87
|
+
lastRole === "user" &&
|
|
88
|
+
parentAssistantRootUuid(lastGroup[0]) === myParentRoot) {
|
|
89
|
+
lastGroup.push(entry);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const groupKey = entry.uuid ?? randomUUID();
|
|
93
|
+
groupMap.set(groupKey, [entry]);
|
|
94
|
+
rootOrder.push(groupKey);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
20
98
|
const turns = [];
|
|
21
99
|
let totalInputTokens = 0;
|
|
22
100
|
let totalOutputTokens = 0;
|
|
23
101
|
let totalCacheReadTokens = 0;
|
|
24
102
|
let gitBranch = null;
|
|
25
103
|
let cwdHash = null;
|
|
26
|
-
for (const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
cache_creation_input_tokens: usage.cache_creation_input_tokens ?? null,
|
|
36
|
-
reasoning_tokens: null,
|
|
37
|
-
}
|
|
38
|
-
: null;
|
|
39
|
-
if (tokenUsage) {
|
|
40
|
-
totalInputTokens += tokenUsage.input_tokens;
|
|
41
|
-
totalOutputTokens += tokenUsage.output_tokens;
|
|
42
|
-
totalCacheReadTokens += tokenUsage.cache_read_input_tokens ?? 0;
|
|
43
|
-
}
|
|
44
|
-
if (line.gitBranch)
|
|
45
|
-
gitBranch = line.gitBranch;
|
|
46
|
-
if (line.cwd && !cwdHash)
|
|
47
|
-
cwdHash = hashString(line.cwd);
|
|
104
|
+
for (const rootUuid of rootOrder) {
|
|
105
|
+
const group = groupMap.get(rootUuid);
|
|
106
|
+
const root = group[0];
|
|
107
|
+
if (root.gitBranch)
|
|
108
|
+
gitBranch = root.gitBranch;
|
|
109
|
+
if (root.cwd && !cwdHash)
|
|
110
|
+
cwdHash = hashString(root.cwd);
|
|
111
|
+
const role = root.message.role === "assistant" ? "assistant" : "user";
|
|
112
|
+
// Collect content blocks from all entries in the group
|
|
48
113
|
const contentBlocks = [];
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
?
|
|
53
|
-
:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
114
|
+
for (const entry of group) {
|
|
115
|
+
const raw = entry.message.content;
|
|
116
|
+
const rawContent = Array.isArray(raw)
|
|
117
|
+
? raw
|
|
118
|
+
: typeof raw === "string"
|
|
119
|
+
? [{ type: "text", text: raw }]
|
|
120
|
+
: [];
|
|
121
|
+
for (const block of rawContent) {
|
|
122
|
+
if (!block || !block.type)
|
|
123
|
+
continue;
|
|
124
|
+
if (block.type === "file-history-snapshot")
|
|
125
|
+
continue;
|
|
126
|
+
if (block.type === "text") {
|
|
127
|
+
contentBlocks.push({ type: "text", text: block.text ?? "" });
|
|
128
|
+
}
|
|
129
|
+
else if (block.type === "thinking") {
|
|
130
|
+
contentBlocks.push({
|
|
131
|
+
type: "thinking",
|
|
132
|
+
text: block.thinking ?? block.text ?? "",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
else if (block.type === "tool_use") {
|
|
136
|
+
contentBlocks.push({
|
|
137
|
+
type: "tool_use",
|
|
138
|
+
tool_call_id: block.id ?? randomUUID(),
|
|
139
|
+
tool_name: block.name ?? "",
|
|
140
|
+
tool_input: block.input ?? {},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
else if (block.type === "tool_result") {
|
|
144
|
+
const resultContent = Array.isArray(block.content)
|
|
145
|
+
? block.content.map((c) => c.text ?? "").join("\n")
|
|
146
|
+
: block.content ?? null;
|
|
147
|
+
contentBlocks.push({
|
|
148
|
+
type: "tool_result",
|
|
149
|
+
tool_call_id: block.tool_use_id ?? "",
|
|
150
|
+
is_error: block.is_error ?? false,
|
|
151
|
+
result_content: resultContent,
|
|
152
|
+
exit_code: null,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
87
155
|
}
|
|
88
156
|
}
|
|
89
157
|
if (contentBlocks.length === 0)
|
|
90
158
|
continue;
|
|
159
|
+
// Token accounting for assistant turns:
|
|
160
|
+
// - input/cache tokens come from the root entry only (same API call repeated on each chain entry)
|
|
161
|
+
// - output tokens must be summed across all chain entries (each has its own generated content)
|
|
162
|
+
let tokenUsage = null;
|
|
163
|
+
if (role === "assistant") {
|
|
164
|
+
const rootUsage = root.message.usage;
|
|
165
|
+
if (rootUsage) {
|
|
166
|
+
const inputTokens = rootUsage.input_tokens ?? 0;
|
|
167
|
+
const cacheCreate = rootUsage.cache_creation_input_tokens ?? 0;
|
|
168
|
+
const cacheRead = rootUsage.cache_read_input_tokens ?? 0;
|
|
169
|
+
// Sum output tokens across all chain entries
|
|
170
|
+
const outputTokens = group.reduce((sum, entry) => {
|
|
171
|
+
return sum + (entry.message.usage?.output_tokens ?? 0);
|
|
172
|
+
}, 0);
|
|
173
|
+
tokenUsage = {
|
|
174
|
+
input_tokens: inputTokens + cacheCreate + cacheRead,
|
|
175
|
+
output_tokens: outputTokens,
|
|
176
|
+
cache_read_input_tokens: cacheRead,
|
|
177
|
+
cache_creation_input_tokens: cacheCreate,
|
|
178
|
+
reasoning_tokens: null,
|
|
179
|
+
};
|
|
180
|
+
totalInputTokens += inputTokens + cacheCreate + cacheRead;
|
|
181
|
+
totalOutputTokens += outputTokens;
|
|
182
|
+
totalCacheReadTokens += cacheRead;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
91
185
|
turns.push({
|
|
92
|
-
turn_id:
|
|
93
|
-
parent_turn_id:
|
|
94
|
-
role
|
|
95
|
-
timestamp:
|
|
186
|
+
turn_id: root.uuid ?? randomUUID(),
|
|
187
|
+
parent_turn_id: root.parentUuid ?? null,
|
|
188
|
+
role,
|
|
189
|
+
timestamp: root.timestamp ?? null,
|
|
96
190
|
content: contentBlocks,
|
|
97
|
-
model: model ?? null,
|
|
191
|
+
model: root.message.model ?? null,
|
|
98
192
|
usage: tokenUsage,
|
|
99
193
|
source_metadata: {
|
|
100
|
-
uuid:
|
|
101
|
-
parentUuid:
|
|
102
|
-
gitBranch:
|
|
194
|
+
uuid: root.uuid,
|
|
195
|
+
parentUuid: root.parentUuid,
|
|
196
|
+
gitBranch: root.gitBranch,
|
|
103
197
|
},
|
|
104
198
|
});
|
|
105
199
|
}
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
200
|
+
const turnActors = deriveTurnActors(turns);
|
|
201
|
+
const normalizedTurns = turns.map((turn) => ({
|
|
202
|
+
...turn,
|
|
203
|
+
actor: turnActors[turn.turn_id],
|
|
204
|
+
}));
|
|
205
|
+
const startedAt = normalizedTurns[0]?.timestamp ?? new Date().toISOString();
|
|
206
|
+
const endedAt = normalizedTurns[normalizedTurns.length - 1]?.timestamp ?? new Date().toISOString();
|
|
207
|
+
const allBlocks = normalizedTurns.flatMap((t) => t.content);
|
|
109
208
|
const toolCallCount = allBlocks.filter((b) => b.type === "tool_use").length;
|
|
110
209
|
const hasFileChanges = allBlocks.some((b) => b.type === "tool_use" &&
|
|
111
210
|
(b.tool_name?.toLowerCase().includes("write") ||
|
|
@@ -127,8 +226,8 @@ export async function extractClaudeCode(sessionFilePath, submittedBy = "unknown"
|
|
|
127
226
|
working_language: null,
|
|
128
227
|
started_at: startedAt,
|
|
129
228
|
ended_at: endedAt,
|
|
130
|
-
turns,
|
|
131
|
-
turn_count:
|
|
229
|
+
turns: normalizedTurns,
|
|
230
|
+
turn_count: normalizedTurns.length,
|
|
132
231
|
tool_call_count: toolCallCount,
|
|
133
232
|
has_tool_calls: toolCallCount > 0,
|
|
134
233
|
has_thinking_blocks: hasThinking,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/extractors/claude-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,IAAI,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/extractors/claude-code.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/extractors/codex.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EAGhB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/extractors/codex.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EAGhB,MAAM,aAAa,CAAC;AAkBrB,wBAAsB,YAAY,CAChC,iBAAiB,EAAE,MAAM,EACzB,WAAW,SAAY,GACtB,OAAO,CAAC,eAAe,CAAC,CAmN1B"}
|
package/dist/extractors/codex.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
2
|
import { hashString, computeContentHash } from "../hash.js";
|
|
3
|
+
import { collectTraceMetrics, createPassiveEnvState, extractTextFragments, pushUniqueTextBlock, } from "./common.js";
|
|
4
|
+
function toNumber(value) {
|
|
5
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
6
|
+
}
|
|
7
|
+
function extractExitCode(output) {
|
|
8
|
+
if (!output)
|
|
9
|
+
return null;
|
|
10
|
+
const match = output.match(/Process exited with code (\d+)/);
|
|
11
|
+
return match ? Number.parseInt(match[1], 10) : null;
|
|
12
|
+
}
|
|
3
13
|
export async function extractCodex(rolloutFileBuffer, submittedBy = "unknown") {
|
|
4
14
|
const events = [];
|
|
5
15
|
for (const line of rolloutFileBuffer.toString("utf-8").split("\n")) {
|
|
@@ -21,6 +31,10 @@ export async function extractCodex(rolloutFileBuffer, submittedBy = "unknown") {
|
|
|
21
31
|
let currentBlocks = [];
|
|
22
32
|
let taskTimestamp = null;
|
|
23
33
|
let currentModel = null;
|
|
34
|
+
let currentTaskLastTimestamp = null;
|
|
35
|
+
let totalInputTokens = null;
|
|
36
|
+
let totalOutputTokens = null;
|
|
37
|
+
let totalCacheReadTokens = null;
|
|
24
38
|
for (const event of events) {
|
|
25
39
|
const type = event["type"];
|
|
26
40
|
const payload = (event["payload"] ?? {});
|
|
@@ -31,6 +45,7 @@ export async function extractCodex(rolloutFileBuffer, submittedBy = "unknown") {
|
|
|
31
45
|
currentUserMessage = null;
|
|
32
46
|
currentBlocks = [];
|
|
33
47
|
taskTimestamp = timestamp;
|
|
48
|
+
currentTaskLastTimestamp = timestamp;
|
|
34
49
|
continue;
|
|
35
50
|
}
|
|
36
51
|
if (type === "event_msg" && payload["type"] === "task_complete") {
|
|
@@ -49,16 +64,14 @@ export async function extractCodex(rolloutFileBuffer, submittedBy = "unknown") {
|
|
|
49
64
|
}
|
|
50
65
|
const lastMsg = payload["last_agent_message"];
|
|
51
66
|
if (lastMsg) {
|
|
52
|
-
|
|
53
|
-
if (!alreadyCaptured)
|
|
54
|
-
currentBlocks.push({ type: "text", text: lastMsg });
|
|
67
|
+
pushUniqueTextBlock(currentBlocks, "text", lastMsg);
|
|
55
68
|
}
|
|
56
69
|
if (currentBlocks.length > 0) {
|
|
57
70
|
turns.push({
|
|
58
71
|
turn_id: currentTurnId ?? randomUUID(),
|
|
59
72
|
parent_turn_id: currentUserMessage ? "user_" + currentTurnId : null,
|
|
60
73
|
role: "assistant",
|
|
61
|
-
timestamp: taskTimestamp,
|
|
74
|
+
timestamp: currentTaskLastTimestamp ?? timestamp ?? taskTimestamp,
|
|
62
75
|
content: currentBlocks,
|
|
63
76
|
model: currentModel,
|
|
64
77
|
usage: null,
|
|
@@ -72,10 +85,13 @@ export async function extractCodex(rolloutFileBuffer, submittedBy = "unknown") {
|
|
|
72
85
|
currentBlocks = [];
|
|
73
86
|
taskTimestamp = null;
|
|
74
87
|
currentModel = null;
|
|
88
|
+
currentTaskLastTimestamp = null;
|
|
75
89
|
continue;
|
|
76
90
|
}
|
|
77
91
|
if (!inTask)
|
|
78
92
|
continue;
|
|
93
|
+
if (timestamp)
|
|
94
|
+
currentTaskLastTimestamp = timestamp;
|
|
79
95
|
if (type === "turn_context") {
|
|
80
96
|
currentModel = payload["model"] ?? null;
|
|
81
97
|
continue;
|
|
@@ -86,17 +102,43 @@ export async function extractCodex(rolloutFileBuffer, submittedBy = "unknown") {
|
|
|
86
102
|
currentUserMessage = payload["message"] ?? "";
|
|
87
103
|
break;
|
|
88
104
|
case "agent_reasoning":
|
|
89
|
-
|
|
90
|
-
currentBlocks.push({ type: "thinking", text: payload["text"] });
|
|
105
|
+
pushUniqueTextBlock(currentBlocks, "thinking", payload["text"]);
|
|
91
106
|
break;
|
|
92
107
|
case "agent_message":
|
|
93
|
-
|
|
94
|
-
currentBlocks.push({ type: "text", text: payload["message"] });
|
|
108
|
+
pushUniqueTextBlock(currentBlocks, "text", payload["message"]);
|
|
95
109
|
break;
|
|
110
|
+
case "token_count": {
|
|
111
|
+
const info = (payload["info"] ?? {});
|
|
112
|
+
const usage = (info["total_token_usage"] ?? {});
|
|
113
|
+
totalInputTokens = toNumber(usage["input_tokens"]) ?? totalInputTokens;
|
|
114
|
+
totalOutputTokens = toNumber(usage["output_tokens"]) ?? totalOutputTokens;
|
|
115
|
+
totalCacheReadTokens =
|
|
116
|
+
toNumber(usage["cached_input_tokens"]) ?? totalCacheReadTokens;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
96
119
|
}
|
|
97
120
|
}
|
|
98
121
|
else if (type === "response_item") {
|
|
99
122
|
switch (payload["type"]) {
|
|
123
|
+
case "message": {
|
|
124
|
+
const role = payload["role"];
|
|
125
|
+
const texts = extractTextFragments(payload["content"]);
|
|
126
|
+
if (role === "assistant") {
|
|
127
|
+
for (const text of texts) {
|
|
128
|
+
pushUniqueTextBlock(currentBlocks, "text", text);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else if (role === "user" && !currentUserMessage) {
|
|
132
|
+
currentUserMessage = texts.join("\n\n") || currentUserMessage;
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case "reasoning": {
|
|
137
|
+
const reasoningText = extractTextFragments(payload["content"]).join("\n\n") ||
|
|
138
|
+
extractTextFragments(payload["summary"]).join("\n\n");
|
|
139
|
+
pushUniqueTextBlock(currentBlocks, "thinking", reasoningText);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
100
142
|
case "function_call": {
|
|
101
143
|
const callId = payload["call_id"] ?? randomUUID();
|
|
102
144
|
let toolInput = {};
|
|
@@ -116,15 +158,13 @@ export async function extractCodex(rolloutFileBuffer, submittedBy = "unknown") {
|
|
|
116
158
|
}
|
|
117
159
|
case "function_call_output": {
|
|
118
160
|
const output = payload["output"];
|
|
119
|
-
const
|
|
120
|
-
&& output.includes("Process exited with code")
|
|
121
|
-
&& !output.includes("code 0");
|
|
161
|
+
const exitCode = extractExitCode(output);
|
|
122
162
|
currentBlocks.push({
|
|
123
163
|
type: "tool_result",
|
|
124
164
|
tool_call_id: payload["call_id"] ?? "",
|
|
125
|
-
is_error:
|
|
165
|
+
is_error: exitCode !== null ? exitCode !== 0 : false,
|
|
126
166
|
result_content: output ?? null,
|
|
127
|
-
exit_code:
|
|
167
|
+
exit_code: exitCode,
|
|
128
168
|
});
|
|
129
169
|
break;
|
|
130
170
|
}
|
|
@@ -141,11 +181,7 @@ export async function extractCodex(rolloutFileBuffer, submittedBy = "unknown") {
|
|
|
141
181
|
}
|
|
142
182
|
}
|
|
143
183
|
}
|
|
144
|
-
const
|
|
145
|
-
const toolCallCount = allBlocks.filter(b => b.type === "tool_use").length;
|
|
146
|
-
const hasFileChanges = allBlocks.some(b => b.type === "tool_use" && ["write_file", "file_change", "create_file"].includes(b.tool_name));
|
|
147
|
-
const hasShellCommands = allBlocks.some(b => b.type === "tool_use" && ["exec_command", "bash", "shell"].includes(b.tool_name));
|
|
148
|
-
const hasThinking = allBlocks.some(b => b.type === "thinking");
|
|
184
|
+
const metrics = collectTraceMetrics(turns);
|
|
149
185
|
const startedAt = sessionMetaPayload["timestamp"] ?? events[0]?.["timestamp"] ?? new Date().toISOString();
|
|
150
186
|
const endedAt = events[events.length - 1]?.["timestamp"] ?? new Date().toISOString();
|
|
151
187
|
const partialTrace = {
|
|
@@ -163,24 +199,16 @@ export async function extractCodex(rolloutFileBuffer, submittedBy = "unknown") {
|
|
|
163
199
|
ended_at: endedAt,
|
|
164
200
|
turns,
|
|
165
201
|
turn_count: turns.length,
|
|
166
|
-
tool_call_count: toolCallCount,
|
|
167
|
-
has_tool_calls: toolCallCount > 0,
|
|
168
|
-
has_thinking_blocks:
|
|
169
|
-
has_file_changes: hasFileChanges,
|
|
170
|
-
has_shell_commands: hasShellCommands,
|
|
171
|
-
total_input_tokens:
|
|
172
|
-
total_output_tokens:
|
|
173
|
-
total_cache_read_tokens:
|
|
202
|
+
tool_call_count: metrics.toolCallCount,
|
|
203
|
+
has_tool_calls: metrics.toolCallCount > 0,
|
|
204
|
+
has_thinking_blocks: metrics.hasThinkingBlocks,
|
|
205
|
+
has_file_changes: metrics.hasFileChanges,
|
|
206
|
+
has_shell_commands: metrics.hasShellCommands,
|
|
207
|
+
total_input_tokens: totalInputTokens,
|
|
208
|
+
total_output_tokens: totalOutputTokens,
|
|
209
|
+
total_cache_read_tokens: totalCacheReadTokens,
|
|
174
210
|
content_fidelity: "full",
|
|
175
|
-
env_state:
|
|
176
|
-
git_branch: null,
|
|
177
|
-
inferred_file_tree: null,
|
|
178
|
-
inferred_changed_files: null,
|
|
179
|
-
inferred_error_files: null,
|
|
180
|
-
shell_exit_codes: null,
|
|
181
|
-
open_files_in_editor: null,
|
|
182
|
-
extraction_method: "passive",
|
|
183
|
-
},
|
|
211
|
+
env_state: createPassiveEnvState(),
|
|
184
212
|
score: null,
|
|
185
213
|
raw_r2_key: "",
|
|
186
214
|
normalized_r2_key: "",
|