@gobing-ai/ts-llm-jsonl-importer 0.4.48 → 0.4.50
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 -3
- package/dist/mappers.d.ts.map +1 -1
- package/dist/mappers.js +181 -80
- package/dist/sources.js +1 -1
- package/package.json +4 -4
- package/src/mappers.ts +187 -77
- 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,10 +3,11 @@ 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 a typed tool call: the shell command string for
|
|
7
|
+
* recognized bash/shell/exec/command tools, sanitized valid JSON otherwise, and
|
|
8
|
+
* undefined when the event carries no invocation arguments.
|
|
8
9
|
*/
|
|
9
|
-
export declare function maybeArgsRaw(
|
|
10
|
+
export declare function maybeArgsRaw(_source: string, toolName: string, args: unknown): string | undefined;
|
|
10
11
|
/** Map a Claude Code JSONL record into one or more ETL split entries. */
|
|
11
12
|
export declare function claudeSplit(raw: Record<string, unknown>): readonly SplitEntry[];
|
|
12
13
|
/** Map a Pi 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
|
@@ -91,37 +91,122 @@ function redactArgs(args) {
|
|
|
91
91
|
return args;
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* opencode `todowrite`/`todoread` measured live in .spur/spur.db (3,237/21, 1,043, 379/2
|
|
100
|
-
* rows with NULL args_raw).
|
|
94
|
+
* Retained tool-call arguments: recognized shell tools keep the extracted plain
|
|
95
|
+
* command (attribution compatibility, capped); every other typed tool call keeps
|
|
96
|
+
* sanitized-but-valid JSON. `args_digest` is computed elsewhere from the unpruned
|
|
97
|
+
* args and is unaffected; secret redaction stays at the persistence `redactRecord`
|
|
98
|
+
* seam (task 0064 R5).
|
|
101
99
|
*/
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
100
|
+
const BASH_COMMAND_MAX_CHARS = 8192;
|
|
101
|
+
const GENERIC_ARGS_MAX_CHARS = 8192;
|
|
102
|
+
/** Extract the shell command from a bash tool's raw input; undefined when absent. */
|
|
103
|
+
function bashCommandOf(args) {
|
|
104
|
+
if (args === null || typeof args !== 'object')
|
|
105
|
+
return undefined;
|
|
106
|
+
const rec = args;
|
|
107
|
+
const command = rec.command ?? rec.CommandLine ?? rec.cmd ?? rec.script;
|
|
108
|
+
if (typeof command === 'string')
|
|
109
|
+
return command;
|
|
110
|
+
if (Array.isArray(command))
|
|
111
|
+
return command.map(String).join(' ');
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
/** Bulky payload keys (normalized) pruned to an omission marker; camelCase and snake_case share one rule. */
|
|
115
|
+
const BULKY_PAYLOAD_KEYS = new Set([
|
|
116
|
+
'content',
|
|
117
|
+
'codecontent',
|
|
118
|
+
'replacementcontent',
|
|
119
|
+
'targetcontent',
|
|
120
|
+
'filedata',
|
|
121
|
+
'buffer',
|
|
122
|
+
'blob',
|
|
123
|
+
'image',
|
|
124
|
+
'imagebase64',
|
|
125
|
+
]);
|
|
126
|
+
/** Lowercase a key and drop separators so code_content and codeContent compare equal. */
|
|
127
|
+
function normalizeKey(key) {
|
|
128
|
+
return key.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
129
|
+
}
|
|
130
|
+
/** Tool names whose command-shaped args retain the existing plain-string representation. */
|
|
131
|
+
function isShellTool(toolName) {
|
|
132
|
+
return /^(?:bash|shell|cmd|command|exec|execcommand|executecommand|runcommand|shellcommand|runshellcommand)$/.test(normalizeKey(toolName));
|
|
133
|
+
}
|
|
112
134
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
135
|
+
* Sanitize any JSON value recursively through objects AND arrays: bulky payload
|
|
136
|
+
* strings under known keys become `[omitted N chars]`, other strings longer than
|
|
137
|
+
* 1,000 characters keep a prefix plus an explicit original-length marker, and
|
|
138
|
+
* every primitive and short payload is preserved untouched (task 0064 R2).
|
|
115
139
|
*/
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
return
|
|
140
|
+
function sanitizeValue(value) {
|
|
141
|
+
if (typeof value === 'string') {
|
|
142
|
+
return value.length > 1000 ? `${value.slice(0, 1000)}[truncated ${value.length} chars]` : value;
|
|
143
|
+
}
|
|
144
|
+
if (Array.isArray(value))
|
|
145
|
+
return value.map(sanitizeValue);
|
|
146
|
+
if (value !== null && typeof value === 'object') {
|
|
147
|
+
const out = {};
|
|
148
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
149
|
+
out[key] =
|
|
150
|
+
typeof entry === 'string' && entry.length > 120 && BULKY_PAYLOAD_KEYS.has(normalizeKey(key))
|
|
151
|
+
? `[omitted ${entry.length} chars]`
|
|
152
|
+
: sanitizeValue(entry);
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
// JSON leaves (number/boolean/null) pass through unchanged; JSON args never
|
|
157
|
+
// reach here as undefined/bigint/symbol/function.
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Serialize sanitized args within the retention ceiling without ever slicing JSON
|
|
162
|
+
* text: values that still serialize past the ceiling collapse into a small valid
|
|
163
|
+
* JSON wrapper carrying `_truncated`, the omitted character count, and a bounded
|
|
164
|
+
* preview reduced until the wrapper itself fits (task 0064 R3/AC4).
|
|
165
|
+
*/
|
|
166
|
+
function serializeBounded(value) {
|
|
167
|
+
const serialized = JSON.stringify(sanitizeValue(value));
|
|
168
|
+
if (serialized.length <= GENERIC_ARGS_MAX_CHARS)
|
|
169
|
+
return serialized;
|
|
170
|
+
let preview = serialized;
|
|
171
|
+
for (;;) {
|
|
172
|
+
const wrapper = JSON.stringify({
|
|
173
|
+
_truncated: true,
|
|
174
|
+
omittedChars: serialized.length - preview.length,
|
|
175
|
+
preview,
|
|
176
|
+
});
|
|
177
|
+
if (wrapper.length <= GENERIC_ARGS_MAX_CHARS)
|
|
178
|
+
return wrapper;
|
|
179
|
+
preview = preview.slice(0, preview.length - (wrapper.length - GENERIC_ARGS_MAX_CHARS));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Return retained raw args for a typed tool call: the shell command string for
|
|
184
|
+
* recognized bash/shell/exec/command tools, sanitized valid JSON otherwise, and
|
|
185
|
+
* undefined when the event carries no invocation arguments.
|
|
186
|
+
*/
|
|
187
|
+
export function maybeArgsRaw(_source, toolName, args) {
|
|
122
188
|
if (args === undefined || args === null)
|
|
123
189
|
return undefined;
|
|
124
|
-
|
|
190
|
+
let value = args;
|
|
191
|
+
if (typeof args === 'string') {
|
|
192
|
+
// Pre-stringified arguments (Codex) re-enter the structured path so the
|
|
193
|
+
// retained value parses and prunes like native JSON.
|
|
194
|
+
try {
|
|
195
|
+
value = JSON.parse(args);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return args.length > BASH_COMMAND_MAX_CHARS ? args.slice(0, BASH_COMMAND_MAX_CHARS) : args;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (value !== null && typeof value === 'object') {
|
|
202
|
+
const cmd = bashCommandOf(value);
|
|
203
|
+
if (cmd !== undefined && isShellTool(toolName)) {
|
|
204
|
+
return cmd.length > BASH_COMMAND_MAX_CHARS ? cmd.slice(0, BASH_COMMAND_MAX_CHARS) : cmd;
|
|
205
|
+
}
|
|
206
|
+
return serializeBounded(value);
|
|
207
|
+
}
|
|
208
|
+
// JSON primitives (number/boolean) retain as valid JSON scalars.
|
|
209
|
+
return JSON.stringify(value);
|
|
125
210
|
}
|
|
126
211
|
// ---------------------------------------------------------------------------
|
|
127
212
|
// Claude mapper
|
|
@@ -748,67 +833,83 @@ export function codexSplit(raw, context) {
|
|
|
748
833
|
export function agySplit(raw, context) {
|
|
749
834
|
const entries = [];
|
|
750
835
|
const recordType = String(raw.type ?? '');
|
|
751
|
-
const explicitSessionId = s(raw.session_id, raw.conversation_id);
|
|
836
|
+
const explicitSessionId = s(raw.session_id, raw.conversation_id, raw.conversationId);
|
|
752
837
|
const pathSessionId = context?.sourceFile ? sessionIdFromSourcePath('agy', context.sourceFile) : undefined;
|
|
753
838
|
const sessionId = explicitSessionId ?? pathSessionId ?? 'unknown';
|
|
754
|
-
const seq = typeof raw.seq === 'number'
|
|
839
|
+
const seq = typeof raw.seq === 'number'
|
|
840
|
+
? raw.seq
|
|
841
|
+
: typeof raw.step_index === 'number'
|
|
842
|
+
? raw.step_index
|
|
843
|
+
: (context?.sourceLine ?? 0);
|
|
755
844
|
const ts = timestampOf(raw.created_at, raw.timestamp, raw.ts);
|
|
756
845
|
let role;
|
|
757
846
|
let disposition;
|
|
758
847
|
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.
|
|
848
|
+
let messageRecordType = recordType;
|
|
849
|
+
// history.jsonl prompt index: {display, timestamp, workspace, conversationId?, type?}.
|
|
850
|
+
// One branch covers absent type, 'slash_command', 'shell', and any future producer type.
|
|
851
|
+
// `display` never appears on legacy brain transcripts, so this cannot collide with 0463.
|
|
852
|
+
if (raw.display !== undefined) {
|
|
853
|
+
role = 'user';
|
|
854
|
+
disposition = 'keep';
|
|
855
|
+
contentText = s(raw.display) ?? null;
|
|
856
|
+
messageRecordType = recordType.length > 0 ? recordType : 'USER_INPUT';
|
|
857
|
+
}
|
|
858
|
+
else {
|
|
859
|
+
// Classification from task 0463 field map (agy transcript types).
|
|
860
|
+
switch (recordType) {
|
|
861
|
+
case 'USER_INPUT':
|
|
862
|
+
case 'ASK_QUESTION':
|
|
863
|
+
role = 'user';
|
|
864
|
+
disposition = 'keep';
|
|
865
|
+
contentText = s(raw.content, raw.text, raw.message) ?? null;
|
|
866
|
+
break;
|
|
867
|
+
case 'PLANNER_RESPONSE':
|
|
868
|
+
role = 'assistant';
|
|
869
|
+
disposition = 'keep';
|
|
870
|
+
contentText = s(raw.content, raw.text, raw.message, raw.plan) ?? null;
|
|
871
|
+
break;
|
|
872
|
+
case 'ERROR_MESSAGE':
|
|
873
|
+
role = 'system';
|
|
874
|
+
disposition = 'keep';
|
|
875
|
+
contentText = s(raw.content, raw.text, raw.message) ?? null;
|
|
876
|
+
break;
|
|
877
|
+
case 'RUN_COMMAND':
|
|
878
|
+
case 'VIEW_FILE':
|
|
879
|
+
case 'GREP_SEARCH':
|
|
880
|
+
case 'LIST_DIRECTORY':
|
|
881
|
+
case 'READ_URL_CONTENT':
|
|
882
|
+
role = 'tool';
|
|
883
|
+
disposition = 'keep';
|
|
884
|
+
contentText = s(raw.content, raw.command, raw.file_path, raw.pattern) ?? null;
|
|
885
|
+
break;
|
|
886
|
+
case 'CODE_ACTION':
|
|
887
|
+
case 'INVOKE_SUBAGENT':
|
|
888
|
+
role = 'tool';
|
|
889
|
+
disposition = 'keep';
|
|
890
|
+
contentText = s(raw.content, raw.text) ?? null;
|
|
891
|
+
break;
|
|
892
|
+
case 'CONVERSATION_HISTORY':
|
|
893
|
+
case 'CHECKPOINT':
|
|
894
|
+
case 'GENERIC':
|
|
807
895
|
role = 'meta';
|
|
808
896
|
disposition = 'meta';
|
|
809
897
|
contentText = s(raw.content, raw.text) ?? null;
|
|
810
|
-
|
|
811
|
-
|
|
898
|
+
break;
|
|
899
|
+
default:
|
|
900
|
+
if (recordType.length === 0) {
|
|
901
|
+
role = 'unknown';
|
|
902
|
+
disposition = 'unknown';
|
|
903
|
+
}
|
|
904
|
+
else {
|
|
905
|
+
// Known discriminator present but not in the frozen 0463 keep/meta table —
|
|
906
|
+
// still a determined type; treat as meta bookkeeping rather than unknown.
|
|
907
|
+
role = 'meta';
|
|
908
|
+
disposition = 'meta';
|
|
909
|
+
contentText = s(raw.content, raw.text) ?? null;
|
|
910
|
+
}
|
|
911
|
+
break;
|
|
912
|
+
}
|
|
812
913
|
}
|
|
813
914
|
const messageSplitIndex = entries.length;
|
|
814
915
|
entries.push({
|
|
@@ -817,7 +918,7 @@ export function agySplit(raw, context) {
|
|
|
817
918
|
session_id: sessionId,
|
|
818
919
|
seq,
|
|
819
920
|
role,
|
|
820
|
-
record_type:
|
|
921
|
+
record_type: messageRecordType,
|
|
821
922
|
disposition,
|
|
822
923
|
ts,
|
|
823
924
|
duration_ms: undefined,
|
|
@@ -828,7 +929,7 @@ export function agySplit(raw, context) {
|
|
|
828
929
|
cache_write_tokens: null,
|
|
829
930
|
cost_usd: null,
|
|
830
931
|
content_text: contentText,
|
|
831
|
-
cwd: null,
|
|
932
|
+
cwd: s(raw.workspace, raw.cwd) ?? null,
|
|
832
933
|
provenance: 'ambient',
|
|
833
934
|
},
|
|
834
935
|
});
|
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.50",
|
|
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.50",
|
|
51
|
+
"@gobing-ai/ts-runtime": "^0.4.50",
|
|
52
|
+
"@gobing-ai/ts-utils": "^0.4.50",
|
|
53
53
|
"zod": "^4.1.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
package/src/mappers.ts
CHANGED
|
@@ -101,35 +101,129 @@ function redactArgs(args: unknown): RedactedValue {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* opencode `todowrite`/`todoread` measured live in .spur/spur.db (3,237/21, 1,043, 379/2
|
|
110
|
-
* rows with NULL args_raw).
|
|
104
|
+
* Retained tool-call arguments: recognized shell tools keep the extracted plain
|
|
105
|
+
* command (attribution compatibility, capped); every other typed tool call keeps
|
|
106
|
+
* sanitized-but-valid JSON. `args_digest` is computed elsewhere from the unpruned
|
|
107
|
+
* args and is unaffected; secret redaction stays at the persistence `redactRecord`
|
|
108
|
+
* seam (task 0064 R5).
|
|
111
109
|
*/
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
110
|
+
const BASH_COMMAND_MAX_CHARS = 8192;
|
|
111
|
+
const GENERIC_ARGS_MAX_CHARS = 8192;
|
|
112
|
+
|
|
113
|
+
/** Extract the shell command from a bash tool's raw input; undefined when absent. */
|
|
114
|
+
function bashCommandOf(args: unknown): string | undefined {
|
|
115
|
+
if (args === null || typeof args !== 'object') return undefined;
|
|
116
|
+
const rec = args as Record<string, unknown>;
|
|
117
|
+
const command = rec.command ?? rec.CommandLine ?? rec.cmd ?? rec.script;
|
|
118
|
+
if (typeof command === 'string') return command;
|
|
119
|
+
if (Array.isArray(command)) return command.map(String).join(' ');
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Bulky payload keys (normalized) pruned to an omission marker; camelCase and snake_case share one rule. */
|
|
124
|
+
const BULKY_PAYLOAD_KEYS: ReadonlySet<string> = new Set([
|
|
125
|
+
'content',
|
|
126
|
+
'codecontent',
|
|
127
|
+
'replacementcontent',
|
|
128
|
+
'targetcontent',
|
|
129
|
+
'filedata',
|
|
130
|
+
'buffer',
|
|
131
|
+
'blob',
|
|
132
|
+
'image',
|
|
133
|
+
'imagebase64',
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
/** Lowercase a key and drop separators so code_content and codeContent compare equal. */
|
|
137
|
+
function normalizeKey(key: string): string {
|
|
138
|
+
return key.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Tool names whose command-shaped args retain the existing plain-string representation. */
|
|
142
|
+
function isShellTool(toolName: string): boolean {
|
|
143
|
+
return /^(?:bash|shell|cmd|command|exec|execcommand|executecommand|runcommand|shellcommand|runshellcommand)$/.test(
|
|
144
|
+
normalizeKey(toolName),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Sanitized tool args: JSON shape preserved, bulky payload strings elided. */
|
|
149
|
+
type SanitizedValue = string | number | boolean | null | SanitizedValue[] | { [key: string]: SanitizedValue };
|
|
122
150
|
|
|
123
151
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
152
|
+
* Sanitize any JSON value recursively through objects AND arrays: bulky payload
|
|
153
|
+
* strings under known keys become `[omitted N chars]`, other strings longer than
|
|
154
|
+
* 1,000 characters keep a prefix plus an explicit original-length marker, and
|
|
155
|
+
* every primitive and short payload is preserved untouched (task 0064 R2).
|
|
126
156
|
*/
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
157
|
+
function sanitizeValue(value: unknown): SanitizedValue {
|
|
158
|
+
if (typeof value === 'string') {
|
|
159
|
+
return value.length > 1000 ? `${value.slice(0, 1000)}[truncated ${value.length} chars]` : value;
|
|
160
|
+
}
|
|
161
|
+
if (Array.isArray(value)) return value.map(sanitizeValue);
|
|
162
|
+
if (value !== null && typeof value === 'object') {
|
|
163
|
+
const out: Record<string, SanitizedValue> = {};
|
|
164
|
+
for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
|
|
165
|
+
out[key] =
|
|
166
|
+
typeof entry === 'string' && entry.length > 120 && BULKY_PAYLOAD_KEYS.has(normalizeKey(key))
|
|
167
|
+
? `[omitted ${entry.length} chars]`
|
|
168
|
+
: sanitizeValue(entry);
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
// JSON leaves (number/boolean/null) pass through unchanged; JSON args never
|
|
173
|
+
// reach here as undefined/bigint/symbol/function.
|
|
174
|
+
return value as SanitizedValue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Serialize sanitized args within the retention ceiling without ever slicing JSON
|
|
179
|
+
* text: values that still serialize past the ceiling collapse into a small valid
|
|
180
|
+
* JSON wrapper carrying `_truncated`, the omitted character count, and a bounded
|
|
181
|
+
* preview reduced until the wrapper itself fits (task 0064 R3/AC4).
|
|
182
|
+
*/
|
|
183
|
+
function serializeBounded(value: unknown): string {
|
|
184
|
+
const serialized = JSON.stringify(sanitizeValue(value));
|
|
185
|
+
if (serialized.length <= GENERIC_ARGS_MAX_CHARS) return serialized;
|
|
186
|
+
let preview = serialized;
|
|
187
|
+
for (;;) {
|
|
188
|
+
const wrapper = JSON.stringify({
|
|
189
|
+
_truncated: true,
|
|
190
|
+
omittedChars: serialized.length - preview.length,
|
|
191
|
+
preview,
|
|
192
|
+
});
|
|
193
|
+
if (wrapper.length <= GENERIC_ARGS_MAX_CHARS) return wrapper;
|
|
194
|
+
preview = preview.slice(0, preview.length - (wrapper.length - GENERIC_ARGS_MAX_CHARS));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Return retained raw args for a typed tool call: the shell command string for
|
|
200
|
+
* recognized bash/shell/exec/command tools, sanitized valid JSON otherwise, and
|
|
201
|
+
* undefined when the event carries no invocation arguments.
|
|
202
|
+
*/
|
|
203
|
+
export function maybeArgsRaw(_source: string, toolName: string, args: unknown): string | undefined {
|
|
131
204
|
if (args === undefined || args === null) return undefined;
|
|
132
|
-
|
|
205
|
+
|
|
206
|
+
let value = args;
|
|
207
|
+
if (typeof args === 'string') {
|
|
208
|
+
// Pre-stringified arguments (Codex) re-enter the structured path so the
|
|
209
|
+
// retained value parses and prunes like native JSON.
|
|
210
|
+
try {
|
|
211
|
+
value = JSON.parse(args);
|
|
212
|
+
} catch {
|
|
213
|
+
return args.length > BASH_COMMAND_MAX_CHARS ? args.slice(0, BASH_COMMAND_MAX_CHARS) : args;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (value !== null && typeof value === 'object') {
|
|
218
|
+
const cmd = bashCommandOf(value);
|
|
219
|
+
if (cmd !== undefined && isShellTool(toolName)) {
|
|
220
|
+
return cmd.length > BASH_COMMAND_MAX_CHARS ? cmd.slice(0, BASH_COMMAND_MAX_CHARS) : cmd;
|
|
221
|
+
}
|
|
222
|
+
return serializeBounded(value);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// JSON primitives (number/boolean) retain as valid JSON scalars.
|
|
226
|
+
return JSON.stringify(value);
|
|
133
227
|
}
|
|
134
228
|
|
|
135
229
|
// ---------------------------------------------------------------------------
|
|
@@ -825,68 +919,84 @@ export function codexSplit(raw: Record<string, unknown>, context?: TransformCont
|
|
|
825
919
|
export function agySplit(raw: Record<string, unknown>, context?: TransformContext): readonly SplitEntry[] {
|
|
826
920
|
const entries: SplitEntry[] = [];
|
|
827
921
|
const recordType = String(raw.type ?? '');
|
|
828
|
-
const explicitSessionId = s(raw.session_id, raw.conversation_id);
|
|
922
|
+
const explicitSessionId = s(raw.session_id, raw.conversation_id, raw.conversationId);
|
|
829
923
|
const pathSessionId = context?.sourceFile ? sessionIdFromSourcePath('agy', context.sourceFile) : undefined;
|
|
830
924
|
const sessionId = explicitSessionId ?? pathSessionId ?? 'unknown';
|
|
831
|
-
const seq =
|
|
925
|
+
const seq =
|
|
926
|
+
typeof raw.seq === 'number'
|
|
927
|
+
? raw.seq
|
|
928
|
+
: typeof raw.step_index === 'number'
|
|
929
|
+
? raw.step_index
|
|
930
|
+
: (context?.sourceLine ?? 0);
|
|
832
931
|
const ts = timestampOf(raw.created_at, raw.timestamp, raw.ts);
|
|
833
932
|
|
|
834
933
|
let role: string;
|
|
835
934
|
let disposition: string;
|
|
836
935
|
let contentText: string | null = null;
|
|
936
|
+
let messageRecordType = recordType;
|
|
837
937
|
|
|
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.
|
|
938
|
+
// history.jsonl prompt index: {display, timestamp, workspace, conversationId?, type?}.
|
|
939
|
+
// One branch covers absent type, 'slash_command', 'shell', and any future producer type.
|
|
940
|
+
// `display` never appears on legacy brain transcripts, so this cannot collide with 0463.
|
|
941
|
+
if (raw.display !== undefined) {
|
|
942
|
+
role = 'user';
|
|
943
|
+
disposition = 'keep';
|
|
944
|
+
contentText = s(raw.display) ?? null;
|
|
945
|
+
messageRecordType = recordType.length > 0 ? recordType : 'USER_INPUT';
|
|
946
|
+
} else {
|
|
947
|
+
// Classification from task 0463 field map (agy transcript types).
|
|
948
|
+
switch (recordType) {
|
|
949
|
+
case 'USER_INPUT':
|
|
950
|
+
case 'ASK_QUESTION':
|
|
951
|
+
role = 'user';
|
|
952
|
+
disposition = 'keep';
|
|
953
|
+
contentText = s(raw.content, raw.text, raw.message) ?? null;
|
|
954
|
+
break;
|
|
955
|
+
case 'PLANNER_RESPONSE':
|
|
956
|
+
role = 'assistant';
|
|
957
|
+
disposition = 'keep';
|
|
958
|
+
contentText = s(raw.content, raw.text, raw.message, raw.plan) ?? null;
|
|
959
|
+
break;
|
|
960
|
+
case 'ERROR_MESSAGE':
|
|
961
|
+
role = 'system';
|
|
962
|
+
disposition = 'keep';
|
|
963
|
+
contentText = s(raw.content, raw.text, raw.message) ?? null;
|
|
964
|
+
break;
|
|
965
|
+
case 'RUN_COMMAND':
|
|
966
|
+
case 'VIEW_FILE':
|
|
967
|
+
case 'GREP_SEARCH':
|
|
968
|
+
case 'LIST_DIRECTORY':
|
|
969
|
+
case 'READ_URL_CONTENT':
|
|
970
|
+
role = 'tool';
|
|
971
|
+
disposition = 'keep';
|
|
972
|
+
contentText = s(raw.content, raw.command, raw.file_path, raw.pattern) ?? null;
|
|
973
|
+
break;
|
|
974
|
+
case 'CODE_ACTION':
|
|
975
|
+
case 'INVOKE_SUBAGENT':
|
|
976
|
+
role = 'tool';
|
|
977
|
+
disposition = 'keep';
|
|
978
|
+
contentText = s(raw.content, raw.text) ?? null;
|
|
979
|
+
break;
|
|
980
|
+
case 'CONVERSATION_HISTORY':
|
|
981
|
+
case 'CHECKPOINT':
|
|
982
|
+
case 'GENERIC':
|
|
885
983
|
role = 'meta';
|
|
886
984
|
disposition = 'meta';
|
|
887
985
|
contentText = s(raw.content, raw.text) ?? null;
|
|
888
|
-
|
|
889
|
-
|
|
986
|
+
break;
|
|
987
|
+
default:
|
|
988
|
+
if (recordType.length === 0) {
|
|
989
|
+
role = 'unknown';
|
|
990
|
+
disposition = 'unknown';
|
|
991
|
+
} else {
|
|
992
|
+
// Known discriminator present but not in the frozen 0463 keep/meta table —
|
|
993
|
+
// still a determined type; treat as meta bookkeeping rather than unknown.
|
|
994
|
+
role = 'meta';
|
|
995
|
+
disposition = 'meta';
|
|
996
|
+
contentText = s(raw.content, raw.text) ?? null;
|
|
997
|
+
}
|
|
998
|
+
break;
|
|
999
|
+
}
|
|
890
1000
|
}
|
|
891
1001
|
|
|
892
1002
|
const messageSplitIndex = entries.length;
|
|
@@ -897,7 +1007,7 @@ export function agySplit(raw: Record<string, unknown>, context?: TransformContex
|
|
|
897
1007
|
session_id: sessionId,
|
|
898
1008
|
seq,
|
|
899
1009
|
role,
|
|
900
|
-
record_type:
|
|
1010
|
+
record_type: messageRecordType,
|
|
901
1011
|
disposition,
|
|
902
1012
|
ts,
|
|
903
1013
|
duration_ms: undefined,
|
|
@@ -908,7 +1018,7 @@ export function agySplit(raw: Record<string, unknown>, context?: TransformContex
|
|
|
908
1018
|
cache_write_tokens: null,
|
|
909
1019
|
cost_usd: null,
|
|
910
1020
|
content_text: contentText,
|
|
911
|
-
cwd: null,
|
|
1021
|
+
cwd: s(raw.workspace, raw.cwd) ?? null,
|
|
912
1022
|
provenance: 'ambient',
|
|
913
1023
|
},
|
|
914
1024
|
});
|
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,
|