@gobing-ai/ts-llm-jsonl-importer 0.4.35 → 0.4.36
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/mappers.d.ts +1 -1
- package/dist/mappers.d.ts.map +1 -1
- package/dist/mappers.js +94 -37
- package/package.json +4 -4
- package/src/mappers.ts +97 -38
package/dist/mappers.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare function argsDigest(args: unknown): string;
|
|
|
5
5
|
/** Map a Claude Code JSONL record into one or more ETL split entries. */
|
|
6
6
|
export declare function claudeSplit(raw: Record<string, unknown>): readonly SplitEntry[];
|
|
7
7
|
/** Map a Pi JSONL record into one or more ETL split entries. */
|
|
8
|
-
export declare function piSplit(raw: unknown): readonly SplitEntry[];
|
|
8
|
+
export declare function piSplit(raw: Record<string, unknown>, context?: TransformContext): readonly SplitEntry[];
|
|
9
9
|
/** Map an OMP JSONL record into one or more ETL split entries. */
|
|
10
10
|
export declare function ompSplit(raw: Record<string, unknown>, context?: TransformContext): readonly SplitEntry[];
|
|
11
11
|
/** Timing signals carried by an OMP toolResult message envelope (task 0564 R1). */
|
package/dist/mappers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mappers.d.ts","sourceRoot":"","sources":["../src/mappers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAmExE,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAGhD;AAqDD,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,UAAU,EAAE,CA6G/E;AAMD,gEAAgE;AAChE,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"mappers.d.ts","sourceRoot":"","sources":["../src/mappers.ts"],"names":[],"mappings":"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"}
|
package/dist/mappers.js
CHANGED
|
@@ -226,24 +226,62 @@ export function claudeSplit(raw) {
|
|
|
226
226
|
// Pi mapper
|
|
227
227
|
// ---------------------------------------------------------------------------
|
|
228
228
|
/** Map a Pi JSONL record into one or more ETL split entries. */
|
|
229
|
-
export function piSplit(raw) {
|
|
229
|
+
export function piSplit(raw, context) {
|
|
230
|
+
// Pi session files are top-level event envelopes: `{type, id, parentId, timestamp,
|
|
231
|
+
// message: {role, content, model, usage, provider, ...}}`. Newer records carry `recordType`
|
|
232
|
+
// instead of `type` with `role`/`text` at the top level. The top-level `id` is an event id,
|
|
233
|
+
// never a session id; the session key and sequence come from the source file (context).
|
|
234
|
+
const msg = raw.message;
|
|
235
|
+
const recordType = String(raw.type ?? raw.recordType ?? '');
|
|
236
|
+
const sessionId = sessionIdFromContext(context, raw);
|
|
237
|
+
const seq = context?.sourceLine ?? (typeof raw.seq === 'number' ? raw.seq : 0);
|
|
238
|
+
// Prefer the ISO `timestamp`; a numeric epoch-ms `ts` is converted, never stringified.
|
|
239
|
+
const ts = s(raw.timestamp, raw.createdAt) ??
|
|
240
|
+
(typeof raw.ts === 'number' && Number.isFinite(raw.ts) ? new Date(raw.ts).toISOString() : s(raw.ts)) ??
|
|
241
|
+
new Date(0).toISOString();
|
|
242
|
+
// Meta lifecycle/custom records collapse to one meta row keyed by the source session,
|
|
243
|
+
// never the unique event id, and never a guessed role.
|
|
244
|
+
if (recordType === 'title' ||
|
|
245
|
+
recordType === 'title_change' ||
|
|
246
|
+
recordType === 'service_tier_change' ||
|
|
247
|
+
recordType === 'session_info' ||
|
|
248
|
+
recordType === 'ttsr_injection' ||
|
|
249
|
+
recordType === 'session_init' ||
|
|
250
|
+
recordType === 'session' ||
|
|
251
|
+
recordType === 'model_change' ||
|
|
252
|
+
recordType === 'thinking_level_change' ||
|
|
253
|
+
recordType === 'compaction' ||
|
|
254
|
+
recordType === 'custom' ||
|
|
255
|
+
recordType === 'custom_message' ||
|
|
256
|
+
recordType.startsWith('custom.')) {
|
|
257
|
+
return [
|
|
258
|
+
{
|
|
259
|
+
targetTable: 'history_message',
|
|
260
|
+
record: {
|
|
261
|
+
session_id: sessionId,
|
|
262
|
+
seq,
|
|
263
|
+
role: 'meta',
|
|
264
|
+
record_type: recordType,
|
|
265
|
+
disposition: 'meta',
|
|
266
|
+
ts,
|
|
267
|
+
provenance: 'ambient',
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
];
|
|
271
|
+
}
|
|
230
272
|
const entries = [];
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
const
|
|
234
|
-
const
|
|
235
|
-
const role = mapRole(r.type ?? r.role);
|
|
236
|
-
const recordType = String(r.type ?? '');
|
|
237
|
-
const model = s(r.model, o(r.message).model);
|
|
238
|
-
const cwd = s(r.cwd, r.dir);
|
|
239
|
-
const msg = r.message;
|
|
240
|
-
const usage = msg?.usage;
|
|
273
|
+
const role = piRole(msg?.role ?? raw.role ?? recordType);
|
|
274
|
+
const model = s(raw.model, o(msg).model);
|
|
275
|
+
const cwd = s(raw.cwd, raw.dir);
|
|
276
|
+
const usage = (msg?.usage ?? raw.usage);
|
|
241
277
|
const inputTokens = (usage?.input ?? usage?.input_tokens ?? undefined);
|
|
242
278
|
const outputTokens = (usage?.output ?? usage?.output_tokens ?? undefined);
|
|
243
279
|
const cacheRead = (usage?.cacheRead ?? usage?.cache_read_tokens ?? undefined);
|
|
244
280
|
const cacheWrite = (usage?.cacheWrite ?? usage?.cache_write_tokens ?? undefined);
|
|
245
|
-
const costObj = (msg?.cost ??
|
|
281
|
+
const costObj = (msg?.cost ?? raw.cost);
|
|
246
282
|
const costUsd = typeof costObj?.total === 'number' ? costObj.total : computeCost(inputTokens, outputTokens, model);
|
|
283
|
+
const contentBlocks = msg?.content ?? raw.content;
|
|
284
|
+
const durationMs = typeof msg?.duration === 'number' && Number.isFinite(msg.duration) ? msg.duration : undefined;
|
|
247
285
|
const messageSplitIndex = entries.length;
|
|
248
286
|
entries.push({
|
|
249
287
|
targetTable: 'history_message',
|
|
@@ -254,45 +292,64 @@ export function piSplit(raw) {
|
|
|
254
292
|
record_type: recordType,
|
|
255
293
|
disposition: 'keep',
|
|
256
294
|
ts,
|
|
257
|
-
duration_ms:
|
|
295
|
+
duration_ms: durationMs,
|
|
258
296
|
model: model ?? null,
|
|
259
297
|
input_tokens: inputTokens ?? null,
|
|
260
298
|
output_tokens: outputTokens ?? null,
|
|
261
299
|
cache_read_tokens: cacheRead ?? null,
|
|
262
300
|
cache_write_tokens: cacheWrite ?? null,
|
|
263
301
|
cost_usd: costUsd ?? null,
|
|
264
|
-
content_text: s(
|
|
302
|
+
content_text: extractContentText(contentBlocks) ?? s(raw.content, raw.text, msg?.content) ?? null,
|
|
265
303
|
cwd: cwd ?? null,
|
|
266
304
|
provenance: 'ambient',
|
|
267
305
|
},
|
|
268
306
|
});
|
|
269
|
-
// Tool calls from
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
307
|
+
// Tool calls from content blocks in assistant messages. normalizeOmpToolCall handles both
|
|
308
|
+
// the legacy nested `{toolCall: {...}}` block and pi's flat `{type: "toolCall", id, name,
|
|
309
|
+
// arguments}` block.
|
|
310
|
+
if (Array.isArray(contentBlocks) && role === 'assistant') {
|
|
311
|
+
for (const block of contentBlocks) {
|
|
312
|
+
const call = normalizeOmpToolCall(block);
|
|
313
|
+
if (call === null)
|
|
314
|
+
continue;
|
|
315
|
+
entries.push({
|
|
316
|
+
targetTable: 'history_tool_call',
|
|
317
|
+
record: {
|
|
318
|
+
_messageSplitIndex: messageSplitIndex,
|
|
319
|
+
session_id: sessionId,
|
|
320
|
+
seq,
|
|
321
|
+
tool_name: String(call.name ?? ''),
|
|
322
|
+
// The tool's own call id is the exact join key a toolResult's `toolCallId`
|
|
323
|
+
// matches (see ompSplit / task 0564 R1).
|
|
324
|
+
call_id: s(call.id),
|
|
325
|
+
args_digest: argsDigest(call.input ?? call.arguments),
|
|
326
|
+
args_raw: maybeArgsRaw('pi', String(call.name ?? ''), call.input ?? call.arguments),
|
|
327
|
+
status: 'ok',
|
|
328
|
+
started_at: undefined,
|
|
329
|
+
completed_at: undefined,
|
|
330
|
+
duration_ms: undefined,
|
|
331
|
+
result_bytes: undefined,
|
|
332
|
+
error_text: undefined,
|
|
333
|
+
},
|
|
334
|
+
});
|
|
292
335
|
}
|
|
293
336
|
}
|
|
294
337
|
return entries;
|
|
295
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* Canonicalize a pi message role. pi tool results and bash executions ride user turns (Claude
|
|
341
|
+
* protocol shape); everything unrecognized clamps to `unknown` so pi record types never leak
|
|
342
|
+
* into the role column (task 0577 AC2).
|
|
343
|
+
*/
|
|
344
|
+
function piRole(type) {
|
|
345
|
+
const t = String(type ?? '').toLowerCase();
|
|
346
|
+
if (t === 'toolresult' || t === 'tool_result' || t === 'bashexecution' || t === 'tool')
|
|
347
|
+
return 'user';
|
|
348
|
+
const mapped = mapRole(t);
|
|
349
|
+
if (mapped === 'user' || mapped === 'assistant' || mapped === 'system')
|
|
350
|
+
return mapped;
|
|
351
|
+
return 'unknown';
|
|
352
|
+
}
|
|
296
353
|
// ---------------------------------------------------------------------------
|
|
297
354
|
// OMP mapper (near-identical to pi)
|
|
298
355
|
// ---------------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/ts-llm-jsonl-importer",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.36",
|
|
4
4
|
"description": "@gobing-ai/ts-llm-jsonl-importer — Generic JSONL importer for LLM agent history files.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
"release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-llm-jsonl-importer-v<version> && git push --tags' && exit 1"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@gobing-ai/ts-db": "^0.4.
|
|
51
|
-
"@gobing-ai/ts-runtime": "^0.4.
|
|
52
|
-
"@gobing-ai/ts-utils": "^0.4.
|
|
50
|
+
"@gobing-ai/ts-db": "^0.4.36",
|
|
51
|
+
"@gobing-ai/ts-runtime": "^0.4.36",
|
|
52
|
+
"@gobing-ai/ts-utils": "^0.4.36",
|
|
53
53
|
"zod": "^4.1.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
package/src/mappers.ts
CHANGED
|
@@ -241,25 +241,67 @@ export function claudeSplit(raw: Record<string, unknown>): readonly SplitEntry[]
|
|
|
241
241
|
// ---------------------------------------------------------------------------
|
|
242
242
|
|
|
243
243
|
/** Map a Pi JSONL record into one or more ETL split entries. */
|
|
244
|
-
export function piSplit(raw: unknown): readonly SplitEntry[] {
|
|
244
|
+
export function piSplit(raw: Record<string, unknown>, context?: TransformContext): readonly SplitEntry[] {
|
|
245
|
+
// Pi session files are top-level event envelopes: `{type, id, parentId, timestamp,
|
|
246
|
+
// message: {role, content, model, usage, provider, ...}}`. Newer records carry `recordType`
|
|
247
|
+
// instead of `type` with `role`/`text` at the top level. The top-level `id` is an event id,
|
|
248
|
+
// never a session id; the session key and sequence come from the source file (context).
|
|
249
|
+
const msg = raw.message as Record<string, unknown> | undefined;
|
|
250
|
+
const recordType = String(raw.type ?? raw.recordType ?? '');
|
|
251
|
+
const sessionId = sessionIdFromContext(context, raw);
|
|
252
|
+
const seq = context?.sourceLine ?? (typeof raw.seq === 'number' ? raw.seq : 0);
|
|
253
|
+
// Prefer the ISO `timestamp`; a numeric epoch-ms `ts` is converted, never stringified.
|
|
254
|
+
const ts =
|
|
255
|
+
s(raw.timestamp, raw.createdAt) ??
|
|
256
|
+
(typeof raw.ts === 'number' && Number.isFinite(raw.ts) ? new Date(raw.ts).toISOString() : s(raw.ts)) ??
|
|
257
|
+
new Date(0).toISOString();
|
|
258
|
+
|
|
259
|
+
// Meta lifecycle/custom records collapse to one meta row keyed by the source session,
|
|
260
|
+
// never the unique event id, and never a guessed role.
|
|
261
|
+
if (
|
|
262
|
+
recordType === 'title' ||
|
|
263
|
+
recordType === 'title_change' ||
|
|
264
|
+
recordType === 'service_tier_change' ||
|
|
265
|
+
recordType === 'session_info' ||
|
|
266
|
+
recordType === 'ttsr_injection' ||
|
|
267
|
+
recordType === 'session_init' ||
|
|
268
|
+
recordType === 'session' ||
|
|
269
|
+
recordType === 'model_change' ||
|
|
270
|
+
recordType === 'thinking_level_change' ||
|
|
271
|
+
recordType === 'compaction' ||
|
|
272
|
+
recordType === 'custom' ||
|
|
273
|
+
recordType === 'custom_message' ||
|
|
274
|
+
recordType.startsWith('custom.')
|
|
275
|
+
) {
|
|
276
|
+
return [
|
|
277
|
+
{
|
|
278
|
+
targetTable: 'history_message',
|
|
279
|
+
record: {
|
|
280
|
+
session_id: sessionId,
|
|
281
|
+
seq,
|
|
282
|
+
role: 'meta',
|
|
283
|
+
record_type: recordType,
|
|
284
|
+
disposition: 'meta',
|
|
285
|
+
ts,
|
|
286
|
+
provenance: 'ambient',
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
];
|
|
290
|
+
}
|
|
291
|
+
|
|
245
292
|
const entries: SplitEntry[] = [];
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
const role = mapRole(r.type ?? r.role);
|
|
251
|
-
const recordType = String(r.type ?? '');
|
|
252
|
-
const model = s(r.model, o(r.message).model);
|
|
253
|
-
const cwd = s(r.cwd, r.dir);
|
|
254
|
-
|
|
255
|
-
const msg = r.message as Record<string, unknown> | undefined;
|
|
256
|
-
const usage = msg?.usage as Record<string, unknown> | undefined;
|
|
293
|
+
const role = piRole(msg?.role ?? raw.role ?? recordType);
|
|
294
|
+
const model = s(raw.model, o(msg).model);
|
|
295
|
+
const cwd = s(raw.cwd, raw.dir);
|
|
296
|
+
const usage = (msg?.usage ?? raw.usage) as Record<string, unknown> | undefined;
|
|
257
297
|
const inputTokens = (usage?.input ?? usage?.input_tokens ?? undefined) as number | undefined;
|
|
258
298
|
const outputTokens = (usage?.output ?? usage?.output_tokens ?? undefined) as number | undefined;
|
|
259
299
|
const cacheRead = (usage?.cacheRead ?? usage?.cache_read_tokens ?? undefined) as number | undefined;
|
|
260
300
|
const cacheWrite = (usage?.cacheWrite ?? usage?.cache_write_tokens ?? undefined) as number | undefined;
|
|
261
|
-
const costObj = (msg?.cost ??
|
|
301
|
+
const costObj = (msg?.cost ?? raw.cost) as Record<string, unknown> | undefined;
|
|
262
302
|
const costUsd = typeof costObj?.total === 'number' ? costObj.total : computeCost(inputTokens, outputTokens, model);
|
|
303
|
+
const contentBlocks = msg?.content ?? raw.content;
|
|
304
|
+
const durationMs = typeof msg?.duration === 'number' && Number.isFinite(msg.duration) ? msg.duration : undefined;
|
|
263
305
|
|
|
264
306
|
const messageSplitIndex = entries.length;
|
|
265
307
|
|
|
@@ -272,48 +314,65 @@ export function piSplit(raw: unknown): readonly SplitEntry[] {
|
|
|
272
314
|
record_type: recordType,
|
|
273
315
|
disposition: 'keep',
|
|
274
316
|
ts,
|
|
275
|
-
duration_ms:
|
|
317
|
+
duration_ms: durationMs,
|
|
276
318
|
model: model ?? null,
|
|
277
319
|
input_tokens: inputTokens ?? null,
|
|
278
320
|
output_tokens: outputTokens ?? null,
|
|
279
321
|
cache_read_tokens: cacheRead ?? null,
|
|
280
322
|
cache_write_tokens: cacheWrite ?? null,
|
|
281
323
|
cost_usd: costUsd ?? null,
|
|
282
|
-
content_text: s(
|
|
324
|
+
content_text: extractContentText(contentBlocks) ?? s(raw.content, raw.text, msg?.content) ?? null,
|
|
283
325
|
cwd: cwd ?? null,
|
|
284
326
|
provenance: 'ambient',
|
|
285
327
|
},
|
|
286
328
|
});
|
|
287
329
|
|
|
288
|
-
// Tool calls from
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
330
|
+
// Tool calls from content blocks in assistant messages. normalizeOmpToolCall handles both
|
|
331
|
+
// the legacy nested `{toolCall: {...}}` block and pi's flat `{type: "toolCall", id, name,
|
|
332
|
+
// arguments}` block.
|
|
333
|
+
if (Array.isArray(contentBlocks) && role === 'assistant') {
|
|
334
|
+
for (const block of contentBlocks as Record<string, unknown>[]) {
|
|
335
|
+
const call = normalizeOmpToolCall(block);
|
|
336
|
+
if (call === null) continue;
|
|
337
|
+
entries.push({
|
|
338
|
+
targetTable: 'history_tool_call',
|
|
339
|
+
record: {
|
|
340
|
+
_messageSplitIndex: messageSplitIndex,
|
|
341
|
+
session_id: sessionId,
|
|
342
|
+
seq,
|
|
343
|
+
tool_name: String(call.name ?? ''),
|
|
344
|
+
// The tool's own call id is the exact join key a toolResult's `toolCallId`
|
|
345
|
+
// matches (see ompSplit / task 0564 R1).
|
|
346
|
+
call_id: s(call.id),
|
|
347
|
+
args_digest: argsDigest(call.input ?? call.arguments),
|
|
348
|
+
args_raw: maybeArgsRaw('pi', String(call.name ?? ''), call.input ?? call.arguments),
|
|
349
|
+
status: 'ok',
|
|
350
|
+
started_at: undefined,
|
|
351
|
+
completed_at: undefined,
|
|
352
|
+
duration_ms: undefined,
|
|
353
|
+
result_bytes: undefined,
|
|
354
|
+
error_text: undefined,
|
|
355
|
+
},
|
|
356
|
+
});
|
|
311
357
|
}
|
|
312
358
|
}
|
|
313
359
|
|
|
314
360
|
return entries;
|
|
315
361
|
}
|
|
316
362
|
|
|
363
|
+
/**
|
|
364
|
+
* Canonicalize a pi message role. pi tool results and bash executions ride user turns (Claude
|
|
365
|
+
* protocol shape); everything unrecognized clamps to `unknown` so pi record types never leak
|
|
366
|
+
* into the role column (task 0577 AC2).
|
|
367
|
+
*/
|
|
368
|
+
function piRole(type: unknown): string {
|
|
369
|
+
const t = String(type ?? '').toLowerCase();
|
|
370
|
+
if (t === 'toolresult' || t === 'tool_result' || t === 'bashexecution' || t === 'tool') return 'user';
|
|
371
|
+
const mapped = mapRole(t);
|
|
372
|
+
if (mapped === 'user' || mapped === 'assistant' || mapped === 'system') return mapped;
|
|
373
|
+
return 'unknown';
|
|
374
|
+
}
|
|
375
|
+
|
|
317
376
|
// ---------------------------------------------------------------------------
|
|
318
377
|
// OMP mapper (near-identical to pi)
|
|
319
378
|
// ---------------------------------------------------------------------------
|