@jsonstudio/llms 0.6.795 → 0.6.938

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 (184) hide show
  1. package/dist/bridge/routecodex-adapter.d.ts +74 -0
  2. package/dist/config-unified/enhanced-path-resolver.d.ts +5 -0
  3. package/dist/config-unified/unified-config.d.ts +26 -0
  4. package/dist/conversion/codec-registry.d.ts +10 -0
  5. package/dist/conversion/codecs/gemini-openai-codec.d.ts +16 -0
  6. package/dist/conversion/codecs/openai-openai-codec.d.ts +12 -0
  7. package/dist/conversion/codecs/responses-openai-codec.d.ts +12 -0
  8. package/dist/conversion/compat/profiles/chat-gemini.json +12 -0
  9. package/dist/conversion/config/config-manager.d.ts +212 -0
  10. package/dist/conversion/hub/config/types.d.ts +26 -0
  11. package/dist/conversion/hub/core/detour-registry.d.ts +9 -0
  12. package/dist/conversion/hub/core/hub-context.d.ts +21 -0
  13. package/dist/conversion/hub/core/index.d.ts +3 -0
  14. package/dist/conversion/hub/core/stage-driver.d.ts +30 -0
  15. package/dist/conversion/hub/format-adapters/anthropic-format-adapter.d.ts +16 -0
  16. package/dist/conversion/hub/format-adapters/chat-format-adapter.d.ts +17 -0
  17. package/dist/conversion/hub/format-adapters/gemini-format-adapter.d.ts +16 -0
  18. package/dist/conversion/hub/format-adapters/index.d.ts +21 -0
  19. package/dist/conversion/hub/hub-feature.d.ts +1 -0
  20. package/dist/conversion/hub/node-support.d.ts +19 -0
  21. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +11 -0
  22. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +3 -0
  23. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +7 -0
  24. package/dist/conversion/hub/pipeline/hub-pipeline.js +71 -14
  25. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +4 -0
  26. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +23 -1
  27. package/dist/conversion/hub/pipelines/inbound.d.ts +22 -0
  28. package/dist/conversion/hub/pipelines/outbound.d.ts +22 -0
  29. package/dist/conversion/hub/policy/policy-engine.d.ts +46 -0
  30. package/dist/conversion/hub/policy/policy-engine.js +176 -0
  31. package/dist/conversion/hub/policy/protocol-spec.d.ts +50 -0
  32. package/dist/conversion/hub/policy/protocol-spec.js +105 -0
  33. package/dist/conversion/hub/process/chat-process.d.ts +32 -0
  34. package/dist/conversion/hub/registry.d.ts +28 -0
  35. package/dist/conversion/hub/response/chat-response-utils.d.ts +6 -0
  36. package/dist/conversion/hub/response/provider-response.js +31 -0
  37. package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +7 -0
  38. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +87 -1
  39. package/dist/conversion/hub/semantic-mappers/index.d.ts +4 -0
  40. package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +21 -0
  41. package/dist/conversion/hub/standardized-bridge.d.ts +12 -0
  42. package/dist/conversion/hub/types/chat-schema.d.ts +112 -0
  43. package/dist/conversion/hub/types/errors.d.ts +5 -0
  44. package/dist/conversion/hub/types/format-envelope.d.ts +7 -0
  45. package/dist/conversion/hub/types/index.d.ts +6 -0
  46. package/dist/conversion/hub/types/json.d.ts +9 -0
  47. package/dist/conversion/hub/types/node.d.ts +31 -0
  48. package/dist/conversion/responses/responses-openai-bridge.js +263 -10
  49. package/dist/conversion/schema-validator.d.ts +7 -0
  50. package/dist/conversion/shared/args-mapping.d.ts +18 -0
  51. package/dist/conversion/shared/chat-request-filters.d.ts +9 -0
  52. package/dist/conversion/shared/errors.d.ts +1 -1
  53. package/dist/conversion/shared/gemini-tool-utils.js +61 -0
  54. package/dist/conversion/shared/jsonish.d.ts +3 -0
  55. package/dist/conversion/shared/mcp-injection.d.ts +2 -0
  56. package/dist/conversion/shared/media.d.ts +1 -0
  57. package/dist/conversion/shared/openai-message-normalize.d.ts +1 -0
  58. package/dist/conversion/shared/payload-budget.d.ts +13 -0
  59. package/dist/conversion/shared/reasoning-mapping.d.ts +5 -0
  60. package/dist/conversion/shared/responses-request-adapter.d.ts +1 -28
  61. package/dist/conversion/shared/responses-request-adapter.js +1 -430
  62. package/dist/conversion/shared/snapshot-hooks.js +112 -4
  63. package/dist/conversion/shared/tool-governor.js +8 -2
  64. package/dist/conversion/shared/tool-harvester.d.ts +31 -0
  65. package/dist/conversion/shared/tool-mapping.js +10 -29
  66. package/dist/conversion/types.d.ts +33 -0
  67. package/dist/filters/builtin/add-fields-filter.d.ts +8 -0
  68. package/dist/filters/builtin/blacklist-filter.d.ts +8 -0
  69. package/dist/filters/builtin/whitelist-filter.d.ts +8 -0
  70. package/dist/filters/engine.d.ts +16 -0
  71. package/dist/filters/special/request-tool-choice-policy.d.ts +11 -0
  72. package/dist/filters/special/response-finish-invariants.d.ts +11 -0
  73. package/dist/filters/special/response-openai-to-responses-bridge.d.ts +13 -0
  74. package/dist/filters/special/response-tool-arguments-blacklist.d.ts +12 -0
  75. package/dist/filters/special/response-tool-arguments-schema-converge.d.ts +13 -0
  76. package/dist/filters/special/response-tool-arguments-stringify.d.ts +9 -0
  77. package/dist/filters/special/response-tool-arguments-whitelist.d.ts +11 -0
  78. package/dist/filters/special/tool-filter-hooks.d.ts +19 -0
  79. package/dist/filters/special/tool-post-constraints.d.ts +31 -0
  80. package/dist/filters/types.d.ts +68 -0
  81. package/dist/filters/utils/fieldmap-loader.d.ts +2 -0
  82. package/dist/filters/utils/snapshot-writer.d.ts +10 -0
  83. package/dist/guidance/index.d.ts +3 -0
  84. package/dist/guidance/index.js +78 -83
  85. package/dist/http/sse-response.d.ts +22 -0
  86. package/dist/router/virtual-router/bootstrap.d.ts +6 -0
  87. package/dist/router/virtual-router/bootstrap.js +49 -5
  88. package/dist/router/virtual-router/classifier.d.ts +10 -0
  89. package/dist/router/virtual-router/engine-selection.js +147 -15
  90. package/dist/router/virtual-router/engine.js +177 -31
  91. package/dist/router/virtual-router/error-center.d.ts +10 -0
  92. package/dist/router/virtual-router/features.d.ts +3 -0
  93. package/dist/router/virtual-router/routing-instructions.d.ts +23 -1
  94. package/dist/router/virtual-router/routing-instructions.js +120 -30
  95. package/dist/router/virtual-router/types.d.ts +11 -0
  96. package/dist/servertool/engine.js +189 -16
  97. package/dist/servertool/handlers/apply-patch-guard.js +269 -0
  98. package/dist/servertool/handlers/exec-command-guard.js +558 -0
  99. package/dist/servertool/handlers/followup-message-trimmer.d.ts +16 -0
  100. package/dist/servertool/handlers/followup-message-trimmer.js +198 -0
  101. package/dist/servertool/handlers/followup-request-builder.d.ts +17 -0
  102. package/dist/servertool/handlers/followup-request-builder.js +122 -0
  103. package/dist/servertool/handlers/gemini-empty-reply-continue.js +252 -51
  104. package/dist/servertool/handlers/iflow-model-error-retry.js +12 -22
  105. package/dist/servertool/handlers/stop-message-auto.js +237 -75
  106. package/dist/servertool/handlers/vision.js +15 -27
  107. package/dist/servertool/handlers/web-search.js +17 -43
  108. package/dist/servertool/server-side-tools.d.ts +3 -0
  109. package/dist/servertool/server-side-tools.js +3 -0
  110. package/dist/sse/json-to-sse/anthropic-json-to-sse-converter.d.ts +2 -1
  111. package/dist/sse/json-to-sse/chat-json-to-sse-converter.d.ts +80 -0
  112. package/dist/sse/json-to-sse/event-generators/chat.d.ts +55 -0
  113. package/dist/sse/json-to-sse/event-generators/responses.d.ts +99 -0
  114. package/dist/sse/json-to-sse/gemini-json-to-sse-converter.d.ts +2 -1
  115. package/dist/sse/json-to-sse/responses-json-to-sse-converter.d.ts +80 -0
  116. package/dist/sse/json-to-sse/sequencers/anthropic-sequencer.d.ts +1 -1
  117. package/dist/sse/json-to-sse/sequencers/chat-sequencer.d.ts +2 -2
  118. package/dist/sse/json-to-sse/sequencers/gemini-sequencer.d.ts +1 -1
  119. package/dist/sse/json-to-sse/sequencers/responses-sequencer.d.ts +40 -0
  120. package/dist/sse/shared/chat-serializer.d.ts +4 -0
  121. package/dist/sse/shared/constants.d.ts +272 -0
  122. package/dist/sse/shared/serializers/anthropic-event-serializer.d.ts +1 -1
  123. package/dist/sse/shared/serializers/base-serializer.d.ts +158 -0
  124. package/dist/sse/shared/serializers/chat-event-serializer.d.ts +82 -0
  125. package/dist/sse/shared/serializers/gemini-event-serializer.d.ts +1 -1
  126. package/dist/sse/shared/serializers/index.d.ts +2 -1
  127. package/dist/sse/shared/serializers/responses-event-serializer.d.ts +123 -0
  128. package/dist/sse/shared/serializers/types.d.ts +51 -0
  129. package/dist/sse/shared/utils.d.ts +254 -0
  130. package/dist/sse/shared/writer.d.ts +2 -2
  131. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -1
  132. package/dist/sse/sse-to-json/builders/anthropic-response-builder.d.ts +1 -1
  133. package/dist/sse/sse-to-json/builders/response-builder.d.ts +1 -1
  134. package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +2 -1
  135. package/dist/sse/sse-to-json/gemini-sse-to-json-converter.d.ts +2 -1
  136. package/dist/sse/sse-to-json/parsers/sse-parser.d.ts +73 -0
  137. package/dist/sse/sse-to-json/responses-sse-to-json-converter.d.ts +1 -1
  138. package/dist/sse/types/chat-types.d.ts +1 -1
  139. package/dist/sse/types/responses-types.d.ts +1 -1
  140. package/dist/tools/apply-patch/execution-capturer.d.ts +13 -0
  141. package/dist/tools/apply-patch/execution-capturer.js +158 -0
  142. package/dist/tools/apply-patch/regression-capturer.d.ts +1 -0
  143. package/dist/tools/apply-patch/regression-capturer.js +5 -4
  144. package/dist/tools/apply-patch/structured.js +109 -13
  145. package/dist/tools/apply-patch/validator.js +261 -17
  146. package/dist/tools/tool-registry.d.ts +8 -0
  147. package/dist/tools/tool-registry.js +2 -1
  148. package/package.json +4 -4
  149. package/dist/conversion/compat/actions/apply-patch-format-fixer.js +0 -233
  150. package/dist/conversion/config/compat-profiles.json +0 -38
  151. package/dist/conversion/hub/response/server-side-tools.d.ts +0 -26
  152. package/dist/conversion/hub/response/server-side-tools.js +0 -383
  153. package/dist/conversion/shared/bridge-conversation-store.d.ts +0 -41
  154. package/dist/conversion/shared/bridge-conversation-store.js +0 -279
  155. package/dist/conversion/shared/bridge-request-adapter.d.ts +0 -28
  156. package/dist/conversion/shared/bridge-request-adapter.js +0 -430
  157. package/dist/conversion/shared/responses-id-utils.js +0 -42
  158. package/dist/conversion/shared/responses-instructions.js +0 -113
  159. package/dist/conversion/shared/responses-message-utils.d.ts +0 -15
  160. package/dist/conversion/shared/responses-message-utils.js +0 -206
  161. package/dist/conversion/shared/responses-metadata.js +0 -1
  162. package/dist/conversion/shared/responses-output-utils.d.ts +0 -7
  163. package/dist/conversion/shared/responses-output-utils.js +0 -108
  164. package/dist/conversion/shared/responses-types.d.ts +0 -33
  165. package/dist/conversion/shared/tool-normalizers.d.ts +0 -4
  166. package/dist/conversion/shared/tool-normalizers.js +0 -84
  167. package/dist/filters/special/request-streaming-to-nonstreaming.d.ts +0 -13
  168. package/dist/filters/special/request-streaming-to-nonstreaming.js +0 -39
  169. package/dist/filters/special/response-apply-patch-toon-decode.d.ts +0 -23
  170. package/dist/filters/special/response-apply-patch-toon-decode.js +0 -460
  171. package/dist/filters/special/response-tool-arguments-toon-decode.d.ts +0 -10
  172. package/dist/filters/special/response-tool-arguments-toon-decode.js +0 -154
  173. package/dist/servertool/flow-types.d.ts +0 -40
  174. package/dist/servertool/flow-types.js +0 -1
  175. package/dist/servertool/orchestration-types.d.ts +0 -33
  176. package/dist/servertool/orchestration-types.js +0 -1
  177. package/dist/servertool/vision-tool.d.ts +0 -2
  178. package/dist/servertool/vision-tool.js +0 -185
  179. package/dist/tools/patch-args-normalizer.d.ts +0 -15
  180. package/dist/tools/patch-args-normalizer.js +0 -472
  181. package/dist/utils/toon.d.ts +0 -4
  182. package/dist/utils/toon.js +0 -75
  183. /package/dist/{conversion/compat/actions/apply-patch-format-fixer.d.ts → servertool/handlers/apply-patch-guard.d.ts} +0 -0
  184. /package/dist/{conversion/shared/responses-types.js → servertool/handlers/exec-command-guard.d.ts} +0 -0
@@ -86,7 +86,8 @@ const convertGitDiffToApplyPatch = (text) => {
86
86
  current = null;
87
87
  return;
88
88
  }
89
- if (current.path && current.lines.length) {
89
+ // Include rename-only diffs (no hunks) when we have a move target.
90
+ if (current.path && (current.lines.length || current.moveTo)) {
90
91
  files.push(current);
91
92
  }
92
93
  current = null;
@@ -95,19 +96,30 @@ const convertGitDiffToApplyPatch = (text) => {
95
96
  const v = String(value || '').trim();
96
97
  if (!v)
97
98
  return '';
98
- const m = v.match(/^(?:a\/|b\/)?(.+)$/);
99
- return (m && m[1] ? m[1] : v).trim();
99
+ const head = v.split('\t')[0] ?? v;
100
+ const m = String(head).trim().match(/^(?:a\/|b\/)?(.+)$/);
101
+ return (m && m[1] ? m[1] : String(head)).trim();
100
102
  };
101
103
  for (const line of lines) {
102
104
  const diffMatch = line.match(/^diff --git\s+a\/(.+?)\s+b\/(.+)$/);
103
105
  if (diffMatch) {
104
106
  sawDiff = true;
105
107
  flush();
106
- current = { path: extractPath(diffMatch[2]), kind: 'update', lines: [] };
108
+ current = {
109
+ path: extractPath(diffMatch[2]),
110
+ kind: 'update',
111
+ lines: [],
112
+ oldPath: extractPath(diffMatch[1]),
113
+ newPath: extractPath(diffMatch[2]),
114
+ };
107
115
  continue;
108
116
  }
109
117
  if (!current)
110
118
  continue;
119
+ if (line.startsWith('GIT binary patch') || line.startsWith('Binary files ')) {
120
+ current.binary = true;
121
+ continue;
122
+ }
111
123
  const delMatch = line.match(/^deleted file mode\s+/);
112
124
  const newMatch = line.match(/^new file mode\s+/);
113
125
  if (delMatch) {
@@ -118,7 +130,39 @@ const convertGitDiffToApplyPatch = (text) => {
118
130
  current.kind = 'add';
119
131
  continue;
120
132
  }
121
- if (line.startsWith('--- ') || line.startsWith('+++ ') || line.startsWith('index ')) {
133
+ if (line.startsWith('rename from ')) {
134
+ const p = extractPath(line.slice('rename from '.length));
135
+ if (p)
136
+ current.oldPath = p;
137
+ continue;
138
+ }
139
+ if (line.startsWith('rename to ')) {
140
+ const p = extractPath(line.slice('rename to '.length));
141
+ if (p) {
142
+ current.newPath = p;
143
+ current.moveTo = p;
144
+ }
145
+ continue;
146
+ }
147
+ if (line.startsWith('--- ')) {
148
+ const p = extractPath(line.slice(4));
149
+ if (p) {
150
+ current.oldPath = p;
151
+ if (p === '/dev/null')
152
+ current.kind = 'add';
153
+ }
154
+ continue;
155
+ }
156
+ if (line.startsWith('+++ ')) {
157
+ const p = extractPath(line.slice(4));
158
+ if (p) {
159
+ current.newPath = p;
160
+ if (p === '/dev/null')
161
+ current.kind = 'delete';
162
+ }
163
+ continue;
164
+ }
165
+ if (line.startsWith('index ') || line.startsWith('similarity index ') || line.startsWith('dissimilarity index ')) {
122
166
  continue;
123
167
  }
124
168
  if (line.startsWith('@@')) {
@@ -133,21 +177,35 @@ const convertGitDiffToApplyPatch = (text) => {
133
177
  flush();
134
178
  if (!sawDiff || files.length === 0)
135
179
  return null;
180
+ if (files.some((f) => f.binary))
181
+ return null;
136
182
  const out = ['*** Begin Patch'];
137
183
  for (const file of files) {
184
+ const oldPath = typeof file.oldPath === 'string' ? file.oldPath : '';
185
+ const newPath = typeof file.newPath === 'string' ? file.newPath : '';
186
+ const resolvedPath = file.kind === 'add'
187
+ ? (newPath && newPath !== '/dev/null' ? newPath : file.path)
188
+ : file.kind === 'delete'
189
+ ? (oldPath && oldPath !== '/dev/null' ? oldPath : file.path)
190
+ : (file.moveTo && oldPath && oldPath !== '/dev/null'
191
+ ? oldPath
192
+ : (newPath && newPath !== '/dev/null' ? newPath : file.path));
138
193
  if (file.kind === 'delete') {
139
- out.push(`*** Delete File: ${file.path}`);
194
+ out.push(`*** Delete File: ${resolvedPath}`);
140
195
  continue;
141
196
  }
142
197
  if (file.kind === 'add') {
143
- out.push(`*** Add File: ${file.path}`);
198
+ out.push(`*** Add File: ${resolvedPath}`);
144
199
  for (const l of file.lines) {
145
200
  if (l.startsWith('+'))
146
201
  out.push(l);
147
202
  }
148
203
  continue;
149
204
  }
150
- out.push(`*** Update File: ${file.path}`);
205
+ out.push(`*** Update File: ${resolvedPath}`);
206
+ if (file.moveTo && file.moveTo !== resolvedPath) {
207
+ out.push(`*** Move to: ${file.moveTo}`);
208
+ }
151
209
  out.push(...file.lines);
152
210
  }
153
211
  out.push('*** End Patch');
@@ -172,9 +230,18 @@ export const normalizeApplyPatchText = (raw) => {
172
230
  const minusMatch = text.match(/^---\s+(.*)$/m);
173
231
  const plusMatch = text.match(/^\+\+\+\s+(.*)$/m);
174
232
  if (minusMatch && plusMatch) {
175
- const rawPlus = plusMatch[1] || '';
176
- const pathMatch = rawPlus.match(/^(?:b\/)?(.+)$/);
177
- const filePath = (pathMatch && pathMatch[1] ? pathMatch[1] : rawPlus).trim();
233
+ const rawMinus = (minusMatch[1] || '').split('\t')[0] || '';
234
+ const rawPlus = (plusMatch[1] || '').split('\t')[0] || '';
235
+ const normalizeHeaderPath = (value) => {
236
+ const v = String(value || '').trim();
237
+ if (!v)
238
+ return '';
239
+ const m = v.match(/^(?:a\/|b\/)?(.+)$/);
240
+ return (m && m[1] ? m[1] : v).trim();
241
+ };
242
+ const minusPath = normalizeHeaderPath(rawMinus);
243
+ const plusPath = normalizeHeaderPath(rawPlus);
244
+ const filePath = plusPath === '/dev/null' ? minusPath : plusPath;
178
245
  if (filePath) {
179
246
  const synthetic = `diff --git a/${filePath} b/${filePath}\n${text}`;
180
247
  const converted = convertGitDiffToApplyPatch(synthetic);
@@ -304,6 +371,129 @@ const tryParseJson = (value) => {
304
371
  return undefined;
305
372
  }
306
373
  };
374
+ const escapeUnescapedQuotesInJsonStrings = (input) => {
375
+ // Best-effort: when JSON is almost valid but contains unescaped `"` inside string values
376
+ // (e.g. JSX snippets like className="..."), escape quotes that are not followed by a
377
+ // valid JSON token delimiter. Deterministic; does not attempt to fix structural issues.
378
+ let out = '';
379
+ let inString = false;
380
+ let escaped = false;
381
+ for (let i = 0; i < input.length; i += 1) {
382
+ const ch = input[i] ?? '';
383
+ if (!inString) {
384
+ if (ch === '"') {
385
+ inString = true;
386
+ escaped = false;
387
+ }
388
+ out += ch;
389
+ continue;
390
+ }
391
+ if (escaped) {
392
+ out += ch;
393
+ escaped = false;
394
+ continue;
395
+ }
396
+ if (ch === '\\') {
397
+ out += ch;
398
+ escaped = true;
399
+ continue;
400
+ }
401
+ if (ch === '"') {
402
+ let j = i + 1;
403
+ while (j < input.length && /\s/.test(input[j] ?? ''))
404
+ j += 1;
405
+ const next = j < input.length ? input[j] : '';
406
+ if (next === '' || next === ':' || next === ',' || next === '}' || next === ']') {
407
+ inString = false;
408
+ out += ch;
409
+ }
410
+ else {
411
+ out += '\\"';
412
+ }
413
+ continue;
414
+ }
415
+ out += ch;
416
+ }
417
+ return out;
418
+ };
419
+ const balanceJsonContainers = (input) => {
420
+ // Best-effort bracket/brace balancing for JSON-like strings.
421
+ // Only operates outside string literals. When encountering a closing token that doesn't
422
+ // match the current stack top, inserts the missing closer(s) to recover.
423
+ let out = '';
424
+ let inString = false;
425
+ let escaped = false;
426
+ const stack = [];
427
+ const closeFor = (open) => (open === '{' ? '}' : ']');
428
+ for (let i = 0; i < input.length; i += 1) {
429
+ const ch = input[i] ?? '';
430
+ if (inString) {
431
+ out += ch;
432
+ if (escaped) {
433
+ escaped = false;
434
+ continue;
435
+ }
436
+ if (ch === '\\') {
437
+ escaped = true;
438
+ continue;
439
+ }
440
+ if (ch === '"') {
441
+ inString = false;
442
+ }
443
+ continue;
444
+ }
445
+ if (ch === '"') {
446
+ inString = true;
447
+ out += ch;
448
+ continue;
449
+ }
450
+ if (ch === '{' || ch === '[') {
451
+ stack.push(ch);
452
+ out += ch;
453
+ continue;
454
+ }
455
+ if (ch === '}' || ch === ']') {
456
+ const expectedOpen = ch === '}' ? '{' : '[';
457
+ while (stack.length && stack[stack.length - 1] !== expectedOpen) {
458
+ const open = stack.pop();
459
+ out += closeFor(open);
460
+ }
461
+ if (stack.length && stack[stack.length - 1] === expectedOpen) {
462
+ stack.pop();
463
+ }
464
+ out += ch;
465
+ continue;
466
+ }
467
+ out += ch;
468
+ }
469
+ while (stack.length) {
470
+ const open = stack.pop();
471
+ out += closeFor(open);
472
+ }
473
+ return out;
474
+ };
475
+ const tryParseJsonLoose = (value) => {
476
+ const parsed = tryParseJson(value);
477
+ if (parsed !== undefined)
478
+ return parsed;
479
+ if (typeof value !== 'string')
480
+ return undefined;
481
+ const trimmed = value.trim();
482
+ if (!trimmed)
483
+ return undefined;
484
+ if (!(trimmed.startsWith('{') || trimmed.startsWith('[')))
485
+ return undefined;
486
+ let repaired = escapeUnescapedQuotesInJsonStrings(trimmed);
487
+ repaired = balanceJsonContainers(repaired);
488
+ if (!repaired || repaired === trimmed)
489
+ return undefined;
490
+ try {
491
+ return JSON.parse(repaired);
492
+ }
493
+ catch {
494
+ return undefined;
495
+ }
496
+ };
307
497
  const coerceChangesArray = (value) => {
308
498
  const parsed = tryParseJson(value);
309
499
  if (!parsed)
@@ -378,29 +568,77 @@ export function validateApplyPatchArgs(argsString, rawArgs) {
378
568
  // Raw patch text without JSON wrapper
379
569
  if (!looksJsonContainer && looksLikePatch(rawTrimmed)) {
380
570
  const patchText = normalizeApplyPatchText(rawTrimmed);
571
+ if (!patchText.includes('*** Begin Patch')) {
572
+ return { ok: false, reason: 'unsupported_patch_format' };
573
+ }
381
574
  return { ok: true, normalizedArgs: toJson({ patch: patchText, input: patchText }) };
382
575
  }
383
576
  const extractFromRecord = (rec) => {
384
577
  // Special case: argsString claims to be JSON but isn't parseable and raw text looks like a patch.
385
578
  if (looksJsonContainer && Object.keys(rec).length === 0 && looksLikePatch(rawTrimmed)) {
386
- return { patchText: normalizeApplyPatchText(rawTrimmed) };
579
+ const patchText = normalizeApplyPatchText(rawTrimmed);
580
+ if (!patchText.includes('*** Begin Patch'))
581
+ return { failureReason: 'unsupported_patch_format' };
582
+ return { patchText };
387
583
  }
388
584
  const patchField = asString(rec.patch);
389
585
  if (patchField && looksLikePatch(patchField)) {
390
- return { patchText: normalizeApplyPatchText(patchField) };
586
+ const patchText = normalizeApplyPatchText(patchField);
587
+ if (!patchText.includes('*** Begin Patch'))
588
+ return { failureReason: 'unsupported_patch_format' };
589
+ return { patchText };
391
590
  }
392
591
  const diffField = asString(rec.diff) ?? asString(rec.patchText) ?? asString(rec.body);
393
592
  if (diffField && looksLikePatch(diffField)) {
394
- return { patchText: normalizeApplyPatchText(diffField) };
593
+ const patchText = normalizeApplyPatchText(diffField);
594
+ if (!patchText.includes('*** Begin Patch'))
595
+ return { failureReason: 'unsupported_patch_format' };
596
+ return { patchText };
395
597
  }
396
598
  const inputField = asString(rec.input);
397
599
  if (inputField && looksLikePatch(inputField)) {
398
- return { patchText: normalizeApplyPatchText(inputField) };
600
+ const patchText = normalizeApplyPatchText(inputField);
601
+ if (!patchText.includes('*** Begin Patch'))
602
+ return { failureReason: 'unsupported_patch_format' };
603
+ return { patchText };
399
604
  }
400
605
  // Common shape: patch text stored under `instructions` (e.g. "*** Update File: ...").
401
606
  const instructionsField = asString(rec.instructions);
402
607
  if (instructionsField && looksLikePatch(instructionsField)) {
403
- return { patchText: normalizeApplyPatchText(instructionsField) };
608
+ const patchText = normalizeApplyPatchText(instructionsField);
609
+ if (!patchText.includes('*** Begin Patch'))
610
+ return { failureReason: 'unsupported_patch_format' };
611
+ return { patchText };
612
+ }
613
+ // Common wrapper shape (seen in codex samples): { _raw: "{...json...}" }.
614
+ // `_raw` may contain either patch text or a JSON-encoded structured payload.
615
+ const rawEnvelope = asString(rec._raw);
616
+ if (rawEnvelope) {
617
+ const trimmed = rawEnvelope.trim();
618
+ if (looksLikePatch(trimmed)) {
619
+ const patchText = normalizeApplyPatchText(trimmed);
620
+ if (!patchText.includes('*** Begin Patch'))
621
+ return { failureReason: 'unsupported_patch_format' };
622
+ return { patchText };
623
+ }
624
+ const parsed = tryParseJsonLoose(trimmed);
625
+ if (parsed && isRecord(parsed)) {
626
+ return extractFromRecord(parsed);
627
+ }
628
+ if (Array.isArray(parsed) && parsed.length > 0) {
629
+ const changesArray = parsed.filter((entry) => isRecord(entry));
630
+ if (changesArray.length && changesArray.some((c) => typeof c.kind === 'string')) {
631
+ const payload = { changes: changesArray };
632
+ try {
633
+ return { patchText: buildStructuredPatch(payload) };
634
+ }
635
+ catch (error) {
636
+ if (!(error instanceof StructuredApplyPatchError))
637
+ throw error;
638
+ return { failureReason: error.reason || 'structured_apply_patch_error' };
639
+ }
640
+ }
641
+ }
404
642
  }
405
643
  const payload = coerceStructuredPayload(rec);
406
644
  if (payload) {
@@ -448,7 +686,13 @@ export function validateApplyPatchArgs(argsString, rawArgs) {
448
686
  }
449
687
  }
450
688
  else if (typeof rawArgs === 'string' && looksLikePatch(rawArgs)) {
451
- patchText = normalizeApplyPatchText(rawArgs);
689
+ const normalized = normalizeApplyPatchText(rawArgs);
690
+ if (normalized.includes('*** Begin Patch')) {
691
+ patchText = normalized;
692
+ }
693
+ else {
694
+ failureReason = 'unsupported_patch_format';
695
+ }
452
696
  }
453
697
  if (!patchText) {
454
698
  if (failureReason)
@@ -0,0 +1,8 @@
1
+ export interface ToolValidationResult {
2
+ ok: boolean;
3
+ reason?: string;
4
+ warnings?: Array<Record<string, unknown>>;
5
+ normalizedArgs?: string;
6
+ }
7
+ export declare function getAllowedToolNames(): string[];
8
+ export declare function validateToolCall(name: string, argsString: string): ToolValidationResult;
@@ -116,7 +116,8 @@ export function validateToolCall(name, argsString) {
116
116
  originalArgs: typeof argsString === 'string' ? argsString : String(argsString ?? ''),
117
117
  normalizedArgs: typeof argsString === 'string' ? argsString : String(argsString ?? ''),
118
118
  validationError: reason,
119
- source: 'tool-registry.validateToolCall'
119
+ source: 'tool-registry.validateToolCall',
120
+ meta: { applyPatchToolMode: 'freeform' }
120
121
  });
121
122
  return { ok: false, reason };
122
123
  }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@jsonstudio/llms",
3
- "version": "0.6.795",
3
+ "version": "0.6.938",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "scripts": {
9
- "build": "node scripts/bump-version.mjs && tsc -p tsconfig.json && node scripts/tools/copy-compat-profiles.mjs",
10
- "build:dev": "node scripts/bump-version.mjs && tsc -p tsconfig.json && node scripts/tools/copy-compat-profiles.mjs",
9
+ "build": "node scripts/clean-dist.mjs && node scripts/bump-version.mjs && tsc -p tsconfig.json && node scripts/tools/copy-compat-profiles.mjs",
10
+ "build:dev": "node scripts/clean-dist.mjs && node scripts/bump-version.mjs && tsc -p tsconfig.json && node scripts/tools/copy-compat-profiles.mjs",
11
11
  "lint": "eslint --no-eslintrc -c .eslintrc.json src --ext .ts --no-cache",
12
12
  "lint:fix": "eslint --no-eslintrc -c .eslintrc.json src --ext .ts --no-cache --fix",
13
13
  "postbuild": "node scripts/tests/run-matrix-ci.mjs",
@@ -25,7 +25,7 @@
25
25
  "test:looprt:gemini": "npm run build && node scripts/tests/loop-rt-gemini.mjs",
26
26
  "replay:responses:chat-sse": "node scripts/exp3-responses-sse-to-chat-sse.mjs",
27
27
  "replay:responses:loop": "node scripts/exp4-responses-sse-loop.mjs",
28
- "test:virtual-router": "npm run build:dev && node scripts/tests/virtual-router-dry-run.mjs && node scripts/tests/virtual-router-context.mjs",
28
+ "test:virtual-router": "npm run build:dev && node scripts/tests/virtual-router-prefer-autoclear.mjs && node scripts/tests/virtual-router-dry-run.mjs && node scripts/tests/virtual-router-context.mjs",
29
29
  "test:virtual-router-health": "npm run build:dev && node scripts/tests/virtual-router-health.mjs"
30
30
  },
31
31
  "dependencies": {
@@ -1,233 +0,0 @@
1
- import { registerBridgeAction } from '../../shared/bridge-actions.js';
2
- import { ensureMessagesArray } from '../../shared/bridge-message-utils.js';
3
- /**
4
- * apply-patch-format-fixer.ts
5
- *
6
- * 专门处理 apply_patch 的格式错误修复,包括:
7
- * 1. 修复缺失 '*** Begin Patch' 包装的 raw diff
8
- * 2. 修复混用 git diff 头部的情况
9
- * 3. 修复 hunk 中缺少前缀的代码行
10
- *
11
- * 注意:仅修复格式问题,不修复上下文匹配问题
12
- */
13
- import { captureApplyPatchRegression } from '../../../tools/patch-regression-capturer.js';
14
- /**
15
- * 检测是否是裸 diff 格式(没有 Begin Patch 包装)
16
- */
17
- function isBareDiff(text) {
18
- const trimmed = text.trim();
19
- // 检测典型的 git diff 标记
20
- const hasGitDiffMarkers = trimmed.startsWith('diff --git') ||
21
- /^---\s+a\//.test(trimmed) ||
22
- /^\+\+\+\s+b\//.test(trimmed) ||
23
- /^@@\s+-\d+,\d+\s+\+\d+,\d+\s+@@/.test(trimmed);
24
- // 确保不是标准的 apply_patch 格式
25
- const hasApplyPatchMarkers = trimmed.includes('*** Begin Patch') ||
26
- trimmed.includes('*** Update File:') ||
27
- trimmed.includes('*** Add File:');
28
- return hasGitDiffMarkers && !hasApplyPatchMarkers;
29
- }
30
- /**
31
- * 转换裸 diff 为 apply_patch 格式
32
- */
33
- function convertBareDiffToApplyPatch(diffText) {
34
- const lines = diffText.replace(/\r\n/g, '\n').split('\n');
35
- const patches = new Map();
36
- let currentFile = '';
37
- let currentHunk = [];
38
- let inHunk = false;
39
- for (const line of lines) {
40
- // 检测文件头
41
- const diffMatch = line.match(/^diff --git\s+a\/(.+?)\s+b\/(.+)$/);
42
- if (diffMatch) {
43
- // 保存上一个文件的 hunk
44
- if (currentFile && currentHunk.length > 0) {
45
- patches.set(currentFile, {
46
- header: `*** Update File: ${diffMatch[2]}`,
47
- hunk: [...currentHunk]
48
- });
49
- }
50
- currentFile = diffMatch[2];
51
- currentHunk = [];
52
- inHunk = false;
53
- continue;
54
- }
55
- // 检测 hunk 头
56
- if (/^@@\s+-\d+/.test(line)) {
57
- currentHunk.push(line);
58
- inHunk = true;
59
- continue;
60
- }
61
- // 在 hunk 中收集行
62
- if (inHunk) {
63
- // 确保每行都有正确的前缀
64
- if (line.startsWith('+') || line.startsWith('-') || line.startsWith(' ')) {
65
- currentHunk.push(line);
66
- }
67
- else if (line.trim() === '') {
68
- currentHunk.push(' ');
69
- }
70
- else {
71
- // 行缺少前缀,作为上下文行处理
72
- currentHunk.push(' ' + line);
73
- }
74
- }
75
- }
76
- // 保存最后一个文件
77
- if (currentFile && currentHunk.length > 0) {
78
- patches.set(currentFile, {
79
- header: `*** Update File: ${currentFile}`,
80
- hunk: [...currentHunk]
81
- });
82
- }
83
- if (patches.size === 0) {
84
- // 如果无法解析,返回原始文本包装
85
- return `*** Begin Patch\n${diffText}\n*** End Patch`;
86
- }
87
- // 构建 apply_patch 格式
88
- const result = ['*** Begin Patch'];
89
- for (const [file, { header, hunk }] of patches.entries()) {
90
- result.push(header);
91
- result.push(...hunk);
92
- }
93
- result.push('*** End Patch');
94
- return result.join('\n');
95
- }
96
- /**
97
- * 修复 hunk 中缺少前缀的行
98
- */
99
- function fixHunkPrefixes(patchText) {
100
- const lines = patchText.split('\n');
101
- const result = [];
102
- let inUpdateSection = false;
103
- let afterHeader = false;
104
- for (let i = 0; i < lines.length; i++) {
105
- const line = lines[i];
106
- if (line.startsWith('*** Begin Patch')) {
107
- result.push(line);
108
- continue;
109
- }
110
- if (line.startsWith('*** End Patch')) {
111
- result.push(line);
112
- inUpdateSection = false;
113
- afterHeader = false;
114
- continue;
115
- }
116
- if (line.startsWith('*** Update File:') || line.startsWith('*** Add File:') || line.startsWith('*** Delete File:')) {
117
- result.push(line);
118
- inUpdateSection = true;
119
- afterHeader = true;
120
- continue;
121
- }
122
- if (inUpdateSection) {
123
- // 跳过紧跟在 header 后的空行
124
- if (afterHeader && line.trim() === '') {
125
- continue;
126
- }
127
- afterHeader = false;
128
- // 检查是否是 hunk 行
129
- if (/^@@\s+-\d+,\d+\s+\+\d+,\d+\s+@@/.test(line)) {
130
- result.push(line);
131
- continue;
132
- }
133
- // 检查是否已经有正确前缀
134
- if (/^[+-]\s/.test(line) || /^\s/.test(line)) {
135
- result.push(line);
136
- continue;
137
- }
138
- // 行缺少前缀,作为上下文行处理
139
- if (line.trim() === '') {
140
- result.push(' ');
141
- }
142
- else {
143
- result.push(' ' + line);
144
- }
145
- }
146
- else {
147
- result.push(line);
148
- }
149
- }
150
- return result.join('\n');
151
- }
152
- /**
153
- * 主要的修复函数
154
- */
155
- function fixApplyPatchFormat(argsStr) {
156
- try {
157
- let args;
158
- try {
159
- args = JSON.parse(argsStr);
160
- }
161
- catch {
162
- return { fixed: argsStr }; // 无法解析,返回原值
163
- }
164
- if (!args || typeof args !== 'object') {
165
- return { fixed: argsStr };
166
- }
167
- let patch = args.patch || args.input || '';
168
- if (typeof patch !== 'string') {
169
- return { fixed: argsStr };
170
- }
171
- let modified = false;
172
- let errorType;
173
- // 修复 1: 裸 diff 缺少包装
174
- if (isBareDiff(patch)) {
175
- patch = convertBareDiffToApplyPatch(patch);
176
- modified = true;
177
- errorType = 'missing_begin_patch';
178
- }
179
- // 修复 2: hunk 中缺少前缀的行
180
- const hasInvalidHunkLine = /"(.*?)".*Unexpected line found in update hunk/.test(patch);
181
- if (hasInvalidHunkLine || !patch.match(/^@@/m)) {
182
- const originalLength = patch.length;
183
- patch = fixHunkPrefixes(patch);
184
- if (patch.length !== originalLength) {
185
- modified = true;
186
- errorType = errorType || 'invalid_hunk_prefix';
187
- }
188
- }
189
- if (modified) {
190
- const newArgs = { ...args, patch, input: patch };
191
- return { fixed: JSON.stringify(newArgs), errorType };
192
- }
193
- return { fixed: argsStr };
194
- }
195
- catch {
196
- return { fixed: argsStr };
197
- }
198
- }
199
- /**
200
- * Bridge action: 遍历消息,修复 apply_patch 格式问题
201
- */
202
- const fixApplyPatchFormatAction = (ctx) => {
203
- const messages = ensureMessagesArray(ctx.state);
204
- for (const message of messages) {
205
- if (message.role !== 'assistant')
206
- continue;
207
- if (!Array.isArray(message.tool_calls))
208
- continue;
209
- for (const toolCall of message.tool_calls) {
210
- if (toolCall.type !== 'function')
211
- continue;
212
- const fn = toolCall.function;
213
- if (!fn || fn.name !== 'apply_patch')
214
- continue;
215
- const rawArgs = fn.arguments;
216
- if (typeof rawArgs !== 'string')
217
- continue;
218
- const { fixed, errorType } = fixApplyPatchFormat(rawArgs);
219
- if (fixed !== rawArgs) {
220
- // 保存可修复样本
221
- captureApplyPatchRegression({
222
- errorType: errorType || 'unknown_format_issue',
223
- originalArgs: rawArgs,
224
- fixerResult: fixed,
225
- source: 'compat.fix-apply-patch-format'
226
- });
227
- fn.arguments = fixed;
228
- toolCall._format_fixed = true;
229
- }
230
- }
231
- }
232
- };
233
- registerBridgeAction('compat.fix-apply-patch-format', fixApplyPatchFormatAction);