@gobing-ai/ts-llm-jsonl-importer 0.4.48 → 0.4.49
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 +4 -4
- package/dist/mappers.d.ts +4 -2
- package/dist/mappers.d.ts.map +1 -1
- package/dist/mappers.js +122 -64
- package/dist/sources.js +1 -1
- package/package.json +4 -4
- package/src/mappers.ts +118 -60
- package/src/sources.ts +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Generic JSONL import pipeline for AI-agent history-style files: discover files,
|
|
|
7
7
|
`ts-llm-jsonl-importer` is a common JSONL importer. It is intentionally not a conversation-history domain model. Built-in source definitions cover common agent file shapes, but downstream systems consume normalized ETL rows and ledger metadata.
|
|
8
8
|
|
|
9
9
|
| Export | Purpose |
|
|
10
|
-
|
|
10
|
+
| -------- | --------- |
|
|
11
11
|
| `runJsonlImport()` | Runs discovery, parsing, validation, redaction, dedupe, and persistence. Accepts a built-in source string **or** a custom `SourceDefinition`. |
|
|
12
12
|
| `applyHistoryImportSchema()` | Installs importer-owned checkpoint, ledger, and typed contract tables |
|
|
13
13
|
| `SOURCE_DEFINITIONS` | Built-in source definitions |
|
|
@@ -19,7 +19,7 @@ Generic JSONL import pipeline for AI-agent history-style files: discover files,
|
|
|
19
19
|
| `sha256()` / `stableJson()` | Stable hash helpers used by the ledger |
|
|
20
20
|
| `HISTORY_IMPORT_SCHEMA_SQL` | SQL schema string for explicit migration flows |
|
|
21
21
|
|
|
22
|
-
Built-in source keys are `claude`, `codex`, `gemini`, `pi`, `opencode`, `antigravity`, `openclaw`, `omp`, `grok`, and `agy`.
|
|
22
|
+
Built-in source keys are `claude`, `codex`, `gemini`, `pi`, `opencode`, `antigravity`, `openclaw`, `omp`, `grok`, and `agy`. The `agy` source scans `~/.gemini/antigravity-cli`, covering both `history.jsonl` and `brain/**` transcripts; conversation `.db` stores under `conversations/` are not imported.
|
|
23
23
|
|
|
24
24
|
## Installation
|
|
25
25
|
|
|
@@ -52,7 +52,7 @@ result.skippedDuplicates;
|
|
|
52
52
|
## Import Modes
|
|
53
53
|
|
|
54
54
|
| Mode | Behavior |
|
|
55
|
-
|
|
55
|
+
| ------ | ---------- |
|
|
56
56
|
| `incremental` | Reads each file from the last imported line checkpoint |
|
|
57
57
|
| `full` | Clears checkpoints for selected files, scans all lines, and still deduplicates by ledger hash |
|
|
58
58
|
| `force-file` | Scans selected files without using the checkpoint, while preserving ledger-based duplicate protection |
|
|
@@ -124,7 +124,7 @@ Use `parseErrors` for invalid JSON or non-object rows. Use `validationErrors` fo
|
|
|
124
124
|
The schema contains:
|
|
125
125
|
|
|
126
126
|
| Table | Purpose |
|
|
127
|
-
|
|
127
|
+
| ------- | --------- |
|
|
128
128
|
| `history_import_checkpoint` | Per-source/per-file last imported line |
|
|
129
129
|
| `history_import_ledger` | Stable hash ledger for dedupe and provenance |
|
|
130
130
|
| `history_message` | Typed normalized messages from built-in forensic mappers |
|
package/dist/mappers.d.ts
CHANGED
|
@@ -3,8 +3,10 @@ import type { JsonObject, SplitEntry, TransformContext } from './types';
|
|
|
3
3
|
/** Compute args_digest: sha256 of stable-jsonified, key-sorted, redacted args. */
|
|
4
4
|
export declare function argsDigest(args: unknown): string;
|
|
5
5
|
/**
|
|
6
|
-
* Return
|
|
7
|
-
*
|
|
6
|
+
* Return retained raw args for tools whose arguments carry forensic value, otherwise
|
|
7
|
+
* undefined. Todo-writing tools retain the full JSON args (phase detection); bash tools
|
|
8
|
+
* retain just the command string (capped). Codex `arguments` arrives as a JSON string
|
|
9
|
+
* already — store as-is for todo tools.
|
|
8
10
|
*/
|
|
9
11
|
export declare function maybeArgsRaw(source: string, toolName: string, args: unknown): string | undefined;
|
|
10
12
|
/** Map a Claude Code 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;AAuExE,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAGhD;
|
|
1
|
+
{"version":3,"file":"mappers.d.ts","sourceRoot":"","sources":["../src/mappers.ts"],"names":[],"mappings":"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"}
|
package/dist/mappers.js
CHANGED
|
@@ -110,18 +110,60 @@ const TODO_TOOL_ALLOWLIST = {
|
|
|
110
110
|
gemini: [],
|
|
111
111
|
};
|
|
112
112
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
113
|
+
* Per-source shell/exec tool names whose command string is retained in args_raw.
|
|
114
|
+
*
|
|
115
|
+
* WHY: bash commands are the primary forensic signal for session-task attribution and ops
|
|
116
|
+
* history (spur's task classifier regexes `spur task <verb> <wbs>` out of args_raw). Only
|
|
117
|
+
* evidenced sources are listed; unverified shapes stay empty the same way the todo allowlist
|
|
118
|
+
* treats agy/gemini. Commands pass through the importer's redaction layer like any other
|
|
119
|
+
* retained field (importer.ts redacts every split record before insert).
|
|
115
120
|
*/
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
const BASH_TOOL_ALLOWLIST = {
|
|
122
|
+
claude: ['Bash'], // input: { command: string } (mappers.test.ts fixture)
|
|
123
|
+
opencode: ['bash'], // input: { command: string } (opencode-importer.test.ts)
|
|
124
|
+
codex: [], // arguments arrive as a pre-stringified JSON string; command shape unverified
|
|
125
|
+
omp: [],
|
|
126
|
+
grok: [],
|
|
127
|
+
pi: ['bash'], // arguments: { command: string } (live pi session transcripts)
|
|
128
|
+
agy: [],
|
|
129
|
+
gemini: [],
|
|
130
|
+
};
|
|
131
|
+
/** Retained bash commands are capped; attribution only needs the invocation line. */
|
|
132
|
+
const BASH_COMMAND_MAX_CHARS = 8192;
|
|
133
|
+
/** Extract the shell command from a bash tool's raw input; undefined when absent. */
|
|
134
|
+
function bashCommandOf(args) {
|
|
135
|
+
if (args === null || typeof args !== 'object')
|
|
123
136
|
return undefined;
|
|
124
|
-
|
|
137
|
+
const command = args.command;
|
|
138
|
+
if (typeof command === 'string')
|
|
139
|
+
return command;
|
|
140
|
+
if (Array.isArray(command))
|
|
141
|
+
return command.map(String).join(' ');
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Return retained raw args for tools whose arguments carry forensic value, otherwise
|
|
146
|
+
* undefined. Todo-writing tools retain the full JSON args (phase detection); bash tools
|
|
147
|
+
* retain just the command string (capped). Codex `arguments` arrives as a JSON string
|
|
148
|
+
* already — store as-is for todo tools.
|
|
149
|
+
*/
|
|
150
|
+
export function maybeArgsRaw(source, toolName, args) {
|
|
151
|
+
const todo = TODO_TOOL_ALLOWLIST[source];
|
|
152
|
+
if (todo?.includes(toolName)) {
|
|
153
|
+
if (typeof args === 'string')
|
|
154
|
+
return args;
|
|
155
|
+
if (args === undefined || args === null)
|
|
156
|
+
return undefined;
|
|
157
|
+
return JSON.stringify(args);
|
|
158
|
+
}
|
|
159
|
+
const bash = BASH_TOOL_ALLOWLIST[source];
|
|
160
|
+
if (bash?.includes(toolName)) {
|
|
161
|
+
const command = bashCommandOf(args);
|
|
162
|
+
if (command === undefined)
|
|
163
|
+
return undefined;
|
|
164
|
+
return command.length > BASH_COMMAND_MAX_CHARS ? command.slice(0, BASH_COMMAND_MAX_CHARS) : command;
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
125
167
|
}
|
|
126
168
|
// ---------------------------------------------------------------------------
|
|
127
169
|
// Claude mapper
|
|
@@ -748,67 +790,83 @@ export function codexSplit(raw, context) {
|
|
|
748
790
|
export function agySplit(raw, context) {
|
|
749
791
|
const entries = [];
|
|
750
792
|
const recordType = String(raw.type ?? '');
|
|
751
|
-
const explicitSessionId = s(raw.session_id, raw.conversation_id);
|
|
793
|
+
const explicitSessionId = s(raw.session_id, raw.conversation_id, raw.conversationId);
|
|
752
794
|
const pathSessionId = context?.sourceFile ? sessionIdFromSourcePath('agy', context.sourceFile) : undefined;
|
|
753
795
|
const sessionId = explicitSessionId ?? pathSessionId ?? 'unknown';
|
|
754
|
-
const seq = typeof raw.seq === 'number'
|
|
796
|
+
const seq = typeof raw.seq === 'number'
|
|
797
|
+
? raw.seq
|
|
798
|
+
: typeof raw.step_index === 'number'
|
|
799
|
+
? raw.step_index
|
|
800
|
+
: (context?.sourceLine ?? 0);
|
|
755
801
|
const ts = timestampOf(raw.created_at, raw.timestamp, raw.ts);
|
|
756
802
|
let role;
|
|
757
803
|
let disposition;
|
|
758
804
|
let contentText = null;
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
// still a determined type; treat as meta bookkeeping rather than unknown.
|
|
805
|
+
let messageRecordType = recordType;
|
|
806
|
+
// history.jsonl prompt index: {display, timestamp, workspace, conversationId?, type?}.
|
|
807
|
+
// One branch covers absent type, 'slash_command', 'shell', and any future producer type.
|
|
808
|
+
// `display` never appears on legacy brain transcripts, so this cannot collide with 0463.
|
|
809
|
+
if (raw.display !== undefined) {
|
|
810
|
+
role = 'user';
|
|
811
|
+
disposition = 'keep';
|
|
812
|
+
contentText = s(raw.display) ?? null;
|
|
813
|
+
messageRecordType = recordType.length > 0 ? recordType : 'USER_INPUT';
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
// Classification from task 0463 field map (agy transcript types).
|
|
817
|
+
switch (recordType) {
|
|
818
|
+
case 'USER_INPUT':
|
|
819
|
+
case 'ASK_QUESTION':
|
|
820
|
+
role = 'user';
|
|
821
|
+
disposition = 'keep';
|
|
822
|
+
contentText = s(raw.content, raw.text, raw.message) ?? null;
|
|
823
|
+
break;
|
|
824
|
+
case 'PLANNER_RESPONSE':
|
|
825
|
+
role = 'assistant';
|
|
826
|
+
disposition = 'keep';
|
|
827
|
+
contentText = s(raw.content, raw.text, raw.message, raw.plan) ?? null;
|
|
828
|
+
break;
|
|
829
|
+
case 'ERROR_MESSAGE':
|
|
830
|
+
role = 'system';
|
|
831
|
+
disposition = 'keep';
|
|
832
|
+
contentText = s(raw.content, raw.text, raw.message) ?? null;
|
|
833
|
+
break;
|
|
834
|
+
case 'RUN_COMMAND':
|
|
835
|
+
case 'VIEW_FILE':
|
|
836
|
+
case 'GREP_SEARCH':
|
|
837
|
+
case 'LIST_DIRECTORY':
|
|
838
|
+
case 'READ_URL_CONTENT':
|
|
839
|
+
role = 'tool';
|
|
840
|
+
disposition = 'keep';
|
|
841
|
+
contentText = s(raw.content, raw.command, raw.file_path, raw.pattern) ?? null;
|
|
842
|
+
break;
|
|
843
|
+
case 'CODE_ACTION':
|
|
844
|
+
case 'INVOKE_SUBAGENT':
|
|
845
|
+
role = 'tool';
|
|
846
|
+
disposition = 'keep';
|
|
847
|
+
contentText = s(raw.content, raw.text) ?? null;
|
|
848
|
+
break;
|
|
849
|
+
case 'CONVERSATION_HISTORY':
|
|
850
|
+
case 'CHECKPOINT':
|
|
851
|
+
case 'GENERIC':
|
|
807
852
|
role = 'meta';
|
|
808
853
|
disposition = 'meta';
|
|
809
854
|
contentText = s(raw.content, raw.text) ?? null;
|
|
810
|
-
|
|
811
|
-
|
|
855
|
+
break;
|
|
856
|
+
default:
|
|
857
|
+
if (recordType.length === 0) {
|
|
858
|
+
role = 'unknown';
|
|
859
|
+
disposition = 'unknown';
|
|
860
|
+
}
|
|
861
|
+
else {
|
|
862
|
+
// Known discriminator present but not in the frozen 0463 keep/meta table —
|
|
863
|
+
// still a determined type; treat as meta bookkeeping rather than unknown.
|
|
864
|
+
role = 'meta';
|
|
865
|
+
disposition = 'meta';
|
|
866
|
+
contentText = s(raw.content, raw.text) ?? null;
|
|
867
|
+
}
|
|
868
|
+
break;
|
|
869
|
+
}
|
|
812
870
|
}
|
|
813
871
|
const messageSplitIndex = entries.length;
|
|
814
872
|
entries.push({
|
|
@@ -817,7 +875,7 @@ export function agySplit(raw, context) {
|
|
|
817
875
|
session_id: sessionId,
|
|
818
876
|
seq,
|
|
819
877
|
role,
|
|
820
|
-
record_type:
|
|
878
|
+
record_type: messageRecordType,
|
|
821
879
|
disposition,
|
|
822
880
|
ts,
|
|
823
881
|
duration_ms: undefined,
|
|
@@ -828,7 +886,7 @@ export function agySplit(raw, context) {
|
|
|
828
886
|
cache_write_tokens: null,
|
|
829
887
|
cost_usd: null,
|
|
830
888
|
content_text: contentText,
|
|
831
|
-
cwd: null,
|
|
889
|
+
cwd: s(raw.workspace, raw.cwd) ?? null,
|
|
832
890
|
provenance: 'ambient',
|
|
833
891
|
},
|
|
834
892
|
});
|
package/dist/sources.js
CHANGED
|
@@ -104,7 +104,7 @@ export const SOURCE_DEFINITIONS = {
|
|
|
104
104
|
omp: customSourceDefinition('omp', 'OMP', ['.omp/agent/sessions'], ['*.jsonl'], ompSplit, OMP_FIELD_MAP, OMP_SCHEMA),
|
|
105
105
|
grok: customSourceDefinition('grok', 'Grok', ['.grok/sessions'], ['*.jsonl'], grokSplit, GROK_FIELD_MAP, GROK_SCHEMA),
|
|
106
106
|
agy: {
|
|
107
|
-
...customSourceDefinition('agy', 'Antigravity CLI', ['.gemini/antigravity-cli
|
|
107
|
+
...customSourceDefinition('agy', 'Antigravity CLI', ['.gemini/antigravity-cli'], ['*.jsonl'], agySplit, AGY_FIELD_MAP, AGY_SCHEMA),
|
|
108
108
|
// agy's producer interleaves non-JSON fragments and torn tail writes into its
|
|
109
109
|
// brain jsonl (task 0623: 789/89,818 lines, all unrecoverable). Skipping keeps
|
|
110
110
|
// the 99%+ good records importable without degrading the whole source.
|
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.49",
|
|
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.49",
|
|
51
|
+
"@gobing-ai/ts-runtime": "^0.4.49",
|
|
52
|
+
"@gobing-ai/ts-utils": "^0.4.49",
|
|
53
53
|
"zod": "^4.1.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
package/src/mappers.ts
CHANGED
|
@@ -121,15 +121,57 @@ const TODO_TOOL_ALLOWLIST: Readonly<Record<string, readonly string[]>> = {
|
|
|
121
121
|
};
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
124
|
+
* Per-source shell/exec tool names whose command string is retained in args_raw.
|
|
125
|
+
*
|
|
126
|
+
* WHY: bash commands are the primary forensic signal for session-task attribution and ops
|
|
127
|
+
* history (spur's task classifier regexes `spur task <verb> <wbs>` out of args_raw). Only
|
|
128
|
+
* evidenced sources are listed; unverified shapes stay empty the same way the todo allowlist
|
|
129
|
+
* treats agy/gemini. Commands pass through the importer's redaction layer like any other
|
|
130
|
+
* retained field (importer.ts redacts every split record before insert).
|
|
131
|
+
*/
|
|
132
|
+
const BASH_TOOL_ALLOWLIST: Readonly<Record<string, readonly string[]>> = {
|
|
133
|
+
claude: ['Bash'], // input: { command: string } (mappers.test.ts fixture)
|
|
134
|
+
opencode: ['bash'], // input: { command: string } (opencode-importer.test.ts)
|
|
135
|
+
codex: [], // arguments arrive as a pre-stringified JSON string; command shape unverified
|
|
136
|
+
omp: [],
|
|
137
|
+
grok: [],
|
|
138
|
+
pi: ['bash'], // arguments: { command: string } (live pi session transcripts)
|
|
139
|
+
agy: [],
|
|
140
|
+
gemini: [],
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** Retained bash commands are capped; attribution only needs the invocation line. */
|
|
144
|
+
const BASH_COMMAND_MAX_CHARS = 8192;
|
|
145
|
+
|
|
146
|
+
/** Extract the shell command from a bash tool's raw input; undefined when absent. */
|
|
147
|
+
function bashCommandOf(args: unknown): string | undefined {
|
|
148
|
+
if (args === null || typeof args !== 'object') return undefined;
|
|
149
|
+
const command = (args as Record<string, unknown>).command;
|
|
150
|
+
if (typeof command === 'string') return command;
|
|
151
|
+
if (Array.isArray(command)) return command.map(String).join(' ');
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Return retained raw args for tools whose arguments carry forensic value, otherwise
|
|
157
|
+
* undefined. Todo-writing tools retain the full JSON args (phase detection); bash tools
|
|
158
|
+
* retain just the command string (capped). Codex `arguments` arrives as a JSON string
|
|
159
|
+
* already — store as-is for todo tools.
|
|
126
160
|
*/
|
|
127
161
|
export function maybeArgsRaw(source: string, toolName: string, args: unknown): string | undefined {
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
162
|
+
const todo = TODO_TOOL_ALLOWLIST[source];
|
|
163
|
+
if (todo?.includes(toolName)) {
|
|
164
|
+
if (typeof args === 'string') return args;
|
|
165
|
+
if (args === undefined || args === null) return undefined;
|
|
166
|
+
return JSON.stringify(args);
|
|
167
|
+
}
|
|
168
|
+
const bash = BASH_TOOL_ALLOWLIST[source];
|
|
169
|
+
if (bash?.includes(toolName)) {
|
|
170
|
+
const command = bashCommandOf(args);
|
|
171
|
+
if (command === undefined) return undefined;
|
|
172
|
+
return command.length > BASH_COMMAND_MAX_CHARS ? command.slice(0, BASH_COMMAND_MAX_CHARS) : command;
|
|
173
|
+
}
|
|
174
|
+
return undefined;
|
|
133
175
|
}
|
|
134
176
|
|
|
135
177
|
// ---------------------------------------------------------------------------
|
|
@@ -825,68 +867,84 @@ export function codexSplit(raw: Record<string, unknown>, context?: TransformCont
|
|
|
825
867
|
export function agySplit(raw: Record<string, unknown>, context?: TransformContext): readonly SplitEntry[] {
|
|
826
868
|
const entries: SplitEntry[] = [];
|
|
827
869
|
const recordType = String(raw.type ?? '');
|
|
828
|
-
const explicitSessionId = s(raw.session_id, raw.conversation_id);
|
|
870
|
+
const explicitSessionId = s(raw.session_id, raw.conversation_id, raw.conversationId);
|
|
829
871
|
const pathSessionId = context?.sourceFile ? sessionIdFromSourcePath('agy', context.sourceFile) : undefined;
|
|
830
872
|
const sessionId = explicitSessionId ?? pathSessionId ?? 'unknown';
|
|
831
|
-
const seq =
|
|
873
|
+
const seq =
|
|
874
|
+
typeof raw.seq === 'number'
|
|
875
|
+
? raw.seq
|
|
876
|
+
: typeof raw.step_index === 'number'
|
|
877
|
+
? raw.step_index
|
|
878
|
+
: (context?.sourceLine ?? 0);
|
|
832
879
|
const ts = timestampOf(raw.created_at, raw.timestamp, raw.ts);
|
|
833
880
|
|
|
834
881
|
let role: string;
|
|
835
882
|
let disposition: string;
|
|
836
883
|
let contentText: string | null = null;
|
|
884
|
+
let messageRecordType = recordType;
|
|
837
885
|
|
|
838
|
-
//
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
// Known discriminator present but not in the frozen 0463 keep/meta table —
|
|
884
|
-
// still a determined type; treat as meta bookkeeping rather than unknown.
|
|
886
|
+
// history.jsonl prompt index: {display, timestamp, workspace, conversationId?, type?}.
|
|
887
|
+
// One branch covers absent type, 'slash_command', 'shell', and any future producer type.
|
|
888
|
+
// `display` never appears on legacy brain transcripts, so this cannot collide with 0463.
|
|
889
|
+
if (raw.display !== undefined) {
|
|
890
|
+
role = 'user';
|
|
891
|
+
disposition = 'keep';
|
|
892
|
+
contentText = s(raw.display) ?? null;
|
|
893
|
+
messageRecordType = recordType.length > 0 ? recordType : 'USER_INPUT';
|
|
894
|
+
} else {
|
|
895
|
+
// Classification from task 0463 field map (agy transcript types).
|
|
896
|
+
switch (recordType) {
|
|
897
|
+
case 'USER_INPUT':
|
|
898
|
+
case 'ASK_QUESTION':
|
|
899
|
+
role = 'user';
|
|
900
|
+
disposition = 'keep';
|
|
901
|
+
contentText = s(raw.content, raw.text, raw.message) ?? null;
|
|
902
|
+
break;
|
|
903
|
+
case 'PLANNER_RESPONSE':
|
|
904
|
+
role = 'assistant';
|
|
905
|
+
disposition = 'keep';
|
|
906
|
+
contentText = s(raw.content, raw.text, raw.message, raw.plan) ?? null;
|
|
907
|
+
break;
|
|
908
|
+
case 'ERROR_MESSAGE':
|
|
909
|
+
role = 'system';
|
|
910
|
+
disposition = 'keep';
|
|
911
|
+
contentText = s(raw.content, raw.text, raw.message) ?? null;
|
|
912
|
+
break;
|
|
913
|
+
case 'RUN_COMMAND':
|
|
914
|
+
case 'VIEW_FILE':
|
|
915
|
+
case 'GREP_SEARCH':
|
|
916
|
+
case 'LIST_DIRECTORY':
|
|
917
|
+
case 'READ_URL_CONTENT':
|
|
918
|
+
role = 'tool';
|
|
919
|
+
disposition = 'keep';
|
|
920
|
+
contentText = s(raw.content, raw.command, raw.file_path, raw.pattern) ?? null;
|
|
921
|
+
break;
|
|
922
|
+
case 'CODE_ACTION':
|
|
923
|
+
case 'INVOKE_SUBAGENT':
|
|
924
|
+
role = 'tool';
|
|
925
|
+
disposition = 'keep';
|
|
926
|
+
contentText = s(raw.content, raw.text) ?? null;
|
|
927
|
+
break;
|
|
928
|
+
case 'CONVERSATION_HISTORY':
|
|
929
|
+
case 'CHECKPOINT':
|
|
930
|
+
case 'GENERIC':
|
|
885
931
|
role = 'meta';
|
|
886
932
|
disposition = 'meta';
|
|
887
933
|
contentText = s(raw.content, raw.text) ?? null;
|
|
888
|
-
|
|
889
|
-
|
|
934
|
+
break;
|
|
935
|
+
default:
|
|
936
|
+
if (recordType.length === 0) {
|
|
937
|
+
role = 'unknown';
|
|
938
|
+
disposition = 'unknown';
|
|
939
|
+
} else {
|
|
940
|
+
// Known discriminator present but not in the frozen 0463 keep/meta table —
|
|
941
|
+
// still a determined type; treat as meta bookkeeping rather than unknown.
|
|
942
|
+
role = 'meta';
|
|
943
|
+
disposition = 'meta';
|
|
944
|
+
contentText = s(raw.content, raw.text) ?? null;
|
|
945
|
+
}
|
|
946
|
+
break;
|
|
947
|
+
}
|
|
890
948
|
}
|
|
891
949
|
|
|
892
950
|
const messageSplitIndex = entries.length;
|
|
@@ -897,7 +955,7 @@ export function agySplit(raw: Record<string, unknown>, context?: TransformContex
|
|
|
897
955
|
session_id: sessionId,
|
|
898
956
|
seq,
|
|
899
957
|
role,
|
|
900
|
-
record_type:
|
|
958
|
+
record_type: messageRecordType,
|
|
901
959
|
disposition,
|
|
902
960
|
ts,
|
|
903
961
|
duration_ms: undefined,
|
|
@@ -908,7 +966,7 @@ export function agySplit(raw: Record<string, unknown>, context?: TransformContex
|
|
|
908
966
|
cache_write_tokens: null,
|
|
909
967
|
cost_usd: null,
|
|
910
968
|
content_text: contentText,
|
|
911
|
-
cwd: null,
|
|
969
|
+
cwd: s(raw.workspace, raw.cwd) ?? null,
|
|
912
970
|
provenance: 'ambient',
|
|
913
971
|
},
|
|
914
972
|
});
|
package/src/sources.ts
CHANGED
|
@@ -194,7 +194,7 @@ export const SOURCE_DEFINITIONS: Readonly<Record<LlmJsonlSource, SourceDefinitio
|
|
|
194
194
|
...customSourceDefinition(
|
|
195
195
|
'agy',
|
|
196
196
|
'Antigravity CLI',
|
|
197
|
-
['.gemini/antigravity-cli
|
|
197
|
+
['.gemini/antigravity-cli'],
|
|
198
198
|
['*.jsonl'],
|
|
199
199
|
agySplit,
|
|
200
200
|
AGY_FIELD_MAP,
|