@jsonstudio/llms 0.6.567 → 0.6.568

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 (55) hide show
  1. package/dist/conversion/codecs/gemini-openai-codec.js +33 -4
  2. package/dist/conversion/codecs/openai-openai-codec.js +2 -1
  3. package/dist/conversion/codecs/responses-openai-codec.js +3 -2
  4. package/dist/conversion/compat/actions/glm-history-image-trim.d.ts +2 -0
  5. package/dist/conversion/compat/actions/glm-history-image-trim.js +88 -0
  6. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +6 -1
  7. package/dist/conversion/hub/pipeline/hub-pipeline.js +25 -13
  8. package/dist/conversion/hub/process/chat-process.js +65 -11
  9. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +16 -3
  10. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +51 -2
  11. package/dist/conversion/hub/types/chat-envelope.d.ts +1 -0
  12. package/dist/conversion/shared/anthropic-message-utils.js +54 -0
  13. package/dist/conversion/shared/args-mapping.js +11 -3
  14. package/dist/conversion/shared/responses-output-builder.js +42 -21
  15. package/dist/conversion/shared/streaming-text-extractor.d.ts +25 -0
  16. package/dist/conversion/shared/streaming-text-extractor.js +31 -38
  17. package/dist/conversion/shared/text-markup-normalizer.js +42 -27
  18. package/dist/conversion/shared/tool-filter-pipeline.js +2 -1
  19. package/dist/conversion/shared/tool-harvester.js +43 -12
  20. package/dist/conversion/shared/tool-mapping.d.ts +1 -0
  21. package/dist/conversion/shared/tool-mapping.js +33 -19
  22. package/dist/filters/index.d.ts +1 -0
  23. package/dist/filters/index.js +1 -0
  24. package/dist/filters/special/request-tools-normalize.js +14 -4
  25. package/dist/filters/special/response-apply-patch-toon-decode.d.ts +23 -0
  26. package/dist/filters/special/response-apply-patch-toon-decode.js +109 -0
  27. package/dist/filters/special/response-tool-arguments-toon-decode.d.ts +10 -0
  28. package/dist/filters/special/response-tool-arguments-toon-decode.js +55 -13
  29. package/dist/guidance/index.js +69 -42
  30. package/dist/router/virtual-router/bootstrap.js +10 -5
  31. package/dist/router/virtual-router/classifier.js +9 -4
  32. package/dist/router/virtual-router/engine-health.d.ts +11 -0
  33. package/dist/router/virtual-router/engine-health.js +217 -4
  34. package/dist/router/virtual-router/engine-logging.d.ts +2 -1
  35. package/dist/router/virtual-router/engine-logging.js +35 -3
  36. package/dist/router/virtual-router/engine.d.ts +17 -1
  37. package/dist/router/virtual-router/engine.js +154 -6
  38. package/dist/router/virtual-router/routing-instructions.d.ts +2 -0
  39. package/dist/router/virtual-router/routing-instructions.js +19 -1
  40. package/dist/router/virtual-router/tool-signals.js +57 -11
  41. package/dist/router/virtual-router/types.d.ts +30 -0
  42. package/dist/router/virtual-router/types.js +1 -1
  43. package/dist/servertool/engine.js +3 -0
  44. package/dist/servertool/handlers/iflow-model-error-retry.d.ts +1 -0
  45. package/dist/servertool/handlers/iflow-model-error-retry.js +93 -0
  46. package/dist/servertool/handlers/stop-message-auto.js +61 -4
  47. package/dist/servertool/server-side-tools.d.ts +1 -0
  48. package/dist/servertool/server-side-tools.js +27 -0
  49. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +16 -0
  50. package/dist/tools/apply-patch-structured.d.ts +20 -0
  51. package/dist/tools/apply-patch-structured.js +239 -0
  52. package/dist/tools/tool-description-utils.d.ts +5 -0
  53. package/dist/tools/tool-description-utils.js +50 -0
  54. package/dist/tools/tool-registry.js +11 -193
  55. package/package.json +2 -2
@@ -0,0 +1,239 @@
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 INVALID_FILE_FORMAT_RE = /^([A-Za-z]:|\/)/;
18
+ const toSafeString = (value, label) => {
19
+ const str = typeof value === 'string' ? value : '';
20
+ if (!str.trim()) {
21
+ throw new StructuredApplyPatchError('missing_field', `${label} is required`);
22
+ }
23
+ return str;
24
+ };
25
+ const normalizeFilePath = (raw, label) => {
26
+ const trimmed = raw.trim();
27
+ if (!trimmed) {
28
+ throw new StructuredApplyPatchError('invalid_file', `${label} must not be empty`);
29
+ }
30
+ if (FILE_PATH_INVALID_RE.test(trimmed)) {
31
+ throw new StructuredApplyPatchError('invalid_file', `${label} must be a single-line relative path`);
32
+ }
33
+ if (INVALID_FILE_FORMAT_RE.test(trimmed)) {
34
+ throw new StructuredApplyPatchError('invalid_file', `${label} must be relative to the workspace root`);
35
+ }
36
+ return trimmed.replace(/\\/g, '/');
37
+ };
38
+ const splitTextIntoLines = (input) => {
39
+ const normalized = input.replace(/\r/g, '');
40
+ const parts = normalized.split('\n');
41
+ if (parts.length && parts[parts.length - 1] === '') {
42
+ parts.pop();
43
+ }
44
+ return parts.length ? parts : [''];
45
+ };
46
+ const normalizeLines = (value, label) => {
47
+ if (Array.isArray(value)) {
48
+ if (!value.length) {
49
+ return [];
50
+ }
51
+ return value.map((entry, idx) => {
52
+ if (typeof entry !== 'string') {
53
+ if (entry === null || entry === undefined) {
54
+ return '';
55
+ }
56
+ return String(entry);
57
+ }
58
+ // Preserve intentional whitespace
59
+ return entry.replace(/\r/g, '');
60
+ });
61
+ }
62
+ if (typeof value === 'string') {
63
+ return splitTextIntoLines(value);
64
+ }
65
+ if (value === null || value === undefined) {
66
+ throw new StructuredApplyPatchError('invalid_lines', `${label} must be an array of strings or a multi-line string`);
67
+ }
68
+ return [String(value)];
69
+ };
70
+ const buildContextLines = (raw) => splitTextIntoLines(raw).map((line) => ` ${line}`);
71
+ const buildPrefixedLines = (lines, prefix) => lines.map((line) => `${prefix}${line}`);
72
+ const detectIndentFromAnchor = (anchorLines, mode) => {
73
+ const source = mode === 'first' ? anchorLines[0] ?? '' : anchorLines[anchorLines.length - 1] ?? '';
74
+ const match = source.match(/^(\s*)/);
75
+ return match ? match[1] ?? '' : '';
76
+ };
77
+ const applyAnchorIndent = (lines, anchorLines, position, enabled) => {
78
+ if (!enabled) {
79
+ return lines;
80
+ }
81
+ const indent = detectIndentFromAnchor(anchorLines, position);
82
+ if (!indent) {
83
+ return lines;
84
+ }
85
+ return lines.map((line) => {
86
+ if (!line.trim()) {
87
+ return line;
88
+ }
89
+ if (/^\s/.test(line)) {
90
+ return line;
91
+ }
92
+ return `${indent}${line}`;
93
+ });
94
+ };
95
+ export function buildStructuredPatch(payload) {
96
+ if (!payload || typeof payload !== 'object') {
97
+ throw new StructuredApplyPatchError('missing_payload', 'apply_patch arguments must be a JSON object');
98
+ }
99
+ if (!Array.isArray(payload.changes) || payload.changes.length === 0) {
100
+ throw new StructuredApplyPatchError('missing_changes', 'apply_patch requires a non-empty "changes" array');
101
+ }
102
+ const topLevelFile = typeof payload.file === 'string' && payload.file.trim()
103
+ ? normalizeFilePath(payload.file, 'file')
104
+ : undefined;
105
+ const sectionOrder = [];
106
+ const fileSections = new Map();
107
+ const ensureUpdateSection = (file) => {
108
+ const existing = fileSections.get(file);
109
+ if (existing) {
110
+ if (existing.type !== 'update') {
111
+ throw new StructuredApplyPatchError('invalid_change_sequence', `File "${file}" already marked as ${existing.type}`);
112
+ }
113
+ return existing;
114
+ }
115
+ const created = { type: 'update', hunks: [] };
116
+ sectionOrder.push(file);
117
+ fileSections.set(file, created);
118
+ return created;
119
+ };
120
+ for (const [index, change] of payload.changes.entries()) {
121
+ if (!change || typeof change !== 'object') {
122
+ throw new StructuredApplyPatchError('invalid_change', `Change at index ${index} must be an object`);
123
+ }
124
+ const kindRaw = typeof change.kind === 'string' ? change.kind.trim().toLowerCase() : '';
125
+ if (!kindRaw) {
126
+ throw new StructuredApplyPatchError('invalid_change_kind', `Change at index ${index} is missing "kind"`);
127
+ }
128
+ if (!SUPPORTED_KINDS.includes(kindRaw)) {
129
+ throw new StructuredApplyPatchError('invalid_change_kind', `Unsupported change kind "${change.kind}" at index ${index}`);
130
+ }
131
+ const file = change.file ? normalizeFilePath(change.file, `changes[${index}].file`) : topLevelFile;
132
+ if (!file) {
133
+ throw new StructuredApplyPatchError('invalid_file', `Change at index ${index} is missing "file"`);
134
+ }
135
+ if (kindRaw === 'create_file') {
136
+ if (fileSections.has(file)) {
137
+ throw new StructuredApplyPatchError('invalid_change_sequence', `File "${file}" already has pending changes`);
138
+ }
139
+ const lines = normalizeLines(change.lines, `changes[${index}].lines`);
140
+ sectionOrder.push(file);
141
+ fileSections.set(file, { type: 'add', lines });
142
+ continue;
143
+ }
144
+ if (kindRaw === 'delete_file') {
145
+ if (fileSections.has(file)) {
146
+ throw new StructuredApplyPatchError('invalid_change_sequence', `File "${file}" already has pending changes`);
147
+ }
148
+ sectionOrder.push(file);
149
+ fileSections.set(file, { type: 'delete' });
150
+ continue;
151
+ }
152
+ const section = ensureUpdateSection(file);
153
+ switch (kindRaw) {
154
+ case 'insert_after': {
155
+ const anchor = toSafeString(change.anchor, `changes[${index}].anchor`);
156
+ const anchorLines = splitTextIntoLines(anchor);
157
+ const additions = normalizeLines(change.lines, `changes[${index}].lines`);
158
+ if (!additions.length) {
159
+ throw new StructuredApplyPatchError('invalid_lines', `changes[${index}].lines must include at least one line`);
160
+ }
161
+ const prepared = applyAnchorIndent(additions, anchorLines, 'last', change.use_anchor_indent);
162
+ const hunkBody = [...buildContextLines(anchor), ...buildPrefixedLines(prepared, '+')];
163
+ section.hunks.push(hunkBody);
164
+ break;
165
+ }
166
+ case 'insert_before': {
167
+ const anchor = toSafeString(change.anchor, `changes[${index}].anchor`);
168
+ const anchorLines = splitTextIntoLines(anchor);
169
+ const additions = normalizeLines(change.lines, `changes[${index}].lines`);
170
+ if (!additions.length) {
171
+ throw new StructuredApplyPatchError('invalid_lines', `changes[${index}].lines must include at least one line`);
172
+ }
173
+ const prepared = applyAnchorIndent(additions, anchorLines, 'first', change.use_anchor_indent);
174
+ const hunkBody = [...buildPrefixedLines(prepared, '+'), ...buildContextLines(anchor)];
175
+ section.hunks.push(hunkBody);
176
+ break;
177
+ }
178
+ case 'replace': {
179
+ const target = toSafeString(change.target, `changes[${index}].target`);
180
+ const replacements = normalizeLines(change.lines, `changes[${index}].lines`);
181
+ const hunkBody = [
182
+ ...buildPrefixedLines(splitTextIntoLines(target), '-'),
183
+ ...buildPrefixedLines(replacements, '+')
184
+ ];
185
+ section.hunks.push(hunkBody);
186
+ break;
187
+ }
188
+ case 'delete': {
189
+ const target = toSafeString(change.target, `changes[${index}].target`);
190
+ const hunkBody = buildPrefixedLines(splitTextIntoLines(target), '-');
191
+ section.hunks.push(hunkBody);
192
+ break;
193
+ }
194
+ default: {
195
+ throw new StructuredApplyPatchError('invalid_change_kind', `Unsupported change kind "${change.kind}" at index ${index}`);
196
+ }
197
+ }
198
+ }
199
+ if (!sectionOrder.length) {
200
+ throw new StructuredApplyPatchError('missing_changes', 'apply_patch payload produced no file operations');
201
+ }
202
+ const lines = ['*** Begin Patch'];
203
+ for (const file of sectionOrder) {
204
+ const section = fileSections.get(file);
205
+ if (!section)
206
+ continue;
207
+ if (section.type === 'add') {
208
+ lines.push(`*** Add File: ${file}`);
209
+ for (const line of section.lines) {
210
+ lines.push(`+${line}`);
211
+ }
212
+ }
213
+ else if (section.type === 'delete') {
214
+ lines.push(`*** Delete File: ${file}`);
215
+ }
216
+ else {
217
+ lines.push(`*** Update File: ${file}`);
218
+ for (const hunk of section.hunks) {
219
+ lines.push('@@');
220
+ for (const entry of hunk) {
221
+ lines.push(entry);
222
+ }
223
+ lines.push('@@');
224
+ }
225
+ }
226
+ }
227
+ lines.push('*** End Patch');
228
+ return lines.join('\n');
229
+ }
230
+ export function isStructuredApplyPatchPayload(candidate) {
231
+ if (!candidate || typeof candidate !== 'object') {
232
+ return false;
233
+ }
234
+ const record = candidate;
235
+ if (!Array.isArray(record.changes)) {
236
+ return false;
237
+ }
238
+ return true;
239
+ }
@@ -0,0 +1,5 @@
1
+ export declare function normalizeToolName(value: unknown): string;
2
+ export declare function isShellToolName(value: unknown): boolean;
3
+ export declare function hasApplyPatchToolDeclared(tools: unknown[] | undefined): boolean;
4
+ export declare function buildShellDescription(toolDisplayName: string, hasApplyPatch: boolean): string;
5
+ export declare function appendApplyPatchReminder(description: string, hasApplyPatch: boolean): string;
@@ -0,0 +1,50 @@
1
+ const SHELL_TOOL_ALIASES = new Set(['shell', 'shell_command', 'exec_command']);
2
+ const APPLY_PATCH_NAME = 'apply_patch';
3
+ export function normalizeToolName(value) {
4
+ return typeof value === 'string' ? value.trim().toLowerCase() : '';
5
+ }
6
+ function extractToolFunctionName(entry) {
7
+ if (!entry || typeof entry !== 'object') {
8
+ return '';
9
+ }
10
+ const fnName = typeof entry.function?.name === 'string'
11
+ ? entry.function.name
12
+ : undefined;
13
+ if (fnName && fnName.trim().length > 0) {
14
+ return fnName.trim();
15
+ }
16
+ const topName = typeof entry.name === 'string' ? entry.name : '';
17
+ return topName.trim();
18
+ }
19
+ export function isShellToolName(value) {
20
+ return SHELL_TOOL_ALIASES.has(normalizeToolName(value));
21
+ }
22
+ export function hasApplyPatchToolDeclared(tools) {
23
+ if (!Array.isArray(tools)) {
24
+ return false;
25
+ }
26
+ return tools.some((entry) => normalizeToolName(extractToolFunctionName(entry)) === APPLY_PATCH_NAME);
27
+ }
28
+ export function buildShellDescription(toolDisplayName, hasApplyPatch) {
29
+ const label = toolDisplayName && toolDisplayName.trim().length > 0
30
+ ? toolDisplayName.trim()
31
+ : 'shell';
32
+ const base = 'Runs a shell command and returns its output.';
33
+ const workdirLine = `- Always set the \`workdir\` param when using the ${label} function. Avoid using \`cd\` unless absolutely necessary.`;
34
+ const applyPatchLine = '- Prefer apply_patch for editing files instead of shell redirection or here-doc usage.';
35
+ return hasApplyPatch ? `${base}\n${workdirLine}\n${applyPatchLine}` : `${base}\n${workdirLine}`;
36
+ }
37
+ export function appendApplyPatchReminder(description, hasApplyPatch) {
38
+ if (!hasApplyPatch) {
39
+ return description;
40
+ }
41
+ const trimmed = description?.trim() ?? '';
42
+ if (!trimmed) {
43
+ return buildShellDescription('shell', true);
44
+ }
45
+ if (trimmed.includes('apply_patch')) {
46
+ return trimmed;
47
+ }
48
+ const applyPatchLine = '- Prefer apply_patch for editing files instead of shell redirection or here-doc usage.';
49
+ return `${trimmed}\n${applyPatchLine}`;
50
+ }
@@ -1,4 +1,5 @@
1
1
  // Tool registry and validator (single source of truth)
2
+ import { buildStructuredPatch, isStructuredApplyPatchPayload, StructuredApplyPatchError } from './apply-patch-structured.js';
2
3
  const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
3
4
  const asString = (value) => {
4
5
  if (typeof value === 'string' && value.trim().length > 0) {
@@ -43,164 +44,6 @@ const toJson = (value) => {
43
44
  return '{}';
44
45
  }
45
46
  };
46
- const APPLY_PATCH_SECTION_RE = /^\*\*\*\s+(Add|Update|Delete|Copy|Move)\s+File:\s*(.+)$/;
47
- const sanitizeRelativePath = (value) => {
48
- if (!value)
49
- return null;
50
- const trimmed = value.trim().replace(/\\/g, '/');
51
- if (!trimmed)
52
- return null;
53
- if (trimmed.startsWith('/'))
54
- return null;
55
- if (/^[a-zA-Z]:/.test(trimmed))
56
- return null;
57
- if (trimmed.includes('\0'))
58
- return null;
59
- if (trimmed.includes('..')) {
60
- const segments = trimmed.split('/');
61
- if (segments.some((seg) => seg === '..')) {
62
- return null;
63
- }
64
- }
65
- return trimmed.replace(/\/{2,}/g, '/');
66
- };
67
- const collectExplicitPaths = (record) => {
68
- const bucket = [];
69
- const invalid = [];
70
- const pushPath = (candidate) => {
71
- const str = asString(candidate);
72
- const normalized = sanitizeRelativePath(str);
73
- if (normalized)
74
- bucket.push(normalized);
75
- else if (typeof candidate === 'string')
76
- invalid.push(candidate);
77
- };
78
- const entries = [
79
- record.paths,
80
- record.path,
81
- record.files,
82
- record.file,
83
- record.targets,
84
- record.target_path
85
- ];
86
- for (const entry of entries) {
87
- if (!entry)
88
- continue;
89
- if (typeof entry === 'string') {
90
- pushPath(entry);
91
- }
92
- else if (Array.isArray(entry)) {
93
- entry.forEach((item) => {
94
- if (typeof item === 'string')
95
- pushPath(item);
96
- else if (isRecord(item))
97
- pushPath(item.path);
98
- });
99
- }
100
- else if (isRecord(entry)) {
101
- pushPath(entry.path ?? entry.value);
102
- }
103
- }
104
- return { paths: bucket, invalid };
105
- };
106
- const looksLikePatchBody = (text) => {
107
- const trimmed = text.trim();
108
- if (!trimmed)
109
- return false;
110
- const hunkRe = /^@@/m;
111
- const diffLineRe = /^[ +-]/m;
112
- return hunkRe.test(trimmed) || diffLineRe.test(trimmed);
113
- };
114
- const applyPathOverrides = (lines, overridePaths) => {
115
- if (!overridePaths.length)
116
- return;
117
- const sections = [];
118
- for (let i = 0; i < lines.length; i += 1) {
119
- const match = lines[i].match(APPLY_PATCH_SECTION_RE);
120
- if (match) {
121
- sections.push({ index: i, action: match[1] });
122
- }
123
- }
124
- if (!sections.length) {
125
- const beginIdx = lines.findIndex((line) => line.trim() === '*** Begin Patch');
126
- if (beginIdx >= 0) {
127
- lines.splice(beginIdx + 1, 0, `*** Update File: ${overridePaths[0]}`);
128
- }
129
- return;
130
- }
131
- const limit = Math.min(sections.length, overridePaths.length);
132
- for (let i = 0; i < limit; i += 1) {
133
- lines[sections[i].index] = `*** ${sections[i].action} File: ${overridePaths[i]}`;
134
- }
135
- };
136
- const normalizeApplyPatchInput = (rawPatch, options) => {
137
- if (typeof rawPatch !== 'string') {
138
- return null;
139
- }
140
- const text = rawPatch.replace(/\r\n/g, '\n');
141
- const beginIdx = text.toLowerCase().indexOf('*** begin patch');
142
- const endIdx = text.toLowerCase().lastIndexOf('*** end patch');
143
- let candidate = '';
144
- if (beginIdx >= 0 && endIdx >= 0 && endIdx > beginIdx) {
145
- candidate = text.slice(beginIdx, endIdx + '*** End Patch'.length);
146
- }
147
- else if (options?.overridePaths && options.overridePaths.length) {
148
- if (!looksLikePatchBody(text)) {
149
- return null;
150
- }
151
- const headerPath = options.overridePaths[0];
152
- candidate = ['*** Begin Patch', `*** Update File: ${headerPath}`, text.trim(), '*** End Patch'].join('\n');
153
- }
154
- else {
155
- return null;
156
- }
157
- const lines = candidate.split(/\r?\n/);
158
- if (!lines.length) {
159
- return null;
160
- }
161
- let firstIdx = 0;
162
- while (firstIdx < lines.length && lines[firstIdx].trim().length === 0) {
163
- firstIdx += 1;
164
- }
165
- if (firstIdx >= lines.length) {
166
- return null;
167
- }
168
- const firstLineRaw = lines[firstIdx];
169
- const firstLineTrimmed = firstLineRaw.trim();
170
- if (!firstLineTrimmed.toLowerCase().startsWith('*** begin patch')) {
171
- return null;
172
- }
173
- if (firstLineTrimmed !== '*** Begin Patch') {
174
- lines[firstIdx] = '*** Begin Patch';
175
- }
176
- let lastIdx = lines.length - 1;
177
- while (lastIdx > firstIdx && lines[lastIdx].trim().length === 0) {
178
- lastIdx -= 1;
179
- }
180
- if (lastIdx > firstIdx) {
181
- const lastLineTrimmed = lines[lastIdx].trim();
182
- if (lastLineTrimmed.toLowerCase().startsWith('*** end patch') && lastLineTrimmed !== '*** End Patch') {
183
- lines[lastIdx] = '*** End Patch';
184
- }
185
- }
186
- if (options?.overridePaths && options.overridePaths.length) {
187
- applyPathOverrides(lines, options.overridePaths);
188
- }
189
- const normalized = lines.join('\n');
190
- if (!normalized.includes('*** Begin Patch') || !normalized.includes('*** End Patch')) {
191
- return null;
192
- }
193
- return normalized;
194
- };
195
- const logApplyPatchValidatorError = (message) => {
196
- try {
197
- // eslint-disable-next-line no-console
198
- console.error(`\x1b[31m[apply_patch][validator] ${message}\x1b[0m`);
199
- }
200
- catch {
201
- /* ignore */
202
- }
203
- };
204
47
  const detectForbiddenWrite = (script) => {
205
48
  const normalized = script.toLowerCase();
206
49
  if (!normalized) {
@@ -254,44 +97,19 @@ export function validateToolCall(name, argsString) {
254
97
  switch (normalizedName) {
255
98
  case 'apply_patch': {
256
99
  const record = rawArgs;
257
- const inputField = asString(record.input);
258
- const patchField = asString(record.patch);
259
- const rawStr = typeof argsString === 'string' ? argsString : '';
260
- let patchRaw = null;
261
- if (patchField) {
262
- patchRaw = patchField;
263
- }
264
- else if (inputField) {
265
- patchRaw = inputField;
266
- }
267
- else if (rawStr && /\*\*\*\s+Begin Patch/i.test(rawStr)) {
268
- patchRaw = rawStr;
100
+ if (!isStructuredApplyPatchPayload(record)) {
101
+ return { ok: false, reason: 'missing_changes' };
269
102
  }
270
- if (!patchRaw) {
271
- logApplyPatchValidatorError('missing apply_patch patch/input payload');
272
- return { ok: false, reason: 'missing_input' };
103
+ try {
104
+ const patchText = buildStructuredPatch(record);
105
+ return { ok: true, normalizedArgs: toJson({ patch: patchText, input: patchText }) };
273
106
  }
274
- const { paths: explicitPaths, invalid } = collectExplicitPaths(record);
275
- if (invalid.length) {
276
- invalid.forEach((value) => logApplyPatchValidatorError(`invalid path argument: ${value}`));
277
- }
278
- const normalizedPatch = normalizeApplyPatchInput(patchRaw, { overridePaths: explicitPaths });
279
- if (!normalizedPatch) {
280
- logApplyPatchValidatorError('patch text missing *** Begin Patch/*** End Patch or failed to auto-wrap with provided paths');
281
- return { ok: false, reason: 'invalid_patch_header' };
282
- }
283
- // Canonical, parameterized shape; keep input for backwards compatibility.
284
- const payload = {
285
- patch: normalizedPatch,
286
- input: normalizedPatch
287
- };
288
- if (explicitPaths.length) {
289
- payload.paths = explicitPaths;
107
+ catch (error) {
108
+ if (error instanceof StructuredApplyPatchError) {
109
+ return { ok: false, reason: error.reason || 'invalid_patch_payload' };
110
+ }
111
+ return { ok: false, reason: 'invalid_patch_payload' };
290
112
  }
291
- return {
292
- ok: true,
293
- normalizedArgs: toJson(payload)
294
- };
295
113
  }
296
114
  case 'shell': {
297
115
  const rawCommand = rawArgs.command;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonstudio/llms",
3
- "version": "0.6.567",
3
+ "version": "0.6.568",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -62,4 +62,4 @@
62
62
  "dist",
63
63
  "README.md"
64
64
  ]
65
- }
65
+ }