@gobing-ai/ts-llm-jsonl-importer 0.4.25 → 0.4.26
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 +3 -1
- package/dist/mappers.d.ts.map +1 -1
- package/dist/mappers.js +67 -36
- package/package.json +4 -4
- package/src/mappers.ts +65 -37
package/dist/mappers.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { JsonObject, SplitEntry, TransformContext } from './types';
|
|
3
|
+
/** Compute args_digest: sha256 of stable-jsonified, key-sorted, redacted args. */
|
|
4
|
+
export declare function argsDigest(args: unknown): string;
|
|
3
5
|
/** Map a Claude Code JSONL record into one or more ETL split entries. */
|
|
4
6
|
export declare function claudeSplit(raw: Record<string, unknown>): readonly SplitEntry[];
|
|
5
7
|
/** Map a Pi JSONL record into one or more ETL split entries. */
|
|
6
8
|
export declare function piSplit(raw: unknown): readonly SplitEntry[];
|
|
7
9
|
/** Map an OMP JSONL record into one or more ETL split entries. */
|
|
8
|
-
export declare function ompSplit(raw: Record<string, unknown
|
|
10
|
+
export declare function ompSplit(raw: Record<string, unknown>, context?: TransformContext): readonly SplitEntry[];
|
|
9
11
|
/** Map a Codex JSONL record into one or more ETL split entries. */
|
|
10
12
|
export declare function codexSplit(raw: Record<string, unknown>): readonly SplitEntry[];
|
|
11
13
|
/** Map an Antigravity (AGY) JSONL record into one or more ETL split entries. */
|
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;
|
|
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;AA+DxE,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAGhD;AAwBD,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,UAAU,EAAE,CA4G/E;AAMD,gEAAgE;AAChE,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,UAAU,EAAE,CAsE3D;AAMD,kEAAkE;AAClE,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,UAAU,EAAE,CA0GxG;AAoBD,mEAAmE;AACnE,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,UAAU,EAAE,CAiG9E;AAMD,gFAAgF;AAChF,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,UAAU,EAAE,CAgH5E;AAMD,gGAAgG;AAChG,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,UAAU,EAAE,CAgF3G;AAyMD,kEAAkE;AAClE,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,UAAU,EAAE,CAyS7E;AAwDD,wGAAwG;AACxG,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAKxD;AAMD,gDAAgD;AAChD,eAAO,MAAM,gBAAgB,wBAAsE,CAAC;AACpG,4CAA4C;AAC5C,eAAO,MAAM,YAAY,wBAAsE,CAAC;AAChG,6CAA6C;AAC7C,eAAO,MAAM,aAAa,wBAAsE,CAAC;AACjG,+CAA+C;AAC/C,eAAO,MAAM,eAAe,wBAAsE,CAAC;AACnG,6CAA6C;AAC7C,eAAO,MAAM,aAAa,wBAAsE,CAAC;AACjG,8CAA8C;AAC9C,eAAO,MAAM,cAAc,wBAAsE,CAAC;AAClG,gDAAgD;AAChD,eAAO,MAAM,gBAAgB,wBAAsE,CAAC;AAQpG,oDAAoD;AACpD,eAAO,MAAM,aAAa,gCAAoB,CAAC;AAC/C,gDAAgD;AAChD,eAAO,MAAM,SAAS,gCAAoB,CAAC;AAC3C,iDAAiD;AACjD,eAAO,MAAM,UAAU,gCAAoB,CAAC;AAC5C,mDAAmD;AACnD,eAAO,MAAM,YAAY,gCAAoB,CAAC;AAC9C,iDAAiD;AACjD,eAAO,MAAM,UAAU,gCAAoB,CAAC;AAC5C,kDAAkD;AAClD,eAAO,MAAM,WAAW,gCAAoB,CAAC;AAC7C,oDAAoD;AACpD,eAAO,MAAM,aAAa,gCAAoB,CAAC"}
|
package/dist/mappers.js
CHANGED
|
@@ -57,7 +57,7 @@ function detectProvenance(cwd) {
|
|
|
57
57
|
return cwd.includes('/spur') || cwd.includes('/spur-new') ? 'spur-run' : 'ambient';
|
|
58
58
|
}
|
|
59
59
|
/** Compute args_digest: sha256 of stable-jsonified, key-sorted, redacted args. */
|
|
60
|
-
function argsDigest(args) {
|
|
60
|
+
export function argsDigest(args) {
|
|
61
61
|
const redacted = redactArgs(args);
|
|
62
62
|
return sha256(redacted);
|
|
63
63
|
}
|
|
@@ -262,44 +262,59 @@ export function piSplit(raw) {
|
|
|
262
262
|
// OMP mapper (near-identical to pi)
|
|
263
263
|
// ---------------------------------------------------------------------------
|
|
264
264
|
/** Map an OMP JSONL record into one or more ETL split entries. */
|
|
265
|
-
export function ompSplit(raw) {
|
|
265
|
+
export function ompSplit(raw, context) {
|
|
266
|
+
// Current OMP files are top-level event envelopes: `{type: "message", id, timestamp, parentId,
|
|
267
|
+
// message: {role, content, model, usage, cost, duration, ...}}`. The unique top-level `id` is
|
|
268
|
+
// an event id, never a session id; the session key and sequence come from the source file
|
|
269
|
+
// (context) when available. The legacy direct shape keeps role/type/content at the top level.
|
|
270
|
+
const msg = raw.message;
|
|
266
271
|
const recordType = String(raw.type ?? '');
|
|
272
|
+
const sessionId = sessionIdFromContext(context, raw);
|
|
273
|
+
const seq = context?.sourceLine ?? (typeof raw.seq === 'number' ? raw.seq : 0);
|
|
274
|
+
const ts = s(raw.ts, raw.timestamp, raw.createdAt) ?? new Date(0).toISOString();
|
|
275
|
+
// Meta lifecycle/custom records — including the current `custom.*` events and non-message
|
|
276
|
+
// lifecycle types — collapse to one meta row keyed by the source session, never the unique
|
|
277
|
+
// event id, and never a guessed role.
|
|
267
278
|
if (recordType === 'title' ||
|
|
268
279
|
recordType === 'title_change' ||
|
|
269
280
|
recordType === 'service_tier_change' ||
|
|
270
281
|
recordType === 'ttsr_injection' ||
|
|
271
282
|
recordType === 'session_init' ||
|
|
272
|
-
recordType === '
|
|
283
|
+
recordType === 'session' ||
|
|
284
|
+
recordType === 'model_change' ||
|
|
285
|
+
recordType === 'thinking_level_change' ||
|
|
286
|
+
recordType === 'compaction' ||
|
|
287
|
+
recordType === 'custom' ||
|
|
288
|
+
recordType === 'custom_message' ||
|
|
289
|
+
recordType.startsWith('custom.')) {
|
|
273
290
|
return [
|
|
274
291
|
{
|
|
275
292
|
targetTable: 'history_message',
|
|
276
293
|
record: {
|
|
277
|
-
session_id:
|
|
278
|
-
seq
|
|
294
|
+
session_id: sessionId,
|
|
295
|
+
seq,
|
|
279
296
|
role: 'meta',
|
|
280
297
|
record_type: recordType,
|
|
281
298
|
disposition: 'meta',
|
|
282
|
-
ts
|
|
299
|
+
ts,
|
|
283
300
|
provenance: 'ambient',
|
|
284
301
|
},
|
|
285
302
|
},
|
|
286
303
|
];
|
|
287
304
|
}
|
|
288
305
|
const entries = [];
|
|
289
|
-
const
|
|
290
|
-
const
|
|
291
|
-
const ts = s(raw.ts, raw.timestamp, raw.createdAt) ?? new Date(0).toISOString();
|
|
292
|
-
const role = mapRole(raw.type ?? raw.role);
|
|
293
|
-
const model = s(raw.model, o(raw.message).model);
|
|
306
|
+
const role = mapRole(msg?.role ?? raw.type ?? raw.role);
|
|
307
|
+
const model = s(raw.model, o(msg).model);
|
|
294
308
|
const cwd = s(raw.cwd, raw.dir);
|
|
295
|
-
const
|
|
296
|
-
const usage = msg?.usage;
|
|
309
|
+
const usage = (msg?.usage ?? raw.usage);
|
|
297
310
|
const inputTokens = (usage?.input ?? usage?.input_tokens ?? undefined);
|
|
298
311
|
const outputTokens = (usage?.output ?? usage?.output_tokens ?? undefined);
|
|
299
312
|
const cacheRead = (usage?.cacheRead ?? usage?.cache_read_tokens ?? undefined);
|
|
300
313
|
const cacheWrite = (usage?.cacheWrite ?? usage?.cache_write_tokens ?? undefined);
|
|
301
314
|
const costObj = (msg?.cost ?? raw.cost);
|
|
302
315
|
const costUsd = typeof costObj?.total === 'number' ? costObj.total : computeCost(inputTokens, outputTokens, model);
|
|
316
|
+
const contentBlocks = msg?.content ?? raw.content;
|
|
317
|
+
const durationMs = typeof msg?.duration === 'number' && Number.isFinite(msg.duration) ? msg.duration : undefined;
|
|
303
318
|
const messageSplitIndex = entries.length;
|
|
304
319
|
entries.push({
|
|
305
320
|
targetTable: 'history_message',
|
|
@@ -310,43 +325,59 @@ export function ompSplit(raw) {
|
|
|
310
325
|
record_type: recordType,
|
|
311
326
|
disposition: 'keep',
|
|
312
327
|
ts,
|
|
313
|
-
duration_ms:
|
|
328
|
+
duration_ms: durationMs,
|
|
314
329
|
model: model ?? null,
|
|
315
330
|
input_tokens: inputTokens ?? null,
|
|
316
331
|
output_tokens: outputTokens ?? null,
|
|
317
332
|
cache_read_tokens: cacheRead ?? null,
|
|
318
333
|
cache_write_tokens: cacheWrite ?? null,
|
|
319
334
|
cost_usd: costUsd ?? null,
|
|
320
|
-
content_text: s(raw.content, raw.text, msg?.content) ?? null,
|
|
335
|
+
content_text: extractContentText(contentBlocks) ?? s(raw.content, raw.text, msg?.content) ?? null,
|
|
321
336
|
cwd: cwd ?? null,
|
|
322
337
|
provenance: detectProvenance(cwd),
|
|
323
338
|
},
|
|
324
339
|
});
|
|
325
|
-
if (Array.isArray(
|
|
326
|
-
for (const block of
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
}
|
|
340
|
+
if (Array.isArray(contentBlocks) && role === 'assistant') {
|
|
341
|
+
for (const block of contentBlocks) {
|
|
342
|
+
const call = normalizeOmpToolCall(block);
|
|
343
|
+
if (call === null)
|
|
344
|
+
continue;
|
|
345
|
+
entries.push({
|
|
346
|
+
targetTable: 'history_tool_call',
|
|
347
|
+
record: {
|
|
348
|
+
_messageSplitIndex: messageSplitIndex,
|
|
349
|
+
session_id: sessionId,
|
|
350
|
+
seq,
|
|
351
|
+
tool_name: String(call.name ?? ''),
|
|
352
|
+
args_digest: argsDigest(call.input ?? call.arguments),
|
|
353
|
+
status: 'ok',
|
|
354
|
+
started_at: undefined,
|
|
355
|
+
completed_at: undefined,
|
|
356
|
+
duration_ms: undefined,
|
|
357
|
+
result_bytes: undefined,
|
|
358
|
+
error_text: undefined,
|
|
359
|
+
},
|
|
360
|
+
});
|
|
346
361
|
}
|
|
347
362
|
}
|
|
348
363
|
return entries;
|
|
349
364
|
}
|
|
365
|
+
/**
|
|
366
|
+
* Normalize an OMP assistant content block to a tool-call object, or null when the block is not a
|
|
367
|
+
* call. Supports both the legacy nested `{toolCall: {...}}` block and the current flat
|
|
368
|
+
* `{type: "toolCall", id, name, arguments}` block. Exactly one history_tool_call row is emitted
|
|
369
|
+
* per call block.
|
|
370
|
+
*/
|
|
371
|
+
function normalizeOmpToolCall(block) {
|
|
372
|
+
if (typeof block !== 'object' || block === null)
|
|
373
|
+
return null;
|
|
374
|
+
const nested = o(block.toolCall);
|
|
375
|
+
if (Object.keys(nested).length > 0)
|
|
376
|
+
return nested;
|
|
377
|
+
if (block.type === 'toolCall')
|
|
378
|
+
return block;
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
350
381
|
// ---------------------------------------------------------------------------
|
|
351
382
|
// Codex mapper
|
|
352
383
|
// ---------------------------------------------------------------------------
|
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.26",
|
|
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.26",
|
|
51
|
+
"@gobing-ai/ts-runtime": "^0.4.26",
|
|
52
|
+
"@gobing-ai/ts-utils": "^0.4.26",
|
|
53
53
|
"zod": "^4.1.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
package/src/mappers.ts
CHANGED
|
@@ -64,7 +64,7 @@ function detectProvenance(cwd?: string): 'ambient' | 'spur-run' {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/** Compute args_digest: sha256 of stable-jsonified, key-sorted, redacted args. */
|
|
67
|
-
function argsDigest(args: unknown): string {
|
|
67
|
+
export function argsDigest(args: unknown): string {
|
|
68
68
|
const redacted = redactArgs(args);
|
|
69
69
|
return sha256(redacted);
|
|
70
70
|
}
|
|
@@ -284,26 +284,44 @@ export function piSplit(raw: unknown): readonly SplitEntry[] {
|
|
|
284
284
|
// ---------------------------------------------------------------------------
|
|
285
285
|
|
|
286
286
|
/** Map an OMP JSONL record into one or more ETL split entries. */
|
|
287
|
-
export function ompSplit(raw: Record<string, unknown
|
|
287
|
+
export function ompSplit(raw: Record<string, unknown>, context?: TransformContext): readonly SplitEntry[] {
|
|
288
|
+
// Current OMP files are top-level event envelopes: `{type: "message", id, timestamp, parentId,
|
|
289
|
+
// message: {role, content, model, usage, cost, duration, ...}}`. The unique top-level `id` is
|
|
290
|
+
// an event id, never a session id; the session key and sequence come from the source file
|
|
291
|
+
// (context) when available. The legacy direct shape keeps role/type/content at the top level.
|
|
292
|
+
const msg = raw.message as Record<string, unknown> | undefined;
|
|
288
293
|
const recordType = String(raw.type ?? '');
|
|
294
|
+
const sessionId = sessionIdFromContext(context, raw);
|
|
295
|
+
const seq = context?.sourceLine ?? (typeof raw.seq === 'number' ? raw.seq : 0);
|
|
296
|
+
const ts = s(raw.ts, raw.timestamp, raw.createdAt) ?? new Date(0).toISOString();
|
|
297
|
+
|
|
298
|
+
// Meta lifecycle/custom records — including the current `custom.*` events and non-message
|
|
299
|
+
// lifecycle types — collapse to one meta row keyed by the source session, never the unique
|
|
300
|
+
// event id, and never a guessed role.
|
|
289
301
|
if (
|
|
290
302
|
recordType === 'title' ||
|
|
291
303
|
recordType === 'title_change' ||
|
|
292
304
|
recordType === 'service_tier_change' ||
|
|
293
305
|
recordType === 'ttsr_injection' ||
|
|
294
306
|
recordType === 'session_init' ||
|
|
295
|
-
recordType === '
|
|
307
|
+
recordType === 'session' ||
|
|
308
|
+
recordType === 'model_change' ||
|
|
309
|
+
recordType === 'thinking_level_change' ||
|
|
310
|
+
recordType === 'compaction' ||
|
|
311
|
+
recordType === 'custom' ||
|
|
312
|
+
recordType === 'custom_message' ||
|
|
313
|
+
recordType.startsWith('custom.')
|
|
296
314
|
) {
|
|
297
315
|
return [
|
|
298
316
|
{
|
|
299
317
|
targetTable: 'history_message',
|
|
300
318
|
record: {
|
|
301
|
-
session_id:
|
|
302
|
-
seq
|
|
319
|
+
session_id: sessionId,
|
|
320
|
+
seq,
|
|
303
321
|
role: 'meta',
|
|
304
322
|
record_type: recordType,
|
|
305
323
|
disposition: 'meta',
|
|
306
|
-
ts
|
|
324
|
+
ts,
|
|
307
325
|
provenance: 'ambient',
|
|
308
326
|
},
|
|
309
327
|
},
|
|
@@ -311,21 +329,18 @@ export function ompSplit(raw: Record<string, unknown>): readonly SplitEntry[] {
|
|
|
311
329
|
}
|
|
312
330
|
|
|
313
331
|
const entries: SplitEntry[] = [];
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
const ts = s(raw.ts, raw.timestamp, raw.createdAt) ?? new Date(0).toISOString();
|
|
317
|
-
const role = mapRole(raw.type ?? raw.role);
|
|
318
|
-
const model = s(raw.model, o(raw.message).model);
|
|
332
|
+
const role = mapRole(msg?.role ?? raw.type ?? raw.role);
|
|
333
|
+
const model = s(raw.model, o(msg).model);
|
|
319
334
|
const cwd = s(raw.cwd, raw.dir);
|
|
320
|
-
|
|
321
|
-
const msg = raw.message as Record<string, unknown> | undefined;
|
|
322
|
-
const usage = msg?.usage as Record<string, unknown> | undefined;
|
|
335
|
+
const usage = (msg?.usage ?? raw.usage) as Record<string, unknown> | undefined;
|
|
323
336
|
const inputTokens = (usage?.input ?? usage?.input_tokens ?? undefined) as number | undefined;
|
|
324
337
|
const outputTokens = (usage?.output ?? usage?.output_tokens ?? undefined) as number | undefined;
|
|
325
338
|
const cacheRead = (usage?.cacheRead ?? usage?.cache_read_tokens ?? undefined) as number | undefined;
|
|
326
339
|
const cacheWrite = (usage?.cacheWrite ?? usage?.cache_write_tokens ?? undefined) as number | undefined;
|
|
327
340
|
const costObj = (msg?.cost ?? raw.cost) as Record<string, unknown> | undefined;
|
|
328
341
|
const costUsd = typeof costObj?.total === 'number' ? costObj.total : computeCost(inputTokens, outputTokens, model);
|
|
342
|
+
const contentBlocks = msg?.content ?? raw.content;
|
|
343
|
+
const durationMs = typeof msg?.duration === 'number' && Number.isFinite(msg.duration) ? msg.duration : undefined;
|
|
329
344
|
|
|
330
345
|
const messageSplitIndex = entries.length;
|
|
331
346
|
|
|
@@ -338,46 +353,59 @@ export function ompSplit(raw: Record<string, unknown>): readonly SplitEntry[] {
|
|
|
338
353
|
record_type: recordType,
|
|
339
354
|
disposition: 'keep',
|
|
340
355
|
ts,
|
|
341
|
-
duration_ms:
|
|
356
|
+
duration_ms: durationMs,
|
|
342
357
|
model: model ?? null,
|
|
343
358
|
input_tokens: inputTokens ?? null,
|
|
344
359
|
output_tokens: outputTokens ?? null,
|
|
345
360
|
cache_read_tokens: cacheRead ?? null,
|
|
346
361
|
cache_write_tokens: cacheWrite ?? null,
|
|
347
362
|
cost_usd: costUsd ?? null,
|
|
348
|
-
content_text: s(raw.content, raw.text, msg?.content) ?? null,
|
|
363
|
+
content_text: extractContentText(contentBlocks) ?? s(raw.content, raw.text, msg?.content) ?? null,
|
|
349
364
|
cwd: cwd ?? null,
|
|
350
365
|
provenance: detectProvenance(cwd),
|
|
351
366
|
},
|
|
352
367
|
});
|
|
353
368
|
|
|
354
|
-
if (Array.isArray(
|
|
355
|
-
for (const block of
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
369
|
+
if (Array.isArray(contentBlocks) && role === 'assistant') {
|
|
370
|
+
for (const block of contentBlocks as Record<string, unknown>[]) {
|
|
371
|
+
const call = normalizeOmpToolCall(block);
|
|
372
|
+
if (call === null) continue;
|
|
373
|
+
entries.push({
|
|
374
|
+
targetTable: 'history_tool_call',
|
|
375
|
+
record: {
|
|
376
|
+
_messageSplitIndex: messageSplitIndex,
|
|
377
|
+
session_id: sessionId,
|
|
378
|
+
seq,
|
|
379
|
+
tool_name: String(call.name ?? ''),
|
|
380
|
+
args_digest: argsDigest(call.input ?? call.arguments),
|
|
381
|
+
status: 'ok',
|
|
382
|
+
started_at: undefined,
|
|
383
|
+
completed_at: undefined,
|
|
384
|
+
duration_ms: undefined,
|
|
385
|
+
result_bytes: undefined,
|
|
386
|
+
error_text: undefined,
|
|
387
|
+
},
|
|
388
|
+
});
|
|
375
389
|
}
|
|
376
390
|
}
|
|
377
391
|
|
|
378
392
|
return entries;
|
|
379
393
|
}
|
|
380
394
|
|
|
395
|
+
/**
|
|
396
|
+
* Normalize an OMP assistant content block to a tool-call object, or null when the block is not a
|
|
397
|
+
* call. Supports both the legacy nested `{toolCall: {...}}` block and the current flat
|
|
398
|
+
* `{type: "toolCall", id, name, arguments}` block. Exactly one history_tool_call row is emitted
|
|
399
|
+
* per call block.
|
|
400
|
+
*/
|
|
401
|
+
function normalizeOmpToolCall(block: Record<string, unknown>): Record<string, unknown> | null {
|
|
402
|
+
if (typeof block !== 'object' || block === null) return null;
|
|
403
|
+
const nested = o(block.toolCall);
|
|
404
|
+
if (Object.keys(nested).length > 0) return nested;
|
|
405
|
+
if (block.type === 'toolCall') return block;
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
|
|
381
409
|
// ---------------------------------------------------------------------------
|
|
382
410
|
// Codex mapper
|
|
383
411
|
// ---------------------------------------------------------------------------
|