@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
package/src/extractors/codex.ts
CHANGED
|
@@ -5,6 +5,22 @@ import type {
|
|
|
5
5
|
Turn,
|
|
6
6
|
ContentBlock,
|
|
7
7
|
} from "../types.js";
|
|
8
|
+
import {
|
|
9
|
+
collectTraceMetrics,
|
|
10
|
+
createPassiveEnvState,
|
|
11
|
+
extractTextFragments,
|
|
12
|
+
pushUniqueTextBlock,
|
|
13
|
+
} from "./common.js";
|
|
14
|
+
|
|
15
|
+
function toNumber(value: unknown): number | null {
|
|
16
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function extractExitCode(output: string | undefined): number | null {
|
|
20
|
+
if (!output) return null;
|
|
21
|
+
const match = output.match(/Process exited with code (\d+)/);
|
|
22
|
+
return match ? Number.parseInt(match[1], 10) : null;
|
|
23
|
+
}
|
|
8
24
|
|
|
9
25
|
export async function extractCodex(
|
|
10
26
|
rolloutFileBuffer: Buffer,
|
|
@@ -29,6 +45,10 @@ export async function extractCodex(
|
|
|
29
45
|
let currentBlocks: ContentBlock[] = [];
|
|
30
46
|
let taskTimestamp: string | null = null;
|
|
31
47
|
let currentModel: string | null = null;
|
|
48
|
+
let currentTaskLastTimestamp: string | null = null;
|
|
49
|
+
let totalInputTokens: number | null = null;
|
|
50
|
+
let totalOutputTokens: number | null = null;
|
|
51
|
+
let totalCacheReadTokens: number | null = null;
|
|
32
52
|
|
|
33
53
|
for (const event of events) {
|
|
34
54
|
const type = event["type"] as string;
|
|
@@ -41,6 +61,7 @@ export async function extractCodex(
|
|
|
41
61
|
currentUserMessage = null;
|
|
42
62
|
currentBlocks = [];
|
|
43
63
|
taskTimestamp = timestamp;
|
|
64
|
+
currentTaskLastTimestamp = timestamp;
|
|
44
65
|
continue;
|
|
45
66
|
}
|
|
46
67
|
|
|
@@ -61,10 +82,7 @@ export async function extractCodex(
|
|
|
61
82
|
|
|
62
83
|
const lastMsg = payload["last_agent_message"] as string | undefined;
|
|
63
84
|
if (lastMsg) {
|
|
64
|
-
|
|
65
|
-
b => b.type === "text" && (b as { type: "text"; text: string }).text === lastMsg
|
|
66
|
-
);
|
|
67
|
-
if (!alreadyCaptured) currentBlocks.push({ type: "text", text: lastMsg });
|
|
85
|
+
pushUniqueTextBlock(currentBlocks, "text", lastMsg);
|
|
68
86
|
}
|
|
69
87
|
|
|
70
88
|
if (currentBlocks.length > 0) {
|
|
@@ -72,7 +90,7 @@ export async function extractCodex(
|
|
|
72
90
|
turn_id: currentTurnId ?? randomUUID(),
|
|
73
91
|
parent_turn_id: currentUserMessage ? "user_" + currentTurnId : null,
|
|
74
92
|
role: "assistant",
|
|
75
|
-
timestamp: taskTimestamp,
|
|
93
|
+
timestamp: currentTaskLastTimestamp ?? timestamp ?? taskTimestamp,
|
|
76
94
|
content: currentBlocks,
|
|
77
95
|
model: currentModel,
|
|
78
96
|
usage: null,
|
|
@@ -86,11 +104,14 @@ export async function extractCodex(
|
|
|
86
104
|
currentBlocks = [];
|
|
87
105
|
taskTimestamp = null;
|
|
88
106
|
currentModel = null;
|
|
107
|
+
currentTaskLastTimestamp = null;
|
|
89
108
|
continue;
|
|
90
109
|
}
|
|
91
110
|
|
|
92
111
|
if (!inTask) continue;
|
|
93
112
|
|
|
113
|
+
if (timestamp) currentTaskLastTimestamp = timestamp;
|
|
114
|
+
|
|
94
115
|
if (type === "turn_context") {
|
|
95
116
|
currentModel = (payload["model"] as string | undefined) ?? null;
|
|
96
117
|
continue;
|
|
@@ -102,14 +123,42 @@ export async function extractCodex(
|
|
|
102
123
|
currentUserMessage = (payload["message"] as string | undefined) ?? "";
|
|
103
124
|
break;
|
|
104
125
|
case "agent_reasoning":
|
|
105
|
-
|
|
126
|
+
pushUniqueTextBlock(currentBlocks, "thinking", payload["text"] as string | undefined);
|
|
106
127
|
break;
|
|
107
128
|
case "agent_message":
|
|
108
|
-
|
|
129
|
+
pushUniqueTextBlock(currentBlocks, "text", payload["message"] as string | undefined);
|
|
130
|
+
break;
|
|
131
|
+
case "token_count": {
|
|
132
|
+
const info = (payload["info"] ?? {}) as Record<string, unknown>;
|
|
133
|
+
const usage = (info["total_token_usage"] ?? {}) as Record<string, unknown>;
|
|
134
|
+
totalInputTokens = toNumber(usage["input_tokens"]) ?? totalInputTokens;
|
|
135
|
+
totalOutputTokens = toNumber(usage["output_tokens"]) ?? totalOutputTokens;
|
|
136
|
+
totalCacheReadTokens =
|
|
137
|
+
toNumber(usage["cached_input_tokens"]) ?? totalCacheReadTokens;
|
|
109
138
|
break;
|
|
139
|
+
}
|
|
110
140
|
}
|
|
111
141
|
} else if (type === "response_item") {
|
|
112
142
|
switch (payload["type"] as string) {
|
|
143
|
+
case "message": {
|
|
144
|
+
const role = payload["role"] as string | undefined;
|
|
145
|
+
const texts = extractTextFragments(payload["content"]);
|
|
146
|
+
if (role === "assistant") {
|
|
147
|
+
for (const text of texts) {
|
|
148
|
+
pushUniqueTextBlock(currentBlocks, "text", text);
|
|
149
|
+
}
|
|
150
|
+
} else if (role === "user" && !currentUserMessage) {
|
|
151
|
+
currentUserMessage = texts.join("\n\n") || currentUserMessage;
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case "reasoning": {
|
|
156
|
+
const reasoningText =
|
|
157
|
+
extractTextFragments(payload["content"]).join("\n\n") ||
|
|
158
|
+
extractTextFragments(payload["summary"]).join("\n\n");
|
|
159
|
+
pushUniqueTextBlock(currentBlocks, "thinking", reasoningText);
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
113
162
|
case "function_call": {
|
|
114
163
|
const callId = (payload["call_id"] as string | undefined) ?? randomUUID();
|
|
115
164
|
let toolInput: Record<string, unknown> = {};
|
|
@@ -126,15 +175,13 @@ export async function extractCodex(
|
|
|
126
175
|
}
|
|
127
176
|
case "function_call_output": {
|
|
128
177
|
const output = payload["output"] as string | undefined;
|
|
129
|
-
const
|
|
130
|
-
&& output.includes("Process exited with code")
|
|
131
|
-
&& !output.includes("code 0");
|
|
178
|
+
const exitCode = extractExitCode(output);
|
|
132
179
|
currentBlocks.push({
|
|
133
180
|
type: "tool_result",
|
|
134
181
|
tool_call_id: (payload["call_id"] as string | undefined) ?? "",
|
|
135
|
-
is_error:
|
|
182
|
+
is_error: exitCode !== null ? exitCode !== 0 : false,
|
|
136
183
|
result_content: output ?? null,
|
|
137
|
-
exit_code:
|
|
184
|
+
exit_code: exitCode,
|
|
138
185
|
});
|
|
139
186
|
break;
|
|
140
187
|
}
|
|
@@ -152,15 +199,7 @@ export async function extractCodex(
|
|
|
152
199
|
}
|
|
153
200
|
}
|
|
154
201
|
|
|
155
|
-
const
|
|
156
|
-
const toolCallCount = allBlocks.filter(b => b.type === "tool_use").length;
|
|
157
|
-
const hasFileChanges = allBlocks.some(
|
|
158
|
-
b => b.type === "tool_use" && ["write_file", "file_change", "create_file"].includes((b as { type: "tool_use"; tool_name: string }).tool_name)
|
|
159
|
-
);
|
|
160
|
-
const hasShellCommands = allBlocks.some(
|
|
161
|
-
b => b.type === "tool_use" && ["exec_command", "bash", "shell"].includes((b as { type: "tool_use"; tool_name: string }).tool_name)
|
|
162
|
-
);
|
|
163
|
-
const hasThinking = allBlocks.some(b => b.type === "thinking");
|
|
202
|
+
const metrics = collectTraceMetrics(turns);
|
|
164
203
|
const startedAt = (sessionMetaPayload["timestamp"] as string | undefined) ?? events[0]?.["timestamp"] as string ?? new Date().toISOString();
|
|
165
204
|
const endedAt = events[events.length - 1]?.["timestamp"] as string ?? new Date().toISOString();
|
|
166
205
|
|
|
@@ -179,24 +218,16 @@ export async function extractCodex(
|
|
|
179
218
|
ended_at: endedAt,
|
|
180
219
|
turns,
|
|
181
220
|
turn_count: turns.length,
|
|
182
|
-
tool_call_count: toolCallCount,
|
|
183
|
-
has_tool_calls: toolCallCount > 0,
|
|
184
|
-
has_thinking_blocks:
|
|
185
|
-
has_file_changes: hasFileChanges,
|
|
186
|
-
has_shell_commands: hasShellCommands,
|
|
187
|
-
total_input_tokens:
|
|
188
|
-
total_output_tokens:
|
|
189
|
-
total_cache_read_tokens:
|
|
221
|
+
tool_call_count: metrics.toolCallCount,
|
|
222
|
+
has_tool_calls: metrics.toolCallCount > 0,
|
|
223
|
+
has_thinking_blocks: metrics.hasThinkingBlocks,
|
|
224
|
+
has_file_changes: metrics.hasFileChanges,
|
|
225
|
+
has_shell_commands: metrics.hasShellCommands,
|
|
226
|
+
total_input_tokens: totalInputTokens,
|
|
227
|
+
total_output_tokens: totalOutputTokens,
|
|
228
|
+
total_cache_read_tokens: totalCacheReadTokens,
|
|
190
229
|
content_fidelity: "full",
|
|
191
|
-
env_state:
|
|
192
|
-
git_branch: null,
|
|
193
|
-
inferred_file_tree: null,
|
|
194
|
-
inferred_changed_files: null,
|
|
195
|
-
inferred_error_files: null,
|
|
196
|
-
shell_exit_codes: null,
|
|
197
|
-
open_files_in_editor: null,
|
|
198
|
-
extraction_method: "passive",
|
|
199
|
-
},
|
|
230
|
+
env_state: createPassiveEnvState(),
|
|
200
231
|
score: null,
|
|
201
232
|
raw_r2_key: "",
|
|
202
233
|
normalized_r2_key: "",
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { ContentBlock, EnvState, Turn } from "../types.js";
|
|
2
|
+
|
|
3
|
+
const SHELL_TOOL_NAMES = ["exec_command", "bash", "shell", "write_stdin"];
|
|
4
|
+
const FILE_MUTATION_TOOL_NAMES = [
|
|
5
|
+
"apply_patch",
|
|
6
|
+
"write_file",
|
|
7
|
+
"create_file",
|
|
8
|
+
"delete_file",
|
|
9
|
+
"rename_file",
|
|
10
|
+
"move_file",
|
|
11
|
+
"file_change",
|
|
12
|
+
"edit",
|
|
13
|
+
"edit_file",
|
|
14
|
+
"multiedit",
|
|
15
|
+
"write",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
function normalizeToolName(toolName: string): string {
|
|
19
|
+
return toolName.trim().toLowerCase();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function toolNameMatches(toolName: string, candidate: string): boolean {
|
|
23
|
+
const normalized = normalizeToolName(toolName);
|
|
24
|
+
return normalized === candidate || normalized.endsWith(`.${candidate}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isShellToolName(toolName: string): boolean {
|
|
28
|
+
return SHELL_TOOL_NAMES.some((candidate) => toolNameMatches(toolName, candidate));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isFileMutationTool(
|
|
32
|
+
toolName: string,
|
|
33
|
+
toolInput: Record<string, unknown>,
|
|
34
|
+
): boolean {
|
|
35
|
+
if (
|
|
36
|
+
FILE_MUTATION_TOOL_NAMES.some((candidate) => toolNameMatches(toolName, candidate))
|
|
37
|
+
) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!isShellToolName(toolName)) return false;
|
|
42
|
+
|
|
43
|
+
const command =
|
|
44
|
+
typeof toolInput.cmd === "string"
|
|
45
|
+
? toolInput.cmd
|
|
46
|
+
: typeof toolInput.command === "string"
|
|
47
|
+
? toolInput.command
|
|
48
|
+
: null;
|
|
49
|
+
|
|
50
|
+
return command !== null && /\bapply_patch\b|\bsed\s+-i\b|\bperl\s+-pi\b/.test(command);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function collectTraceMetrics(turns: Turn[]) {
|
|
54
|
+
const allBlocks = turns.flatMap((turn) => turn.content);
|
|
55
|
+
const toolUses = allBlocks.filter(
|
|
56
|
+
(block): block is Extract<ContentBlock, { type: "tool_use" }> =>
|
|
57
|
+
block.type === "tool_use",
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
toolCallCount: toolUses.length,
|
|
62
|
+
hasFileChanges: toolUses.some((block) =>
|
|
63
|
+
isFileMutationTool(block.tool_name, block.tool_input),
|
|
64
|
+
),
|
|
65
|
+
hasShellCommands: toolUses.some((block) => isShellToolName(block.tool_name)),
|
|
66
|
+
hasThinkingBlocks: allBlocks.some((block) => block.type === "thinking"),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function createPassiveEnvState(
|
|
71
|
+
overrides: Partial<EnvState> = {},
|
|
72
|
+
): EnvState {
|
|
73
|
+
return {
|
|
74
|
+
git_branch: null,
|
|
75
|
+
inferred_file_tree: null,
|
|
76
|
+
inferred_changed_files: null,
|
|
77
|
+
inferred_error_files: null,
|
|
78
|
+
shell_exit_codes: null,
|
|
79
|
+
open_files_in_editor: null,
|
|
80
|
+
extraction_method: "passive",
|
|
81
|
+
...overrides,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function extractTextFragments(value: unknown): string[] {
|
|
86
|
+
if (typeof value === "string") {
|
|
87
|
+
return value.trim() ? [value] : [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (Array.isArray(value)) {
|
|
91
|
+
return value.flatMap((item) => extractTextFragments(item));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!value || typeof value !== "object") {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const record = value as Record<string, unknown>;
|
|
99
|
+
|
|
100
|
+
if (typeof record.text === "string" && record.text.trim()) {
|
|
101
|
+
return [record.text];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const nested: string[] = [];
|
|
105
|
+
for (const key of ["content", "summary", "thinking", "message"]) {
|
|
106
|
+
if (key in record) {
|
|
107
|
+
nested.push(...extractTextFragments(record[key]));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return nested;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function pushUniqueTextBlock(
|
|
115
|
+
blocks: ContentBlock[],
|
|
116
|
+
type: "text" | "thinking",
|
|
117
|
+
text: string | null | undefined,
|
|
118
|
+
): void {
|
|
119
|
+
const normalized = text?.trim();
|
|
120
|
+
if (!normalized) return;
|
|
121
|
+
|
|
122
|
+
const exists = blocks.some(
|
|
123
|
+
(block) => block.type === type && block.text === normalized,
|
|
124
|
+
);
|
|
125
|
+
if (!exists) {
|
|
126
|
+
blocks.push({ type, text: normalized });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function normalizeTimestamp(value: unknown): string | null {
|
|
131
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
132
|
+
|
|
133
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
134
|
+
const millis = value > 1_000_000_000_000 ? value : value * 1000;
|
|
135
|
+
return new Date(millis).toISOString();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return null;
|
|
139
|
+
}
|