@spexcode/transcript 0.7.0-next.2 → 0.7.0-next.4
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/parsers.js +32 -9
- package/package.json +1 -1
package/dist/parsers.js
CHANGED
|
@@ -54,8 +54,22 @@ const lineCount = (value) => value ? value.split(/\r?\n/).length : 0;
|
|
|
54
54
|
// --- the seven native shapes -------------------------------------------------------------------------------
|
|
55
55
|
export function claudeEvent(value) {
|
|
56
56
|
const entry = object(value);
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
if (!entry)
|
|
58
|
+
return null;
|
|
59
|
+
// A message steered into a RUNNING turn (stream-json `type:user` on stdin, a queued command in the TUI) is not
|
|
60
|
+
// recorded as a `user` message: Claude writes it as an `attachment` of type `queued_command` carrying the prompt
|
|
61
|
+
// blocks. It is the person's turn all the same, and the one place a steer becomes observable — hooks never fire
|
|
62
|
+
// for it — so the reader draws it as a user turn at the moment it entered the conversation.
|
|
63
|
+
if (entry.type === 'attachment') {
|
|
64
|
+
const attachment = object(entry.attachment);
|
|
65
|
+
const queuedAt = at(entry);
|
|
66
|
+
if (attachment?.type !== 'queued_command' || queuedAt === null)
|
|
67
|
+
return null;
|
|
68
|
+
const text = items(attachment.prompt).map((block) => string(object(block)?.text)).filter(Boolean).join('\n') || null;
|
|
69
|
+
return text ? { at: queuedAt, turn: { id: idOf(entry), at: queuedAt, role: 'user', text, tools: [] } } : null;
|
|
70
|
+
}
|
|
71
|
+
const message = object(entry.message);
|
|
72
|
+
if (!message)
|
|
59
73
|
return null;
|
|
60
74
|
const eventAt = at(entry) ?? at(message);
|
|
61
75
|
if (eventAt === null)
|
|
@@ -90,6 +104,9 @@ export function claudeEvent(value) {
|
|
|
90
104
|
}
|
|
91
105
|
return null;
|
|
92
106
|
}
|
|
107
|
+
const blockText = (content) => typeof content === 'string'
|
|
108
|
+
? string(content)
|
|
109
|
+
: items(content).map((block) => string(object(block)?.text)).filter(Boolean).join('\n') || null;
|
|
93
110
|
export function codexEvent(value) {
|
|
94
111
|
const entry = object(value);
|
|
95
112
|
const payload = object(entry?.payload);
|
|
@@ -99,16 +116,25 @@ export function codexEvent(value) {
|
|
|
99
116
|
if (eventAt === null)
|
|
100
117
|
return { at: null, turn: null };
|
|
101
118
|
const type = string(payload.type);
|
|
102
|
-
|
|
103
|
-
|
|
119
|
+
// THE PERSON'S MESSAGE is the `user_message` event. Codex also records it as a `response_item` message (the API
|
|
120
|
+
// form, `input_text` blocks) beside harness injections that no person typed (the AGENTS.md instructions), so
|
|
121
|
+
// that form is not a turn — the event is, once.
|
|
122
|
+
if (entry.type === 'event_msg' && type === 'user_message') {
|
|
104
123
|
const text = typeof payload.message === 'string' ? payload.message : compact(payload.message ?? payload.content ?? '');
|
|
105
124
|
return text ? { at: eventAt, turn: { id: idOf(payload) ?? idOf(entry), at: eventAt, role: 'user', text, tools: [] } } : null;
|
|
106
125
|
}
|
|
107
|
-
|
|
126
|
+
if (entry.type === 'response_item' && (type === 'message' || type === 'input_message') && payload.role === 'user')
|
|
127
|
+
return { at: eventAt, turn: null };
|
|
128
|
+
// WHAT THE AGENT SAID is the `agent_message` event — commentary and the final answer alike. Codex 0.146 also
|
|
129
|
+
// records the same prose as a `response_item` message (`output_text` blocks) beside it, so that form is not read:
|
|
130
|
+
// reading both would say every sentence twice. An empty event (a final answer that was a tool call) is a clock,
|
|
131
|
+
// not a turn. Structured reasoning stays private.
|
|
108
132
|
if (entry.type === 'event_msg' && type === 'agent_message') {
|
|
109
133
|
const text = string(payload.message ?? payload.text);
|
|
110
|
-
return text ? { at: eventAt, turn: { id: idOf(payload) ?? idOf(entry), at: eventAt, role: 'assistant', text, tools: [] } } : null;
|
|
134
|
+
return text ? { at: eventAt, turn: { id: idOf(payload) ?? idOf(entry), at: eventAt, role: 'assistant', text, tools: [] } } : { at: eventAt, turn: null };
|
|
111
135
|
}
|
|
136
|
+
if (entry.type === 'response_item' && type === 'message' && payload.role === 'assistant')
|
|
137
|
+
return { at: eventAt, turn: null };
|
|
112
138
|
if (entry.type === 'response_item' && (type === 'custom_tool_call' || type === 'function_call')) {
|
|
113
139
|
const id = string(payload.call_id ?? payload.id) ?? 'tool';
|
|
114
140
|
return { at: eventAt, turn: { id: idOf(payload) ?? idOf(entry), at: eventAt, role: 'assistant', tools: [{ id, name: string(payload.name ?? payload.tool_name) ?? 'tool', input: payload.input === undefined && payload.arguments === undefined ? undefined : compact(payload.input ?? payload.arguments), outputLines: 0, outputBytes: 0 }] } };
|
|
@@ -215,9 +241,6 @@ export function codexAppServerStream() {
|
|
|
215
241
|
return parsed;
|
|
216
242
|
};
|
|
217
243
|
}
|
|
218
|
-
const blockText = (content) => typeof content === 'string'
|
|
219
|
-
? string(content)
|
|
220
|
-
: items(content).map((block) => string(object(block)?.text)).filter(Boolean).join('\n') || null;
|
|
221
244
|
export function piEvent(value) {
|
|
222
245
|
const entry = object(value);
|
|
223
246
|
const message = object(entry?.message);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spexcode/transcript",
|
|
3
|
-
"version": "0.7.0-next.
|
|
3
|
+
"version": "0.7.0-next.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Normalized agent transcripts: one parser per harness, a bounded interval reader over a native thread file or an in-memory event stream, and the full/delta frame protocol every transport and renderer share.",
|
|
6
6
|
"files": [
|