@jsonstudio/llms 0.6.753 → 0.6.802

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.
Files changed (48) hide show
  1. package/dist/conversion/compat/actions/apply-patch-fixer.d.ts +1 -0
  2. package/dist/conversion/compat/actions/apply-patch-fixer.js +30 -0
  3. package/dist/conversion/compat/actions/apply-patch-format-fixer.d.ts +1 -0
  4. package/dist/conversion/compat/actions/apply-patch-format-fixer.js +233 -0
  5. package/dist/conversion/compat/actions/index.d.ts +2 -0
  6. package/dist/conversion/compat/actions/index.js +2 -0
  7. package/dist/conversion/compat/profiles/chat-gemini.json +15 -15
  8. package/dist/conversion/compat/profiles/chat-glm.json +194 -194
  9. package/dist/conversion/compat/profiles/chat-iflow.json +199 -199
  10. package/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
  11. package/dist/conversion/compat/profiles/chat-qwen.json +20 -20
  12. package/dist/conversion/compat/profiles/responses-c4m.json +42 -42
  13. package/dist/conversion/compat/profiles/responses-output2choices-test.json +10 -9
  14. package/dist/conversion/hub/pipeline/context-limit.d.ts +13 -0
  15. package/dist/conversion/hub/pipeline/context-limit.js +55 -0
  16. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +6 -0
  17. package/dist/conversion/hub/pipeline/hub-pipeline.js +35 -0
  18. package/dist/conversion/shared/bridge-message-utils.d.ts +1 -0
  19. package/dist/conversion/shared/bridge-message-utils.js +7 -0
  20. package/dist/conversion/shared/bridge-policies.js +8 -8
  21. package/dist/conversion/shared/snapshot-hooks.js +54 -1
  22. package/dist/conversion/shared/tool-governor.js +18 -23
  23. package/dist/filters/special/response-tool-arguments-stringify.js +3 -22
  24. package/dist/router/virtual-router/engine-selection.js +49 -4
  25. package/dist/router/virtual-router/engine.d.ts +5 -0
  26. package/dist/router/virtual-router/engine.js +21 -0
  27. package/dist/tools/apply-patch/regression-capturer.d.ts +12 -0
  28. package/dist/tools/apply-patch/regression-capturer.js +112 -0
  29. package/dist/tools/apply-patch/structured.d.ts +20 -0
  30. package/dist/tools/apply-patch/structured.js +441 -0
  31. package/dist/tools/apply-patch/validator.d.ts +8 -0
  32. package/dist/tools/apply-patch/validator.js +616 -0
  33. package/dist/tools/apply-patch-structured.d.ts +1 -20
  34. package/dist/tools/apply-patch-structured.js +1 -277
  35. package/dist/tools/args-json.d.ts +1 -0
  36. package/dist/tools/args-json.js +175 -0
  37. package/dist/tools/exec-command/normalize.d.ts +17 -0
  38. package/dist/tools/exec-command/normalize.js +112 -0
  39. package/dist/tools/exec-command/regression-capturer.d.ts +11 -0
  40. package/dist/tools/exec-command/regression-capturer.js +144 -0
  41. package/dist/tools/exec-command/validator.d.ts +6 -0
  42. package/dist/tools/exec-command/validator.js +22 -0
  43. package/dist/tools/patch-args-normalizer.d.ts +15 -0
  44. package/dist/tools/patch-args-normalizer.js +472 -0
  45. package/dist/tools/patch-regression-capturer.d.ts +1 -0
  46. package/dist/tools/patch-regression-capturer.js +1 -0
  47. package/dist/tools/tool-registry.js +36 -541
  48. package/package.json +1 -1
@@ -1,277 +1 @@
1
- export class StructuredApplyPatchError extends Error {
2
- reason;
3
- constructor(reason, message) {
4
- super(message);
5
- this.reason = reason;
6
- }
7
- }
8
- const SUPPORTED_KINDS = [
9
- 'insert_after',
10
- 'insert_before',
11
- 'replace',
12
- 'delete',
13
- 'create_file',
14
- 'delete_file'
15
- ];
16
- const FILE_PATH_INVALID_RE = /[\r\n]/;
17
- const decodeEscapedNewlinesIfObvious = (value) => {
18
- if (!value)
19
- return value;
20
- if (value.includes('\n'))
21
- return value;
22
- const lower = value.toLowerCase();
23
- const looksEscaped = value.includes('\\r\\n') ||
24
- (value.includes('\\n') && /\\n[ \t]/.test(value)) ||
25
- lower.includes('\\u000a') ||
26
- lower.includes('\\u000d');
27
- if (!looksEscaped) {
28
- return value;
29
- }
30
- let out = value;
31
- out = out.replace(/\\r\\n/g, '\n');
32
- out = out.replace(/\\n/g, '\n');
33
- out = out.replace(/\\r/g, '\n');
34
- out = out.replace(/\\u000a/gi, '\n');
35
- out = out.replace(/\\u000d/gi, '\n');
36
- return out;
37
- };
38
- const toSafeString = (value, label) => {
39
- const str = typeof value === 'string' ? value : '';
40
- if (!str.trim()) {
41
- throw new StructuredApplyPatchError('missing_field', `${label} is required`);
42
- }
43
- return str;
44
- };
45
- const normalizeFilePath = (raw, label) => {
46
- let trimmed = raw.trim();
47
- if (!trimmed) {
48
- throw new StructuredApplyPatchError('invalid_file', `${label} must not be empty`);
49
- }
50
- if (FILE_PATH_INVALID_RE.test(trimmed)) {
51
- const firstLine = trimmed.split(/[\r\n]/)[0]?.trim() ?? '';
52
- if (!firstLine) {
53
- throw new StructuredApplyPatchError('invalid_file', `${label} must be a single-line path`);
54
- }
55
- trimmed = firstLine;
56
- }
57
- return trimmed.replace(/\\/g, '/');
58
- };
59
- const splitTextIntoLines = (input) => {
60
- const decoded = decodeEscapedNewlinesIfObvious(input);
61
- const normalized = decoded.replace(/\r/g, '');
62
- const parts = normalized.split('\n');
63
- if (parts.length && parts[parts.length - 1] === '') {
64
- parts.pop();
65
- }
66
- return parts.length ? parts : [''];
67
- };
68
- const normalizeLines = (value, label) => {
69
- if (Array.isArray(value)) {
70
- if (!value.length) {
71
- return [];
72
- }
73
- const out = [];
74
- for (const [idx, entry] of value.entries()) {
75
- if (typeof entry !== 'string') {
76
- if (entry === null || entry === undefined) {
77
- out.push('');
78
- continue;
79
- }
80
- out.push(String(entry));
81
- continue;
82
- }
83
- // Preserve intentional whitespace
84
- const normalized = entry.replace(/\r/g, '');
85
- const decoded = decodeEscapedNewlinesIfObvious(normalized);
86
- if (decoded.includes('\n')) {
87
- out.push(...splitTextIntoLines(decoded));
88
- }
89
- else {
90
- out.push(decoded);
91
- }
92
- }
93
- return out;
94
- }
95
- if (typeof value === 'string') {
96
- return splitTextIntoLines(value);
97
- }
98
- if (value === null || value === undefined) {
99
- throw new StructuredApplyPatchError('invalid_lines', `${label} must be an array of strings or a multi-line string`);
100
- }
101
- return [String(value)];
102
- };
103
- const buildContextLines = (raw) => splitTextIntoLines(raw).map((line) => ` ${line}`);
104
- const buildPrefixedLines = (lines, prefix) => lines.map((line) => `${prefix}${line}`);
105
- const detectIndentFromAnchor = (anchorLines, mode) => {
106
- const source = mode === 'first' ? anchorLines[0] ?? '' : anchorLines[anchorLines.length - 1] ?? '';
107
- const match = source.match(/^(\s*)/);
108
- return match ? match[1] ?? '' : '';
109
- };
110
- const applyAnchorIndent = (lines, anchorLines, position, enabled) => {
111
- if (!enabled) {
112
- return lines;
113
- }
114
- const indent = detectIndentFromAnchor(anchorLines, position);
115
- if (!indent) {
116
- return lines;
117
- }
118
- return lines.map((line) => {
119
- if (!line.trim()) {
120
- return line;
121
- }
122
- if (/^\s/.test(line)) {
123
- return line;
124
- }
125
- return `${indent}${line}`;
126
- });
127
- };
128
- export function buildStructuredPatch(payload) {
129
- if (!payload || typeof payload !== 'object') {
130
- throw new StructuredApplyPatchError('missing_payload', 'apply_patch arguments must be a JSON object');
131
- }
132
- if (!Array.isArray(payload.changes) || payload.changes.length === 0) {
133
- throw new StructuredApplyPatchError('missing_changes', 'apply_patch requires a non-empty "changes" array');
134
- }
135
- const topLevelFile = typeof payload.file === 'string' && payload.file.trim()
136
- ? normalizeFilePath(payload.file, 'file')
137
- : undefined;
138
- const sectionOrder = [];
139
- const fileSections = new Map();
140
- const ensureUpdateSection = (file) => {
141
- const existing = fileSections.get(file);
142
- if (existing) {
143
- if (existing.type !== 'update') {
144
- throw new StructuredApplyPatchError('invalid_change_sequence', `File "${file}" already marked as ${existing.type}`);
145
- }
146
- return existing;
147
- }
148
- const created = { type: 'update', hunks: [] };
149
- sectionOrder.push(file);
150
- fileSections.set(file, created);
151
- return created;
152
- };
153
- for (const [index, change] of payload.changes.entries()) {
154
- if (!change || typeof change !== 'object') {
155
- throw new StructuredApplyPatchError('invalid_change', `Change at index ${index} must be an object`);
156
- }
157
- const kindRaw = typeof change.kind === 'string' ? change.kind.trim().toLowerCase() : '';
158
- if (!kindRaw) {
159
- throw new StructuredApplyPatchError('invalid_change_kind', `Change at index ${index} is missing "kind"`);
160
- }
161
- if (!SUPPORTED_KINDS.includes(kindRaw)) {
162
- throw new StructuredApplyPatchError('invalid_change_kind', `Unsupported change kind "${change.kind}" at index ${index}`);
163
- }
164
- const file = change.file ? normalizeFilePath(change.file, `changes[${index}].file`) : topLevelFile;
165
- if (!file) {
166
- throw new StructuredApplyPatchError('invalid_file', `Change at index ${index} is missing "file"`);
167
- }
168
- if (kindRaw === 'create_file') {
169
- if (fileSections.has(file)) {
170
- throw new StructuredApplyPatchError('invalid_change_sequence', `File "${file}" already has pending changes`);
171
- }
172
- const lines = normalizeLines(change.lines, `changes[${index}].lines`);
173
- sectionOrder.push(file);
174
- fileSections.set(file, { type: 'add', lines });
175
- continue;
176
- }
177
- if (kindRaw === 'delete_file') {
178
- if (fileSections.has(file)) {
179
- throw new StructuredApplyPatchError('invalid_change_sequence', `File "${file}" already has pending changes`);
180
- }
181
- sectionOrder.push(file);
182
- fileSections.set(file, { type: 'delete' });
183
- continue;
184
- }
185
- const section = ensureUpdateSection(file);
186
- switch (kindRaw) {
187
- case 'insert_after': {
188
- const anchor = toSafeString(change.anchor, `changes[${index}].anchor`);
189
- const anchorLines = splitTextIntoLines(anchor);
190
- const additions = normalizeLines(change.lines, `changes[${index}].lines`);
191
- if (!additions.length) {
192
- throw new StructuredApplyPatchError('invalid_lines', `changes[${index}].lines must include at least one line`);
193
- }
194
- const prepared = applyAnchorIndent(additions, anchorLines, 'last', change.use_anchor_indent);
195
- const hunkBody = [...buildContextLines(anchor), ...buildPrefixedLines(prepared, '+')];
196
- section.hunks.push(hunkBody);
197
- break;
198
- }
199
- case 'insert_before': {
200
- const anchor = toSafeString(change.anchor, `changes[${index}].anchor`);
201
- const anchorLines = splitTextIntoLines(anchor);
202
- const additions = normalizeLines(change.lines, `changes[${index}].lines`);
203
- if (!additions.length) {
204
- throw new StructuredApplyPatchError('invalid_lines', `changes[${index}].lines must include at least one line`);
205
- }
206
- const prepared = applyAnchorIndent(additions, anchorLines, 'first', change.use_anchor_indent);
207
- const hunkBody = [...buildPrefixedLines(prepared, '+'), ...buildContextLines(anchor)];
208
- section.hunks.push(hunkBody);
209
- break;
210
- }
211
- case 'replace': {
212
- // 兼容仅提供 anchor 的 replace 形态:将 anchor 视为 target 以尽可能保留用户意图。
213
- const targetSource = change.target ?? change.anchor;
214
- const target = toSafeString(targetSource, `changes[${index}].target`);
215
- const replacements = normalizeLines(change.lines, `changes[${index}].lines`);
216
- const hunkBody = [
217
- ...buildPrefixedLines(splitTextIntoLines(target), '-'),
218
- ...buildPrefixedLines(replacements, '+')
219
- ];
220
- section.hunks.push(hunkBody);
221
- break;
222
- }
223
- case 'delete': {
224
- const target = toSafeString(change.target, `changes[${index}].target`);
225
- const hunkBody = buildPrefixedLines(splitTextIntoLines(target), '-');
226
- section.hunks.push(hunkBody);
227
- break;
228
- }
229
- default: {
230
- throw new StructuredApplyPatchError('invalid_change_kind', `Unsupported change kind "${change.kind}" at index ${index}`);
231
- }
232
- }
233
- }
234
- if (!sectionOrder.length) {
235
- throw new StructuredApplyPatchError('missing_changes', 'apply_patch payload produced no file operations');
236
- }
237
- const lines = ['*** Begin Patch'];
238
- for (const file of sectionOrder) {
239
- const section = fileSections.get(file);
240
- if (!section)
241
- continue;
242
- if (section.type === 'add') {
243
- lines.push(`*** Add File: ${file}`);
244
- for (const line of section.lines) {
245
- lines.push(`+${line}`);
246
- }
247
- }
248
- else if (section.type === 'delete') {
249
- lines.push(`*** Delete File: ${file}`);
250
- }
251
- else {
252
- lines.push(`*** Update File: ${file}`);
253
- const hunks = section.hunks || [];
254
- for (const hunk of hunks) {
255
- // 结构化补丁仅负责生成统一 diff 形态,不对多段 hunk 做逻辑裁剪;
256
- // 具体哪些 hunk 能成功应用由 apply_patch 客户端自行校验并返回错误信息。
257
- for (const entry of hunk) {
258
- if (!entry.startsWith('@@')) {
259
- lines.push(entry);
260
- }
261
- }
262
- }
263
- }
264
- }
265
- lines.push('*** End Patch');
266
- return lines.join('\n');
267
- }
268
- export function isStructuredApplyPatchPayload(candidate) {
269
- if (!candidate || typeof candidate !== 'object') {
270
- return false;
271
- }
272
- const record = candidate;
273
- if (!Array.isArray(record.changes)) {
274
- return false;
275
- }
276
- return true;
277
- }
1
+ export * from './apply-patch/structured.js';
@@ -0,0 +1 @@
1
+ export declare function parseToolArgsJson(input: unknown): unknown;
@@ -0,0 +1,175 @@
1
+ // Shared JSON argument parsing helpers.
2
+ // Goal: tolerate common markup artifacts injected by upstream providers (e.g. <arg_key>/<arg_value>),
3
+ // while keeping behavior deterministic and side-effect free.
4
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
5
+ const stripXmlLikeTags = (input) => {
6
+ try {
7
+ return String(input || '').replace(/<[^>]+>/g, '');
8
+ }
9
+ catch {
10
+ return input;
11
+ }
12
+ };
13
+ const stripArgKeyArtifacts = (input) => {
14
+ try {
15
+ return String(input || '')
16
+ .replace(/<\/?\s*tool_call[^>]*>/gi, '')
17
+ .replace(/<\/?\s*arg_key\s*>/gi, '')
18
+ .replace(/<\/?\s*arg_value\s*>/gi, '');
19
+ }
20
+ catch {
21
+ return input;
22
+ }
23
+ };
24
+ const normalizeObjectKey = (rawKey) => {
25
+ const cleaned = stripXmlLikeTags(stripArgKeyArtifacts(rawKey)).trim();
26
+ if (!cleaned)
27
+ return rawKey;
28
+ return cleaned;
29
+ };
30
+ const coercePrimitive = (raw) => {
31
+ const trimmed = raw.trim();
32
+ if (!trimmed)
33
+ return '';
34
+ if (/^(true|false)$/i.test(trimmed))
35
+ return /^true$/i.test(trimmed);
36
+ if (/^-?\d+(?:\.\d+)?$/.test(trimmed))
37
+ return Number(trimmed);
38
+ if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
39
+ (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
40
+ try {
41
+ return JSON.parse(trimmed);
42
+ }
43
+ catch {
44
+ return trimmed;
45
+ }
46
+ }
47
+ return trimmed;
48
+ };
49
+ const extractInjectedArgPairs = (raw) => {
50
+ const delimiter = '</arg_key><arg_value>';
51
+ if (typeof raw !== 'string' || !raw.includes(delimiter))
52
+ return null;
53
+ const parts = raw.split(delimiter);
54
+ if (parts.length < 2)
55
+ return null;
56
+ const looksLikeKey = (s) => /^[A-Za-z_][A-Za-z0-9_-]*$/.test(s.trim());
57
+ const pairs = [];
58
+ let baseValue = parts[0] ?? '';
59
+ if (parts.length === 2) {
60
+ const k = (parts[0] ?? '').trim();
61
+ const v = (parts[1] ?? '').trim();
62
+ if (looksLikeKey(k) && v.length > 0) {
63
+ baseValue = '';
64
+ pairs.push({ key: k, value: coercePrimitive(v) });
65
+ }
66
+ return pairs.length ? { baseValue, pairs } : null;
67
+ }
68
+ for (let i = 1; i + 1 < parts.length; i += 2) {
69
+ const key = (parts[i] ?? '').trim();
70
+ const rawValue = (parts[i + 1] ?? '').trim();
71
+ if (!looksLikeKey(key)) {
72
+ continue;
73
+ }
74
+ if (rawValue.length === 0) {
75
+ continue;
76
+ }
77
+ pairs.push({ key, value: coercePrimitive(rawValue) });
78
+ }
79
+ if (!pairs.length)
80
+ return null;
81
+ return { baseValue, pairs };
82
+ };
83
+ const repairArgKeyArtifactsInKeys = (value) => {
84
+ const visit = (node) => {
85
+ if (Array.isArray(node)) {
86
+ for (const entry of node)
87
+ visit(entry);
88
+ return;
89
+ }
90
+ if (!isRecord(node))
91
+ return;
92
+ const keys = Object.keys(node);
93
+ for (const key of keys) {
94
+ const normalizedKey = normalizeObjectKey(key);
95
+ if (normalizedKey !== key && normalizedKey.trim()) {
96
+ if (!Object.prototype.hasOwnProperty.call(node, normalizedKey)) {
97
+ node[normalizedKey] = node[key];
98
+ }
99
+ delete node[key];
100
+ }
101
+ }
102
+ for (const v of Object.values(node))
103
+ visit(v);
104
+ };
105
+ visit(value);
106
+ };
107
+ const repairArgKeyArtifactsInObject = (value) => {
108
+ const visit = (node) => {
109
+ if (Array.isArray(node)) {
110
+ for (const entry of node)
111
+ visit(entry);
112
+ return;
113
+ }
114
+ if (!isRecord(node))
115
+ return;
116
+ for (const [k, v] of Object.entries(node)) {
117
+ if (typeof v === 'string') {
118
+ const injected = extractInjectedArgPairs(v);
119
+ if (injected) {
120
+ if (injected.baseValue !== '') {
121
+ node[k] = injected.baseValue;
122
+ }
123
+ for (const pair of injected.pairs) {
124
+ if (!Object.prototype.hasOwnProperty.call(node, pair.key)) {
125
+ node[pair.key] = pair.value;
126
+ }
127
+ }
128
+ }
129
+ }
130
+ visit(node[k]);
131
+ }
132
+ };
133
+ visit(value);
134
+ };
135
+ export function parseToolArgsJson(input) {
136
+ const raw = typeof input === 'string' ? input : '';
137
+ if (!raw.trim())
138
+ return {};
139
+ try {
140
+ const parsed = JSON.parse(raw);
141
+ repairArgKeyArtifactsInKeys(parsed);
142
+ repairArgKeyArtifactsInObject(parsed);
143
+ return parsed;
144
+ }
145
+ catch {
146
+ // try stripping common tool-call markup artifacts and parsing again
147
+ try {
148
+ const stripped = stripArgKeyArtifacts(raw).trim();
149
+ if (stripped && stripped !== raw) {
150
+ const parsed = JSON.parse(stripped);
151
+ repairArgKeyArtifactsInKeys(parsed);
152
+ repairArgKeyArtifactsInObject(parsed);
153
+ return parsed;
154
+ }
155
+ }
156
+ catch {
157
+ // continue
158
+ }
159
+ // attempt to parse the first JSON container substring
160
+ try {
161
+ const candidate = raw.match(/\{[\s\S]*\}|\[[\s\S]*\]/)?.[0];
162
+ if (candidate) {
163
+ const strippedCandidate = stripArgKeyArtifacts(candidate).trim();
164
+ const parsed = JSON.parse(strippedCandidate);
165
+ repairArgKeyArtifactsInKeys(parsed);
166
+ repairArgKeyArtifactsInObject(parsed);
167
+ return parsed;
168
+ }
169
+ }
170
+ catch {
171
+ // ignore
172
+ }
173
+ return {};
174
+ }
175
+ }
@@ -0,0 +1,17 @@
1
+ type UnknownRecord = Record<string, unknown>;
2
+ export type ExecCommandNormalizeResult = {
3
+ ok: true;
4
+ normalized: UnknownRecord;
5
+ } | {
6
+ ok: false;
7
+ reason: 'missing_cmd';
8
+ normalized: UnknownRecord;
9
+ };
10
+ /**
11
+ * Normalize Codex CLI exec_command arguments (shape fixes only):
12
+ * - accept cmd/command/toon as sources for cmd
13
+ * - drop "toon" (must never be exposed to client)
14
+ * - repair common `find -exec ... ;` meta issues in cmd
15
+ */
16
+ export declare function normalizeExecCommandArgs(args: unknown): ExecCommandNormalizeResult;
17
+ export {};
@@ -0,0 +1,112 @@
1
+ import { repairFindMeta } from '../../conversion/shared/tooling.js';
2
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
3
+ const asNonEmptyString = (value) => {
4
+ if (typeof value !== 'string')
5
+ return undefined;
6
+ const trimmed = value.trim();
7
+ return trimmed ? trimmed : undefined;
8
+ };
9
+ const asPrimitiveString = (value) => {
10
+ if (typeof value === 'string')
11
+ return asNonEmptyString(value);
12
+ if (typeof value === 'number' && Number.isFinite(value))
13
+ return String(value);
14
+ if (typeof value === 'boolean')
15
+ return String(value);
16
+ return undefined;
17
+ };
18
+ const asFiniteNumber = (value) => {
19
+ if (typeof value !== 'number' || !Number.isFinite(value))
20
+ return undefined;
21
+ return value;
22
+ };
23
+ const asBoolean = (value) => {
24
+ if (typeof value !== 'boolean')
25
+ return undefined;
26
+ return value;
27
+ };
28
+ const asStringArray = (value) => {
29
+ if (!Array.isArray(value))
30
+ return undefined;
31
+ const out = [];
32
+ for (const entry of value) {
33
+ if (typeof entry === 'string') {
34
+ out.push(entry);
35
+ continue;
36
+ }
37
+ if (entry == null)
38
+ continue;
39
+ out.push(String(entry));
40
+ }
41
+ return out.length ? out : undefined;
42
+ };
43
+ /**
44
+ * Normalize Codex CLI exec_command arguments (shape fixes only):
45
+ * - accept cmd/command/toon as sources for cmd
46
+ * - drop "toon" (must never be exposed to client)
47
+ * - repair common `find -exec ... ;` meta issues in cmd
48
+ */
49
+ export function normalizeExecCommandArgs(args) {
50
+ const base = isRecord(args) ? { ...args } : {};
51
+ const cmdCandidate = asPrimitiveString(base.cmd) ??
52
+ asPrimitiveString(base.command) ??
53
+ asPrimitiveString(base.toon) ??
54
+ asPrimitiveString(base.script) ??
55
+ (() => {
56
+ const arr = asStringArray(base.command) ?? asStringArray(base.cmd);
57
+ return arr ? arr.join(' ') : undefined;
58
+ })();
59
+ // Always drop toon from outward-facing args (even if used as cmd source).
60
+ try {
61
+ if (Object.prototype.hasOwnProperty.call(base, 'toon')) {
62
+ delete base.toon;
63
+ }
64
+ }
65
+ catch {
66
+ // ignore
67
+ }
68
+ if (!cmdCandidate) {
69
+ // Keep captured samples free of hidden TOON payloads.
70
+ try {
71
+ if (Object.prototype.hasOwnProperty.call(base, 'toon')) {
72
+ delete base.toon;
73
+ }
74
+ }
75
+ catch {
76
+ // ignore
77
+ }
78
+ return { ok: false, reason: 'missing_cmd', normalized: base };
79
+ }
80
+ const fixedCmd = repairFindMeta(cmdCandidate);
81
+ const normalized = { cmd: fixedCmd };
82
+ const workdir = asNonEmptyString(base.workdir) ??
83
+ asNonEmptyString(base.cwd) ??
84
+ asNonEmptyString(base.workDir);
85
+ if (workdir)
86
+ normalized.workdir = workdir;
87
+ const login = asBoolean(base.login);
88
+ if (login !== undefined)
89
+ normalized.login = login;
90
+ const tty = asBoolean(base.tty);
91
+ if (tty !== undefined)
92
+ normalized.tty = tty;
93
+ const shell = asNonEmptyString(base.shell);
94
+ if (shell)
95
+ normalized.shell = shell;
96
+ const sandboxPermissions = asNonEmptyString(base.sandbox_permissions) ??
97
+ (asBoolean(base.with_escalated_permissions) ? 'require_escalated' : undefined);
98
+ if (sandboxPermissions)
99
+ normalized.sandbox_permissions = sandboxPermissions;
100
+ const justification = asNonEmptyString(base.justification);
101
+ if (justification)
102
+ normalized.justification = justification;
103
+ const maxOutput = asFiniteNumber(base.max_output_tokens) ?? asFiniteNumber(base.max_tokens);
104
+ if (maxOutput !== undefined)
105
+ normalized.max_output_tokens = maxOutput;
106
+ const yieldTime = asFiniteNumber(base.yield_time_ms) ??
107
+ asFiniteNumber(base.yield_ms) ??
108
+ asFiniteNumber(base.wait_ms);
109
+ if (yieldTime !== undefined)
110
+ normalized.yield_time_ms = yieldTime;
111
+ return { ok: true, normalized };
112
+ }
@@ -0,0 +1,11 @@
1
+ interface RegressionSample {
2
+ id: string;
3
+ timestamp: string;
4
+ errorType: string;
5
+ originalArgs: string;
6
+ normalizedArgs?: string;
7
+ validationError?: string;
8
+ source?: string;
9
+ }
10
+ export declare function captureExecCommandRegression(sample: Omit<RegressionSample, 'id' | 'timestamp'>): void;
11
+ export {};