@gobing-ai/ts-llm-jsonl-importer 0.4.41 → 0.4.42

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/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { HistoryImportError } from './errors';
2
2
  export { sha256, stableJson } from './hash';
3
3
  export { runJsonlImport } from './importer';
4
4
  export { applyHistoryImportSchema, normalizeSourceFilePaths } from './jsonl-importer-dao';
5
+ export { sessionIdFromSourcePath } from './mappers';
5
6
  export { type OpenCodeImportOptions, runOpenCodeImport } from './opencode-importer';
6
7
  export { DEFAULT_REDACTION_RULES, redactRecord, redactValue } from './redaction';
7
8
  export { HISTORY_IMPORT_SCHEMA_SQL } from './schema-sql';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EAAE,KAAK,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAE,uBAAuB,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EACH,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,GAC3B,MAAM,WAAW,CAAC;AACnB,YAAY,EACR,cAAc,EACd,WAAW,EACX,UAAU,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,gBAAgB,GACnB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,KAAK,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAE,uBAAuB,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EACH,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,GAC3B,MAAM,WAAW,CAAC;AACnB,YAAY,EACR,cAAc,EACd,WAAW,EACX,UAAU,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,gBAAgB,GACnB,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ export { HistoryImportError } from './errors.js';
2
2
  export { sha256, stableJson } from './hash.js';
3
3
  export { runJsonlImport } from './importer.js';
4
4
  export { applyHistoryImportSchema, normalizeSourceFilePaths } from './jsonl-importer-dao.js';
5
+ export { sessionIdFromSourcePath } from './mappers.js';
5
6
  export { runOpenCodeImport } from './opencode-importer.js';
6
7
  export { DEFAULT_REDACTION_RULES, redactRecord, redactValue } from './redaction.js';
7
8
  export { HISTORY_IMPORT_SCHEMA_SQL } from './schema-sql.js';
package/dist/mappers.d.ts CHANGED
@@ -53,11 +53,19 @@ export interface ClaudeToolResultTiming {
53
53
  */
54
54
  export declare function claudeToolResultTiming(raw: Record<string, unknown>): ClaudeToolResultTiming | null;
55
55
  /** Map a Codex JSONL record into one or more ETL split entries. */
56
- export declare function codexSplit(raw: Record<string, unknown>): readonly SplitEntry[];
56
+ export declare function codexSplit(raw: Record<string, unknown>, context?: TransformContext): readonly SplitEntry[];
57
57
  /** Map an Antigravity (AGY) JSONL record into one or more ETL split entries. */
58
- export declare function agySplit(raw: Record<string, unknown>): readonly SplitEntry[];
58
+ export declare function agySplit(raw: Record<string, unknown>, context?: TransformContext): readonly SplitEntry[];
59
59
  /** Map Gemini CLI message, tool, and session-state events into the forensic contract tables. */
60
60
  export declare function geminiSplit(raw: Record<string, unknown>, context?: TransformContext): readonly SplitEntry[];
61
+ /**
62
+ * Pure helper extracting canonical session identity from source transcript paths (task 0638 R4).
63
+ *
64
+ * Normalizes POSIX/Windows separators and returns only valid UUID-shaped identifiers:
65
+ * - agy: segment immediately following `/brain/`
66
+ * - codex: trailing UUID before `.jsonl` in a filename beginning with `rollout-`
67
+ */
68
+ export declare function sessionIdFromSourcePath(source: string, sourceFile: string): string | undefined;
61
69
  /** Map a Grok JSONL record into one or more ETL split entries. */
62
70
  export declare function grokSplit(raw: Record<string, unknown>): readonly SplitEntry[];
63
71
  /** Compute a stable field-shape key from a JSON object: sorted, lowercased, +-joined top-level keys. */
@@ -1 +1 @@
1
- {"version":3,"file":"mappers.d.ts","sourceRoot":"","sources":["../src/mappers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mappers.d.ts","sourceRoot":"","sources":["../src/mappers.ts"],"names":[],"mappings":"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"}
package/dist/mappers.js CHANGED
@@ -549,16 +549,25 @@ export function claudeToolResultTiming(raw) {
549
549
  // Codex mapper
550
550
  // ---------------------------------------------------------------------------
551
551
  /** Map a Codex JSONL record into one or more ETL split entries. */
552
- export function codexSplit(raw) {
552
+ export function codexSplit(raw, context) {
553
553
  const entries = [];
554
554
  const payload = (raw.payload ?? raw);
555
- const sessionId = s(raw.session_id, o(raw.session_meta).id, raw.id) ?? 'unknown';
556
555
  const seq = typeof raw.seq === 'number' ? raw.seq : 0;
557
556
  const ts = timestampOf(raw.timestamp, raw.ts, payload.ts);
558
557
  const recordType = String(raw.type ?? '');
559
558
  const payloadType = String(payload.type ?? '');
559
+ const isShortFormat = recordType === '' && raw.id !== undefined && raw.timestamp !== undefined && raw.instructions !== undefined;
560
+ let explicitSessionId = s(raw.session_id, o(raw.session_meta).id);
561
+ if (explicitSessionId === undefined && recordType === 'session_meta') {
562
+ explicitSessionId = s(payload.id, o(raw.payload).id);
563
+ }
564
+ if (explicitSessionId === undefined && isShortFormat) {
565
+ explicitSessionId = s(raw.id);
566
+ }
567
+ const pathSessionId = context?.sourceFile ? sessionIdFromSourcePath('codex', context.sourceFile) : undefined;
568
+ const sessionId = explicitSessionId ?? pathSessionId ?? 'unknown';
560
569
  // Check for older short format
561
- if (recordType === '' && raw.id && raw.timestamp && raw.instructions) {
570
+ if (isShortFormat) {
562
571
  return [
563
572
  {
564
573
  targetTable: 'history_message',
@@ -711,10 +720,12 @@ export function codexSplit(raw) {
711
720
  // AGY mapper (Antigravity CLI)
712
721
  // ---------------------------------------------------------------------------
713
722
  /** Map an Antigravity (AGY) JSONL record into one or more ETL split entries. */
714
- export function agySplit(raw) {
723
+ export function agySplit(raw, context) {
715
724
  const entries = [];
716
725
  const recordType = String(raw.type ?? '');
717
- const sessionId = s(raw.session_id, raw.conversation_id) ?? 'unknown';
726
+ const explicitSessionId = s(raw.session_id, raw.conversation_id);
727
+ const pathSessionId = context?.sourceFile ? sessionIdFromSourcePath('agy', context.sourceFile) : undefined;
728
+ const sessionId = explicitSessionId ?? pathSessionId ?? 'unknown';
718
729
  const seq = typeof raw.seq === 'number' ? raw.seq : typeof raw.step_index === 'number' ? raw.step_index : 0;
719
730
  const ts = timestampOf(raw.created_at, raw.timestamp, raw.ts);
720
731
  let role;
@@ -902,6 +913,33 @@ export function geminiSplit(raw, context) {
902
913
  }
903
914
  return entries;
904
915
  }
916
+ const UUID_PATTERN = '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}';
917
+ const AGY_BRAIN_REGEX = new RegExp(`(?:^|\\/)brain\\/(${UUID_PATTERN})(?:\\/|$)`, 'i');
918
+ const CODEX_ROLLOUT_REGEX = new RegExp(`(?:^|-|_)(${UUID_PATTERN})$`, 'i');
919
+ /**
920
+ * Pure helper extracting canonical session identity from source transcript paths (task 0638 R4).
921
+ *
922
+ * Normalizes POSIX/Windows separators and returns only valid UUID-shaped identifiers:
923
+ * - agy: segment immediately following `/brain/`
924
+ * - codex: trailing UUID before `.jsonl` in a filename beginning with `rollout-`
925
+ */
926
+ export function sessionIdFromSourcePath(source, sourceFile) {
927
+ const normalized = sourceFile.replace(/\\/g, '/');
928
+ const sLower = source.toLowerCase();
929
+ if (sLower === 'agy' || sLower === 'antigravity') {
930
+ const match = normalized.match(AGY_BRAIN_REGEX);
931
+ return match?.[1];
932
+ }
933
+ if (sLower === 'codex') {
934
+ const basename = normalized.split('/').at(-1) ?? '';
935
+ if (basename.startsWith('rollout-') && basename.endsWith('.jsonl')) {
936
+ const stem = basename.slice(0, -6);
937
+ const match = stem.match(CODEX_ROLLOUT_REGEX);
938
+ return match?.[1];
939
+ }
940
+ }
941
+ return undefined;
942
+ }
905
943
  function sessionIdFromContext(context, raw) {
906
944
  if (context !== undefined) {
907
945
  const file = context.sourceFile.split('/').at(-1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/ts-llm-jsonl-importer",
3
- "version": "0.4.41",
3
+ "version": "0.4.42",
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.41",
51
- "@gobing-ai/ts-runtime": "^0.4.41",
52
- "@gobing-ai/ts-utils": "^0.4.41",
50
+ "@gobing-ai/ts-db": "^0.4.42",
51
+ "@gobing-ai/ts-runtime": "^0.4.42",
52
+ "@gobing-ai/ts-utils": "^0.4.42",
53
53
  "zod": "^4.1.0"
54
54
  },
55
55
  "devDependencies": {
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export { HistoryImportError } from './errors';
2
2
  export { sha256, stableJson } from './hash';
3
3
  export { runJsonlImport } from './importer';
4
4
  export { applyHistoryImportSchema, normalizeSourceFilePaths } from './jsonl-importer-dao';
5
+ export { sessionIdFromSourcePath } from './mappers';
5
6
  export { type OpenCodeImportOptions, runOpenCodeImport } from './opencode-importer';
6
7
  export { DEFAULT_REDACTION_RULES, redactRecord, redactValue } from './redaction';
7
8
  export { HISTORY_IMPORT_SCHEMA_SQL } from './schema-sql';
package/src/mappers.ts CHANGED
@@ -605,18 +605,30 @@ export function claudeToolResultTiming(raw: Record<string, unknown>): ClaudeTool
605
605
  // ---------------------------------------------------------------------------
606
606
 
607
607
  /** Map a Codex JSONL record into one or more ETL split entries. */
608
- export function codexSplit(raw: Record<string, unknown>): readonly SplitEntry[] {
608
+ export function codexSplit(raw: Record<string, unknown>, context?: TransformContext): readonly SplitEntry[] {
609
609
  const entries: SplitEntry[] = [];
610
610
 
611
611
  const payload = (raw.payload ?? raw) as Record<string, unknown>;
612
- const sessionId = s(raw.session_id, o(raw.session_meta).id, raw.id) ?? 'unknown';
613
612
  const seq = typeof raw.seq === 'number' ? raw.seq : 0;
614
613
  const ts = timestampOf(raw.timestamp, raw.ts, payload.ts);
615
614
  const recordType = String(raw.type ?? '');
616
615
  const payloadType = String(payload.type ?? '');
617
616
 
617
+ const isShortFormat =
618
+ recordType === '' && raw.id !== undefined && raw.timestamp !== undefined && raw.instructions !== undefined;
619
+
620
+ let explicitSessionId = s(raw.session_id, o(raw.session_meta).id);
621
+ if (explicitSessionId === undefined && recordType === 'session_meta') {
622
+ explicitSessionId = s(payload.id, o(raw.payload).id);
623
+ }
624
+ if (explicitSessionId === undefined && isShortFormat) {
625
+ explicitSessionId = s(raw.id);
626
+ }
627
+ const pathSessionId = context?.sourceFile ? sessionIdFromSourcePath('codex', context.sourceFile) : undefined;
628
+ const sessionId = explicitSessionId ?? pathSessionId ?? 'unknown';
629
+
618
630
  // Check for older short format
619
- if (recordType === '' && raw.id && raw.timestamp && raw.instructions) {
631
+ if (isShortFormat) {
620
632
  return [
621
633
  {
622
634
  targetTable: 'history_message',
@@ -776,10 +788,12 @@ export function codexSplit(raw: Record<string, unknown>): readonly SplitEntry[]
776
788
  // ---------------------------------------------------------------------------
777
789
 
778
790
  /** Map an Antigravity (AGY) JSONL record into one or more ETL split entries. */
779
- export function agySplit(raw: Record<string, unknown>): readonly SplitEntry[] {
791
+ export function agySplit(raw: Record<string, unknown>, context?: TransformContext): readonly SplitEntry[] {
780
792
  const entries: SplitEntry[] = [];
781
793
  const recordType = String(raw.type ?? '');
782
- const sessionId = s(raw.session_id, raw.conversation_id) ?? 'unknown';
794
+ const explicitSessionId = s(raw.session_id, raw.conversation_id);
795
+ const pathSessionId = context?.sourceFile ? sessionIdFromSourcePath('agy', context.sourceFile) : undefined;
796
+ const sessionId = explicitSessionId ?? pathSessionId ?? 'unknown';
783
797
  const seq = typeof raw.seq === 'number' ? raw.seq : typeof raw.step_index === 'number' ? raw.step_index : 0;
784
798
  const ts = timestampOf(raw.created_at, raw.timestamp, raw.ts);
785
799
 
@@ -979,6 +993,35 @@ export function geminiSplit(raw: Record<string, unknown>, context?: TransformCon
979
993
  return entries;
980
994
  }
981
995
 
996
+ const UUID_PATTERN = '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}';
997
+ const AGY_BRAIN_REGEX = new RegExp(`(?:^|\\/)brain\\/(${UUID_PATTERN})(?:\\/|$)`, 'i');
998
+ const CODEX_ROLLOUT_REGEX = new RegExp(`(?:^|-|_)(${UUID_PATTERN})$`, 'i');
999
+
1000
+ /**
1001
+ * Pure helper extracting canonical session identity from source transcript paths (task 0638 R4).
1002
+ *
1003
+ * Normalizes POSIX/Windows separators and returns only valid UUID-shaped identifiers:
1004
+ * - agy: segment immediately following `/brain/`
1005
+ * - codex: trailing UUID before `.jsonl` in a filename beginning with `rollout-`
1006
+ */
1007
+ export function sessionIdFromSourcePath(source: string, sourceFile: string): string | undefined {
1008
+ const normalized = sourceFile.replace(/\\/g, '/');
1009
+ const sLower = source.toLowerCase();
1010
+ if (sLower === 'agy' || sLower === 'antigravity') {
1011
+ const match = normalized.match(AGY_BRAIN_REGEX);
1012
+ return match?.[1];
1013
+ }
1014
+ if (sLower === 'codex') {
1015
+ const basename = normalized.split('/').at(-1) ?? '';
1016
+ if (basename.startsWith('rollout-') && basename.endsWith('.jsonl')) {
1017
+ const stem = basename.slice(0, -6);
1018
+ const match = stem.match(CODEX_ROLLOUT_REGEX);
1019
+ return match?.[1];
1020
+ }
1021
+ }
1022
+ return undefined;
1023
+ }
1024
+
982
1025
  function sessionIdFromContext(context: TransformContext | undefined, raw: Record<string, unknown>): string {
983
1026
  if (context !== undefined) {
984
1027
  const file = context.sourceFile.split('/').at(-1);