@zewstid/zeye-capture 0.3.0 → 0.3.1
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/README.md +1 -1
- package/dist/src/cli.js +0 -0
- package/dist/src/host.js +47 -5
- package/dist/src/providers.d.ts +12 -1
- package/dist/src/providers.js +62 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ redaction. Hidden model reasoning is never captured.
|
|
|
8
8
|
Install the public CLI with Node 20.18.1 or newer:
|
|
9
9
|
|
|
10
10
|
```sh
|
|
11
|
-
npm install --global @zewstid/zeye-capture@0.3.
|
|
11
|
+
npm install --global @zewstid/zeye-capture@0.3.1
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Capture `0.3.x` uses the `@zewstid/zeye-shared` `0.5.x` contracts.
|
package/dist/src/cli.js
CHANGED
|
File without changes
|
package/dist/src/host.js
CHANGED
|
@@ -13,8 +13,15 @@ function parseCodexCheckpoint(cursor) {
|
|
|
13
13
|
try {
|
|
14
14
|
const parsed = JSON.parse(cursor);
|
|
15
15
|
const completedLines = parsed.completedLines;
|
|
16
|
-
if (parsed.version === 2 && Number.isInteger(completedLines) && completedLines !== undefined && completedLines >= 0 && typeof parsed.sourceGenerationId === 'string' && parsed.sourceGenerationId) {
|
|
17
|
-
return {
|
|
16
|
+
if ((parsed.version === 2 || parsed.version === 3) && Number.isInteger(completedLines) && completedLines !== undefined && completedLines >= 0 && typeof parsed.sourceGenerationId === 'string' && parsed.sourceGenerationId) {
|
|
17
|
+
return {
|
|
18
|
+
version: parsed.version,
|
|
19
|
+
completedLines,
|
|
20
|
+
lastLineHash: typeof parsed.lastLineHash === 'string' ? parsed.lastLineHash : undefined,
|
|
21
|
+
sourceGenerationId: parsed.sourceGenerationId,
|
|
22
|
+
sourceSessionId: typeof parsed.sourceSessionId === 'string' ? parsed.sourceSessionId : undefined,
|
|
23
|
+
clientVersion: typeof parsed.clientVersion === 'string' ? parsed.clientVersion : undefined,
|
|
24
|
+
};
|
|
18
25
|
}
|
|
19
26
|
}
|
|
20
27
|
catch { /* legacy cursor */ }
|
|
@@ -47,17 +54,45 @@ async function importCodexJsonl(outbox, filePath, context, captureContent = fals
|
|
|
47
54
|
// replacement without ever storing the source record itself.
|
|
48
55
|
const checkpointMatches = Boolean(checkpoint && completedLines <= lines.length && (!checkpoint.lastLineHash || completedLines === 0 || checkpoint.lastLineHash === (0, canonical_1.sha256)(lines[completedLines - 1])));
|
|
49
56
|
const rotated = Boolean(checkpoint && !checkpointMatches);
|
|
50
|
-
|
|
57
|
+
// v2 checkpoints advanced past records that omitted a per-line session ID.
|
|
58
|
+
// Reprocess them once under v3; the outbox's source-key idempotency safely
|
|
59
|
+
// collapses the records that were already captured while recovering those
|
|
60
|
+
// that the old parser skipped.
|
|
61
|
+
const checkpointNeedsIdentityUpgrade = Boolean(checkpoint && checkpoint.version !== 3);
|
|
62
|
+
const start = rotated || checkpointNeedsIdentityUpgrade ? 0 : completedLines;
|
|
51
63
|
const generationId = checkpoint && !rotated && checkpoint.sourceGenerationId
|
|
52
64
|
? checkpoint.sourceGenerationId
|
|
53
65
|
: sourceGenerationId(filePath, lines[0], file);
|
|
66
|
+
let streamIdentity = rotated
|
|
67
|
+
? {}
|
|
68
|
+
: { sessionId: checkpoint?.sourceSessionId, clientVersion: checkpoint?.clientVersion };
|
|
69
|
+
// Session metadata is normally first, but pre-scan content-blind identity so
|
|
70
|
+
// resumed imports and provider ordering changes remain safe.
|
|
71
|
+
for (const line of lines) {
|
|
72
|
+
try {
|
|
73
|
+
const identity = (0, providers_1.codexStreamIdentity)(JSON.parse(line));
|
|
74
|
+
streamIdentity = {
|
|
75
|
+
sessionId: streamIdentity.sessionId || identity.sessionId,
|
|
76
|
+
clientVersion: streamIdentity.clientVersion || identity.clientVersion,
|
|
77
|
+
};
|
|
78
|
+
if (streamIdentity.sessionId && streamIdentity.clientVersion)
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
catch { /* the ordered import below owns malformed-line handling */ }
|
|
82
|
+
}
|
|
54
83
|
let imported = 0;
|
|
55
84
|
let skipped = 0;
|
|
56
85
|
let deferred = raw.length > 0 && !(raw.endsWith('\n') || raw.endsWith('\r\n')) ? 1 : 0;
|
|
57
86
|
let consumed = start;
|
|
58
87
|
for (let index = start; index < lines.length; index += 1) {
|
|
59
88
|
try {
|
|
60
|
-
const
|
|
89
|
+
const parsedLine = JSON.parse(lines[index]);
|
|
90
|
+
const identity = (0, providers_1.codexStreamIdentity)(parsedLine);
|
|
91
|
+
streamIdentity = {
|
|
92
|
+
sessionId: identity.sessionId || streamIdentity.sessionId,
|
|
93
|
+
clientVersion: identity.clientVersion || streamIdentity.clientVersion,
|
|
94
|
+
};
|
|
95
|
+
const observation = (0, providers_1.fromCodexJsonl)(parsedLine, (0, node_path_1.basename)(filePath), captureContent, streamIdentity);
|
|
61
96
|
if (!observation) {
|
|
62
97
|
skipped += 1;
|
|
63
98
|
consumed = index + 1;
|
|
@@ -83,7 +118,14 @@ async function importCodexJsonl(outbox, filePath, context, captureContent = fals
|
|
|
83
118
|
break;
|
|
84
119
|
}
|
|
85
120
|
}
|
|
86
|
-
await outbox.checkpoint('codex', scope, checkpointCursor({
|
|
121
|
+
await outbox.checkpoint('codex', scope, checkpointCursor({
|
|
122
|
+
version: 3,
|
|
123
|
+
completedLines: consumed,
|
|
124
|
+
lastLineHash: consumed ? (0, canonical_1.sha256)(lines[consumed - 1]) : undefined,
|
|
125
|
+
sourceGenerationId: generationId,
|
|
126
|
+
sourceSessionId: streamIdentity.sessionId,
|
|
127
|
+
clientVersion: streamIdentity.clientVersion,
|
|
128
|
+
}));
|
|
87
129
|
return { imported, skipped, deferred, rotated };
|
|
88
130
|
}
|
|
89
131
|
async function flushIfConfigured(outbox, config) {
|
package/dist/src/providers.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { MetadataCaptureEvent } from "./types";
|
|
2
|
+
export type CodexStreamIdentity = {
|
|
3
|
+
sessionId?: string;
|
|
4
|
+
clientVersion?: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Current Codex rollout logs put stream identity on `session_meta.payload`,
|
|
8
|
+
* while subsequent records carry only turn/item identifiers. Keep this small
|
|
9
|
+
* helper content-blind so the importer can safely inherit identity without
|
|
10
|
+
* inspecting prompts, responses, tool arguments, or reasoning.
|
|
11
|
+
*/
|
|
12
|
+
export declare function codexStreamIdentity(input: unknown): CodexStreamIdentity;
|
|
2
13
|
/** Parse provider-visible Claude transcript JSONL without hidden thinking blocks. */
|
|
3
14
|
export declare function fromClaudeTranscriptJsonl(input: unknown, fallbackSessionId?: string): MetadataCaptureEvent | null;
|
|
4
15
|
/** Parse a Claude Code hook payload without retaining prompt/tool/file content. */
|
|
@@ -8,7 +19,7 @@ export declare function fromClaudeCodeHook(input: unknown, captureContent?: bool
|
|
|
8
19
|
* format evolves, so unknown record kinds become a safe capture warning rather
|
|
9
20
|
* than being misrepresented as a complete observation.
|
|
10
21
|
*/
|
|
11
|
-
export declare function fromCodexJsonl(input: unknown, sourceFile?: string, captureContent?: boolean): MetadataCaptureEvent | null;
|
|
22
|
+
export declare function fromCodexJsonl(input: unknown, sourceFile?: string, captureContent?: boolean, inheritedIdentity?: CodexStreamIdentity): MetadataCaptureEvent | null;
|
|
12
23
|
/**
|
|
13
24
|
* Convert a deliberately small event emitted by a VS Code/Copilot companion
|
|
14
25
|
* extension. This is not a transcript importer: workspace paths, chat text,
|
package/dist/src/providers.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.codexStreamIdentity = codexStreamIdentity;
|
|
3
4
|
exports.fromClaudeTranscriptJsonl = fromClaudeTranscriptJsonl;
|
|
4
5
|
exports.fromClaudeCodeHook = fromClaudeCodeHook;
|
|
5
6
|
exports.fromCodexJsonl = fromCodexJsonl;
|
|
@@ -24,6 +25,29 @@ function opaqueId(value, fallback) {
|
|
|
24
25
|
return (candidate ||
|
|
25
26
|
(0, node_crypto_1.createHash)("sha256").update(fallback).digest("hex").slice(0, 32));
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Current Codex rollout logs put stream identity on `session_meta.payload`,
|
|
30
|
+
* while subsequent records carry only turn/item identifiers. Keep this small
|
|
31
|
+
* helper content-blind so the importer can safely inherit identity without
|
|
32
|
+
* inspecting prompts, responses, tool arguments, or reasoning.
|
|
33
|
+
*/
|
|
34
|
+
function codexStreamIdentity(input) {
|
|
35
|
+
const record = asRecord(input);
|
|
36
|
+
const payload = asRecord(record.payload);
|
|
37
|
+
const kind = string(record.type) || string(record.kind);
|
|
38
|
+
return {
|
|
39
|
+
sessionId: string(record.session_id) ||
|
|
40
|
+
string(record.sessionId) ||
|
|
41
|
+
string(payload.session_id) ||
|
|
42
|
+
string(payload.sessionId) ||
|
|
43
|
+
(kind === "session_meta" ? string(payload.id) : undefined),
|
|
44
|
+
clientVersion: string(record.client_version) ||
|
|
45
|
+
string(record.clientVersion) ||
|
|
46
|
+
string(payload.cli_version) ||
|
|
47
|
+
string(payload.client_version) ||
|
|
48
|
+
string(payload.clientVersion),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
27
51
|
const relationships = new Set([
|
|
28
52
|
"ROOT",
|
|
29
53
|
"RESUMED_FROM",
|
|
@@ -57,7 +81,11 @@ function usagePayload(value) {
|
|
|
57
81
|
inputTokens: ["inputTokens", "input_tokens"],
|
|
58
82
|
outputTokens: ["outputTokens", "output_tokens"],
|
|
59
83
|
cachedInputTokens: ["cachedInputTokens", "cached_input_tokens"],
|
|
60
|
-
reasoningTokens: [
|
|
84
|
+
reasoningTokens: [
|
|
85
|
+
"reasoningTokens",
|
|
86
|
+
"reasoning_tokens",
|
|
87
|
+
"reasoning_output_tokens",
|
|
88
|
+
],
|
|
61
89
|
costMicros: ["costMicros", "cost_micros"],
|
|
62
90
|
};
|
|
63
91
|
for (const [target, keys] of Object.entries(aliases)) {
|
|
@@ -219,17 +247,31 @@ function fromClaudeCodeHook(input, captureContent = false) {
|
|
|
219
247
|
* format evolves, so unknown record kinds become a safe capture warning rather
|
|
220
248
|
* than being misrepresented as a complete observation.
|
|
221
249
|
*/
|
|
222
|
-
function fromCodexJsonl(input, sourceFile = "session.jsonl", captureContent = false) {
|
|
250
|
+
function fromCodexJsonl(input, sourceFile = "session.jsonl", captureContent = false, inheritedIdentity = {}) {
|
|
223
251
|
const record = asRecord(input);
|
|
224
252
|
const payload = asRecord(record.payload);
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
string(payload.session_id) ||
|
|
228
|
-
string(payload.sessionId);
|
|
253
|
+
const identity = codexStreamIdentity(record);
|
|
254
|
+
const sourceSessionId = identity.sessionId || inheritedIdentity.sessionId;
|
|
229
255
|
if (!sourceSessionId)
|
|
230
256
|
return null;
|
|
231
257
|
const kind = string(record.type) || string(record.kind) || "unknown";
|
|
232
258
|
const eventName = string(payload.type) || string(payload.event) || kind;
|
|
259
|
+
// Current Codex emits these bookkeeping/wrapper records alongside the
|
|
260
|
+
// canonical response_item/token_usage_record stream. They do not describe a
|
|
261
|
+
// distinct user-visible event, and reasoning is provider-private. Ignoring
|
|
262
|
+
// them avoids duplicate evidence and never turns hidden reasoning into a
|
|
263
|
+
// transcript or misleading schema warning.
|
|
264
|
+
if (kind === "turn_context" ||
|
|
265
|
+
kind === "world_state" ||
|
|
266
|
+
(kind === "response_item" && eventName === "reasoning") ||
|
|
267
|
+
(kind === "event_msg" && [
|
|
268
|
+
"item_completed",
|
|
269
|
+
"token_count",
|
|
270
|
+
"thread_settings_applied",
|
|
271
|
+
"task_started",
|
|
272
|
+
"task_complete",
|
|
273
|
+
].includes(eventName)))
|
|
274
|
+
return null;
|
|
233
275
|
let type = "capture.warning";
|
|
234
276
|
if (kind === "session_meta" ||
|
|
235
277
|
eventName === "session_started" ||
|
|
@@ -244,12 +286,12 @@ function fromCodexJsonl(input, sourceFile = "session.jsonl", captureContent = fa
|
|
|
244
286
|
else if (eventName === "task_complete" || eventName === "session_ended")
|
|
245
287
|
type = "session.ended";
|
|
246
288
|
else if (kind === "response_item" &&
|
|
247
|
-
(string(payload.type)
|
|
248
|
-
string(payload.item_type)
|
|
289
|
+
(["function_call", "custom_tool_call"].includes(string(payload.type) || "") ||
|
|
290
|
+
["function_call", "custom_tool_call"].includes(string(payload.item_type) || "")))
|
|
249
291
|
type = "tool.started";
|
|
250
292
|
else if (kind === "response_item" &&
|
|
251
|
-
(string(payload.type)
|
|
252
|
-
string(payload.item_type)
|
|
293
|
+
(["function_call_output", "custom_tool_call_output"].includes(string(payload.type) || "") ||
|
|
294
|
+
["function_call_output", "custom_tool_call_output"].includes(string(payload.item_type) || "")))
|
|
253
295
|
type = "tool.completed";
|
|
254
296
|
else if (eventName === "subagent_started")
|
|
255
297
|
type = "subagent.started";
|
|
@@ -259,11 +301,13 @@ function fromCodexJsonl(input, sourceFile = "session.jsonl", captureContent = fa
|
|
|
259
301
|
type = "message.recorded";
|
|
260
302
|
else if (eventName === "user_message" || eventName === "agent_message")
|
|
261
303
|
type = "message.recorded";
|
|
262
|
-
else if (
|
|
304
|
+
else if (kind === "token_usage_record" ||
|
|
305
|
+
eventName === "usage" ||
|
|
306
|
+
eventName === "usage_recorded")
|
|
263
307
|
type = "usage.recorded";
|
|
264
308
|
const lineId = string(record.id) ||
|
|
265
309
|
string(payload.id) ||
|
|
266
|
-
`${sourceFile}:${kind}:${record.timestamp || ""}:${eventName}`;
|
|
310
|
+
`${sourceFile}:${kind}:${record.ordinal || ""}:${record.timestamp || ""}:${eventName}`;
|
|
267
311
|
const parent = string(payload.parent_session_id) ||
|
|
268
312
|
string(payload.parentSessionId) ||
|
|
269
313
|
string(record.parent_session_id) ||
|
|
@@ -274,9 +318,12 @@ function fromCodexJsonl(input, sourceFile = "session.jsonl", captureContent = fa
|
|
|
274
318
|
string(payload.text);
|
|
275
319
|
const toolName = string(payload.name) || string(payload.tool_name);
|
|
276
320
|
const toolInput = string(payload.arguments) ||
|
|
321
|
+
string(payload.input) ||
|
|
277
322
|
(payload.arguments && typeof payload.arguments === "object"
|
|
278
323
|
? JSON.stringify(payload.arguments)
|
|
279
|
-
:
|
|
324
|
+
: payload.input && typeof payload.input === "object"
|
|
325
|
+
? JSON.stringify(payload.input)
|
|
326
|
+
: undefined);
|
|
280
327
|
const toolOutput = textParts(payload.output) || string(payload.result);
|
|
281
328
|
const visible = type === "message.recorded"
|
|
282
329
|
? transcript(role === "assistant" || eventName === "agent_message"
|
|
@@ -299,7 +346,7 @@ function fromCodexJsonl(input, sourceFile = "session.jsonl", captureContent = fa
|
|
|
299
346
|
adapter: "codex",
|
|
300
347
|
provider: "codex",
|
|
301
348
|
clientName: "Codex",
|
|
302
|
-
clientVersion:
|
|
349
|
+
clientVersion: identity.clientVersion || inheritedIdentity.clientVersion,
|
|
303
350
|
sessionId: sourceSessionId,
|
|
304
351
|
sourceEventId: opaqueId(lineId, sourceSessionId),
|
|
305
352
|
occurredAt: timestamp(record.timestamp || payload.timestamp),
|
|
@@ -308,7 +355,7 @@ function fromCodexJsonl(input, sourceFile = "session.jsonl", captureContent = fa
|
|
|
308
355
|
recordKind: kind,
|
|
309
356
|
eventName,
|
|
310
357
|
sourceFilePresent: Boolean(sourceFile),
|
|
311
|
-
...usagePayload(payload),
|
|
358
|
+
...usagePayload(kind === "token_usage_record" ? asRecord(payload.usage) : payload),
|
|
312
359
|
},
|
|
313
360
|
sourceGenerationId: generation(record, payload),
|
|
314
361
|
relationship: relationship(payload.relationship ||
|
package/package.json
CHANGED