@gobing-ai/ts-llm-jsonl-importer 0.4.49 → 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/dist/mappers.d.ts +4 -5
- package/dist/mappers.d.ts.map +1 -1
- package/dist/mappers.js +100 -57
- package/package.json +4 -4
- package/src/mappers.ts +108 -56
package/dist/mappers.d.ts
CHANGED
|
@@ -3,12 +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 retained raw args for
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* already — store as-is for todo tools.
|
|
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.
|
|
10
9
|
*/
|
|
11
|
-
export declare function maybeArgsRaw(
|
|
10
|
+
export declare function maybeArgsRaw(_source: string, toolName: string, args: unknown): string | undefined;
|
|
12
11
|
/** Map a Claude Code JSONL record into one or more ETL split entries. */
|
|
13
12
|
export declare function claudeSplit(raw: Record<string, unknown>): readonly SplitEntry[];
|
|
14
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,79 +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).
|
|
101
|
-
*/
|
|
102
|
-
const TODO_TOOL_ALLOWLIST = {
|
|
103
|
-
claude: ['TodoWrite'],
|
|
104
|
-
opencode: ['todowrite', 'todoread'],
|
|
105
|
-
codex: ['update_plan'],
|
|
106
|
-
omp: ['TodoWrite', 'todo', 'todo_write'],
|
|
107
|
-
grok: ['todo_write'],
|
|
108
|
-
pi: ['todo', 'manage_todo_list'],
|
|
109
|
-
agy: [],
|
|
110
|
-
gemini: [],
|
|
111
|
-
};
|
|
112
|
-
/**
|
|
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).
|
|
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).
|
|
120
99
|
*/
|
|
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
100
|
const BASH_COMMAND_MAX_CHARS = 8192;
|
|
101
|
+
const GENERIC_ARGS_MAX_CHARS = 8192;
|
|
133
102
|
/** Extract the shell command from a bash tool's raw input; undefined when absent. */
|
|
134
103
|
function bashCommandOf(args) {
|
|
135
104
|
if (args === null || typeof args !== 'object')
|
|
136
105
|
return undefined;
|
|
137
|
-
const
|
|
106
|
+
const rec = args;
|
|
107
|
+
const command = rec.command ?? rec.CommandLine ?? rec.cmd ?? rec.script;
|
|
138
108
|
if (typeof command === 'string')
|
|
139
109
|
return command;
|
|
140
110
|
if (Array.isArray(command))
|
|
141
111
|
return command.map(String).join(' ');
|
|
142
112
|
return undefined;
|
|
143
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
|
+
}
|
|
144
134
|
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
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).
|
|
149
139
|
*/
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (typeof args === 'string')
|
|
154
|
-
return args;
|
|
155
|
-
if (args === undefined || args === null)
|
|
156
|
-
return undefined;
|
|
157
|
-
return JSON.stringify(args);
|
|
140
|
+
function sanitizeValue(value) {
|
|
141
|
+
if (typeof value === 'string') {
|
|
142
|
+
return value.length > 1000 ? `${value.slice(0, 1000)}[truncated ${value.length} chars]` : value;
|
|
158
143
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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;
|
|
165
155
|
}
|
|
166
|
-
|
|
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) {
|
|
188
|
+
if (args === undefined || args === null)
|
|
189
|
+
return undefined;
|
|
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);
|
|
167
210
|
}
|
|
168
211
|
// ---------------------------------------------------------------------------
|
|
169
212
|
// Claude mapper
|
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,77 +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).
|
|
111
|
-
*/
|
|
112
|
-
const TODO_TOOL_ALLOWLIST: Readonly<Record<string, readonly string[]>> = {
|
|
113
|
-
claude: ['TodoWrite'],
|
|
114
|
-
opencode: ['todowrite', 'todoread'],
|
|
115
|
-
codex: ['update_plan'],
|
|
116
|
-
omp: ['TodoWrite', 'todo', 'todo_write'],
|
|
117
|
-
grok: ['todo_write'],
|
|
118
|
-
pi: ['todo', 'manage_todo_list'],
|
|
119
|
-
agy: [],
|
|
120
|
-
gemini: [],
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
/**
|
|
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).
|
|
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).
|
|
131
109
|
*/
|
|
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
110
|
const BASH_COMMAND_MAX_CHARS = 8192;
|
|
111
|
+
const GENERIC_ARGS_MAX_CHARS = 8192;
|
|
145
112
|
|
|
146
113
|
/** Extract the shell command from a bash tool's raw input; undefined when absent. */
|
|
147
114
|
function bashCommandOf(args: unknown): string | undefined {
|
|
148
115
|
if (args === null || typeof args !== 'object') return undefined;
|
|
149
|
-
const
|
|
116
|
+
const rec = args as Record<string, unknown>;
|
|
117
|
+
const command = rec.command ?? rec.CommandLine ?? rec.cmd ?? rec.script;
|
|
150
118
|
if (typeof command === 'string') return command;
|
|
151
119
|
if (Array.isArray(command)) return command.map(String).join(' ');
|
|
152
120
|
return undefined;
|
|
153
121
|
}
|
|
154
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 };
|
|
150
|
+
|
|
155
151
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
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).
|
|
160
156
|
*/
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (typeof args === 'string') return args;
|
|
165
|
-
if (args === undefined || args === null) return undefined;
|
|
166
|
-
return JSON.stringify(args);
|
|
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;
|
|
167
160
|
}
|
|
168
|
-
|
|
169
|
-
if (
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
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;
|
|
173
171
|
}
|
|
174
|
-
|
|
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 {
|
|
204
|
+
if (args === undefined || args === null) return undefined;
|
|
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);
|
|
175
227
|
}
|
|
176
228
|
|
|
177
229
|
// ---------------------------------------------------------------------------
|