@jsonstudio/llms 0.6.802 → 0.6.954

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 (188) 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 +113 -17
  25. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +6 -3
  26. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +4 -0
  27. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +23 -1
  28. package/dist/conversion/hub/pipelines/inbound.d.ts +22 -0
  29. package/dist/conversion/hub/pipelines/outbound.d.ts +22 -0
  30. package/dist/conversion/hub/policy/policy-engine.d.ts +46 -0
  31. package/dist/conversion/hub/policy/policy-engine.js +176 -0
  32. package/dist/conversion/hub/policy/protocol-spec.d.ts +50 -0
  33. package/dist/conversion/hub/policy/protocol-spec.js +105 -0
  34. package/dist/conversion/hub/process/chat-process.d.ts +32 -0
  35. package/dist/conversion/hub/registry.d.ts +28 -0
  36. package/dist/conversion/hub/response/chat-response-utils.d.ts +6 -0
  37. package/dist/conversion/hub/response/provider-response.js +31 -0
  38. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +32 -1
  39. package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +7 -0
  40. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +96 -1
  41. package/dist/conversion/hub/semantic-mappers/index.d.ts +4 -0
  42. package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +21 -0
  43. package/dist/conversion/hub/standardized-bridge.d.ts +12 -0
  44. package/dist/conversion/hub/types/chat-schema.d.ts +112 -0
  45. package/dist/conversion/hub/types/errors.d.ts +5 -0
  46. package/dist/conversion/hub/types/format-envelope.d.ts +7 -0
  47. package/dist/conversion/hub/types/index.d.ts +6 -0
  48. package/dist/conversion/hub/types/json.d.ts +9 -0
  49. package/dist/conversion/hub/types/node.d.ts +31 -0
  50. package/dist/conversion/responses/responses-openai-bridge.js +263 -10
  51. package/dist/conversion/schema-validator.d.ts +7 -0
  52. package/dist/conversion/shared/args-mapping.d.ts +18 -0
  53. package/dist/conversion/shared/chat-request-filters.d.ts +9 -0
  54. package/dist/conversion/shared/errors.d.ts +1 -1
  55. package/dist/conversion/shared/gemini-tool-utils.js +105 -1
  56. package/dist/conversion/shared/jsonish.d.ts +3 -0
  57. package/dist/conversion/shared/mcp-injection.d.ts +2 -0
  58. package/dist/conversion/shared/media.d.ts +1 -0
  59. package/dist/conversion/shared/openai-message-normalize.d.ts +1 -0
  60. package/dist/conversion/shared/payload-budget.d.ts +13 -0
  61. package/dist/conversion/shared/reasoning-mapping.d.ts +5 -0
  62. package/dist/conversion/shared/responses-request-adapter.d.ts +1 -28
  63. package/dist/conversion/shared/responses-request-adapter.js +1 -430
  64. package/dist/conversion/shared/snapshot-hooks.js +58 -3
  65. package/dist/conversion/shared/tool-governor.js +8 -2
  66. package/dist/conversion/shared/tool-harvester.d.ts +31 -0
  67. package/dist/conversion/shared/tool-mapping.js +10 -29
  68. package/dist/conversion/types.d.ts +33 -0
  69. package/dist/filters/builtin/add-fields-filter.d.ts +8 -0
  70. package/dist/filters/builtin/blacklist-filter.d.ts +8 -0
  71. package/dist/filters/builtin/whitelist-filter.d.ts +8 -0
  72. package/dist/filters/engine.d.ts +16 -0
  73. package/dist/filters/special/request-tool-choice-policy.d.ts +11 -0
  74. package/dist/filters/special/response-finish-invariants.d.ts +11 -0
  75. package/dist/filters/special/response-openai-to-responses-bridge.d.ts +13 -0
  76. package/dist/filters/special/response-tool-arguments-blacklist.d.ts +12 -0
  77. package/dist/filters/special/response-tool-arguments-schema-converge.d.ts +13 -0
  78. package/dist/filters/special/response-tool-arguments-stringify.d.ts +9 -0
  79. package/dist/filters/special/response-tool-arguments-whitelist.d.ts +11 -0
  80. package/dist/filters/special/tool-filter-hooks.d.ts +19 -0
  81. package/dist/filters/special/tool-post-constraints.d.ts +31 -0
  82. package/dist/filters/types.d.ts +68 -0
  83. package/dist/filters/utils/fieldmap-loader.d.ts +2 -0
  84. package/dist/filters/utils/snapshot-writer.d.ts +10 -0
  85. package/dist/guidance/index.d.ts +3 -0
  86. package/dist/guidance/index.js +78 -83
  87. package/dist/http/sse-response.d.ts +22 -0
  88. package/dist/router/virtual-router/bootstrap.d.ts +6 -0
  89. package/dist/router/virtual-router/bootstrap.js +49 -5
  90. package/dist/router/virtual-router/classifier.d.ts +10 -0
  91. package/dist/router/virtual-router/engine-selection.js +98 -11
  92. package/dist/router/virtual-router/engine.js +177 -31
  93. package/dist/router/virtual-router/error-center.d.ts +10 -0
  94. package/dist/router/virtual-router/features.d.ts +3 -0
  95. package/dist/router/virtual-router/routing-instructions.d.ts +23 -1
  96. package/dist/router/virtual-router/routing-instructions.js +120 -30
  97. package/dist/router/virtual-router/types.d.ts +11 -0
  98. package/dist/servertool/engine.js +192 -17
  99. package/dist/servertool/handlers/apply-patch-guard.js +269 -0
  100. package/dist/servertool/handlers/exec-command-guard.js +558 -0
  101. package/dist/servertool/handlers/followup-message-trimmer.d.ts +16 -0
  102. package/dist/servertool/handlers/followup-message-trimmer.js +198 -0
  103. package/dist/servertool/handlers/followup-request-builder.d.ts +17 -0
  104. package/dist/servertool/handlers/followup-request-builder.js +122 -0
  105. package/dist/servertool/handlers/gemini-empty-reply-continue.js +252 -51
  106. package/dist/servertool/handlers/iflow-model-error-retry.js +12 -22
  107. package/dist/servertool/handlers/stop-message-auto.js +237 -75
  108. package/dist/servertool/handlers/vision.js +15 -27
  109. package/dist/servertool/handlers/web-search.js +17 -43
  110. package/dist/servertool/server-side-tools.d.ts +3 -0
  111. package/dist/servertool/server-side-tools.js +3 -0
  112. package/dist/sse/json-to-sse/anthropic-json-to-sse-converter.d.ts +2 -1
  113. package/dist/sse/json-to-sse/chat-json-to-sse-converter.d.ts +80 -0
  114. package/dist/sse/json-to-sse/event-generators/chat.d.ts +55 -0
  115. package/dist/sse/json-to-sse/event-generators/responses.d.ts +99 -0
  116. package/dist/sse/json-to-sse/gemini-json-to-sse-converter.d.ts +2 -1
  117. package/dist/sse/json-to-sse/responses-json-to-sse-converter.d.ts +80 -0
  118. package/dist/sse/json-to-sse/sequencers/anthropic-sequencer.d.ts +1 -1
  119. package/dist/sse/json-to-sse/sequencers/chat-sequencer.d.ts +2 -2
  120. package/dist/sse/json-to-sse/sequencers/gemini-sequencer.d.ts +1 -1
  121. package/dist/sse/json-to-sse/sequencers/responses-sequencer.d.ts +40 -0
  122. package/dist/sse/shared/chat-serializer.d.ts +4 -0
  123. package/dist/sse/shared/constants.d.ts +272 -0
  124. package/dist/sse/shared/serializers/anthropic-event-serializer.d.ts +1 -1
  125. package/dist/sse/shared/serializers/base-serializer.d.ts +158 -0
  126. package/dist/sse/shared/serializers/chat-event-serializer.d.ts +82 -0
  127. package/dist/sse/shared/serializers/gemini-event-serializer.d.ts +1 -1
  128. package/dist/sse/shared/serializers/index.d.ts +2 -1
  129. package/dist/sse/shared/serializers/responses-event-serializer.d.ts +123 -0
  130. package/dist/sse/shared/serializers/types.d.ts +51 -0
  131. package/dist/sse/shared/utils.d.ts +254 -0
  132. package/dist/sse/shared/writer.d.ts +2 -2
  133. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -1
  134. package/dist/sse/sse-to-json/builders/anthropic-response-builder.d.ts +1 -1
  135. package/dist/sse/sse-to-json/builders/response-builder.d.ts +1 -1
  136. package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +2 -1
  137. package/dist/sse/sse-to-json/gemini-sse-to-json-converter.d.ts +2 -1
  138. package/dist/sse/sse-to-json/parsers/sse-parser.d.ts +73 -0
  139. package/dist/sse/sse-to-json/responses-sse-to-json-converter.d.ts +1 -1
  140. package/dist/sse/types/chat-types.d.ts +1 -1
  141. package/dist/sse/types/responses-types.d.ts +1 -1
  142. package/dist/tools/apply-patch/execution-capturer.d.ts +13 -0
  143. package/dist/tools/apply-patch/execution-capturer.js +158 -0
  144. package/dist/tools/apply-patch/regression-capturer.d.ts +1 -0
  145. package/dist/tools/apply-patch/regression-capturer.js +5 -4
  146. package/dist/tools/apply-patch/structured.js +109 -13
  147. package/dist/tools/apply-patch/validator.js +112 -18
  148. package/dist/tools/tool-registry.d.ts +8 -0
  149. package/dist/tools/tool-registry.js +2 -1
  150. package/package.json +4 -4
  151. package/dist/conversion/compat/actions/apply-patch-format-fixer.js +0 -233
  152. package/dist/conversion/config/compat-profiles.json +0 -38
  153. package/dist/conversion/hub/pipeline/context-limit.d.ts +0 -13
  154. package/dist/conversion/hub/pipeline/context-limit.js +0 -55
  155. package/dist/conversion/hub/response/server-side-tools.d.ts +0 -26
  156. package/dist/conversion/hub/response/server-side-tools.js +0 -383
  157. package/dist/conversion/shared/bridge-conversation-store.d.ts +0 -41
  158. package/dist/conversion/shared/bridge-conversation-store.js +0 -279
  159. package/dist/conversion/shared/bridge-request-adapter.d.ts +0 -28
  160. package/dist/conversion/shared/bridge-request-adapter.js +0 -430
  161. package/dist/conversion/shared/responses-id-utils.js +0 -42
  162. package/dist/conversion/shared/responses-instructions.js +0 -113
  163. package/dist/conversion/shared/responses-message-utils.d.ts +0 -15
  164. package/dist/conversion/shared/responses-message-utils.js +0 -206
  165. package/dist/conversion/shared/responses-metadata.js +0 -1
  166. package/dist/conversion/shared/responses-output-utils.d.ts +0 -7
  167. package/dist/conversion/shared/responses-output-utils.js +0 -108
  168. package/dist/conversion/shared/responses-types.d.ts +0 -33
  169. package/dist/conversion/shared/tool-normalizers.d.ts +0 -4
  170. package/dist/conversion/shared/tool-normalizers.js +0 -84
  171. package/dist/filters/special/request-streaming-to-nonstreaming.d.ts +0 -13
  172. package/dist/filters/special/request-streaming-to-nonstreaming.js +0 -39
  173. package/dist/filters/special/response-apply-patch-toon-decode.d.ts +0 -23
  174. package/dist/filters/special/response-apply-patch-toon-decode.js +0 -460
  175. package/dist/filters/special/response-tool-arguments-toon-decode.d.ts +0 -10
  176. package/dist/filters/special/response-tool-arguments-toon-decode.js +0 -154
  177. package/dist/servertool/flow-types.d.ts +0 -40
  178. package/dist/servertool/flow-types.js +0 -1
  179. package/dist/servertool/orchestration-types.d.ts +0 -33
  180. package/dist/servertool/orchestration-types.js +0 -1
  181. package/dist/servertool/vision-tool.d.ts +0 -2
  182. package/dist/servertool/vision-tool.js +0 -185
  183. package/dist/tools/patch-args-normalizer.d.ts +0 -15
  184. package/dist/tools/patch-args-normalizer.js +0 -472
  185. package/dist/utils/toon.d.ts +0 -4
  186. package/dist/utils/toon.js +0 -75
  187. /package/dist/{conversion/compat/actions/apply-patch-format-fixer.d.ts → servertool/handlers/apply-patch-guard.d.ts} +0 -0
  188. /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);
@@ -501,29 +568,47 @@ export function validateApplyPatchArgs(argsString, rawArgs) {
501
568
  // Raw patch text without JSON wrapper
502
569
  if (!looksJsonContainer && looksLikePatch(rawTrimmed)) {
503
570
  const patchText = normalizeApplyPatchText(rawTrimmed);
571
+ if (!patchText.includes('*** Begin Patch')) {
572
+ return { ok: false, reason: 'unsupported_patch_format' };
573
+ }
504
574
  return { ok: true, normalizedArgs: toJson({ patch: patchText, input: patchText }) };
505
575
  }
506
576
  const extractFromRecord = (rec) => {
507
577
  // Special case: argsString claims to be JSON but isn't parseable and raw text looks like a patch.
508
578
  if (looksJsonContainer && Object.keys(rec).length === 0 && looksLikePatch(rawTrimmed)) {
509
- return { patchText: normalizeApplyPatchText(rawTrimmed) };
579
+ const patchText = normalizeApplyPatchText(rawTrimmed);
580
+ if (!patchText.includes('*** Begin Patch'))
581
+ return { failureReason: 'unsupported_patch_format' };
582
+ return { patchText };
510
583
  }
511
584
  const patchField = asString(rec.patch);
512
585
  if (patchField && looksLikePatch(patchField)) {
513
- return { patchText: normalizeApplyPatchText(patchField) };
586
+ const patchText = normalizeApplyPatchText(patchField);
587
+ if (!patchText.includes('*** Begin Patch'))
588
+ return { failureReason: 'unsupported_patch_format' };
589
+ return { patchText };
514
590
  }
515
591
  const diffField = asString(rec.diff) ?? asString(rec.patchText) ?? asString(rec.body);
516
592
  if (diffField && looksLikePatch(diffField)) {
517
- return { patchText: normalizeApplyPatchText(diffField) };
593
+ const patchText = normalizeApplyPatchText(diffField);
594
+ if (!patchText.includes('*** Begin Patch'))
595
+ return { failureReason: 'unsupported_patch_format' };
596
+ return { patchText };
518
597
  }
519
598
  const inputField = asString(rec.input);
520
599
  if (inputField && looksLikePatch(inputField)) {
521
- return { patchText: normalizeApplyPatchText(inputField) };
600
+ const patchText = normalizeApplyPatchText(inputField);
601
+ if (!patchText.includes('*** Begin Patch'))
602
+ return { failureReason: 'unsupported_patch_format' };
603
+ return { patchText };
522
604
  }
523
605
  // Common shape: patch text stored under `instructions` (e.g. "*** Update File: ...").
524
606
  const instructionsField = asString(rec.instructions);
525
607
  if (instructionsField && looksLikePatch(instructionsField)) {
526
- return { patchText: normalizeApplyPatchText(instructionsField) };
608
+ const patchText = normalizeApplyPatchText(instructionsField);
609
+ if (!patchText.includes('*** Begin Patch'))
610
+ return { failureReason: 'unsupported_patch_format' };
611
+ return { patchText };
527
612
  }
528
613
  // Common wrapper shape (seen in codex samples): { _raw: "{...json...}" }.
529
614
  // `_raw` may contain either patch text or a JSON-encoded structured payload.
@@ -531,7 +616,10 @@ export function validateApplyPatchArgs(argsString, rawArgs) {
531
616
  if (rawEnvelope) {
532
617
  const trimmed = rawEnvelope.trim();
533
618
  if (looksLikePatch(trimmed)) {
534
- return { patchText: normalizeApplyPatchText(trimmed) };
619
+ const patchText = normalizeApplyPatchText(trimmed);
620
+ if (!patchText.includes('*** Begin Patch'))
621
+ return { failureReason: 'unsupported_patch_format' };
622
+ return { patchText };
535
623
  }
536
624
  const parsed = tryParseJsonLoose(trimmed);
537
625
  if (parsed && isRecord(parsed)) {
@@ -598,7 +686,13 @@ export function validateApplyPatchArgs(argsString, rawArgs) {
598
686
  }
599
687
  }
600
688
  else if (typeof rawArgs === 'string' && looksLikePatch(rawArgs)) {
601
- 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
+ }
602
696
  }
603
697
  if (!patchText) {
604
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.802",
3
+ "version": "0.6.954",
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);
@@ -1,38 +0,0 @@
1
- {
2
- "version": "1.0.0",
3
- "profiles": {
4
- "chat:iflow": {
5
- "protocol": "openai-chat",
6
- "direction": "request",
7
- "mappings": [
8
- {
9
- "action": "remove",
10
- "path": "messages[*].content[*].annotations"
11
- }
12
- ],
13
- "filters": []
14
- },
15
- "chat:glm": {
16
- "protocol": "openai-chat",
17
- "direction": "request",
18
- "mappings": [
19
- {
20
- "action": "rename",
21
- "from": "response_format",
22
- "to": "extra.response_format"
23
- }
24
- ],
25
- "filters": []
26
- },
27
- "responses:c4m": {
28
- "protocol": "openai-responses",
29
- "direction": "response",
30
- "filters": [
31
- {
32
- "action": "rate_limit_text",
33
- "needle": "The Codex-For.ME service is available, but you have reached the request limit"
34
- }
35
- ]
36
- }
37
- }
38
- }
@@ -1,13 +0,0 @@
1
- import type { ProcessedRequest, StandardizedRequest } from '../types/standardized.js';
2
- import type { TargetMetadata } from '../../../router/virtual-router/types.js';
3
- export declare function enforceTargetContextLimitOrThrow(options: {
4
- requestId: string;
5
- routeName?: string;
6
- target: TargetMetadata;
7
- request: StandardizedRequest | ProcessedRequest;
8
- }): {
9
- estimatedInputTokens?: number;
10
- maxContextTokens?: number;
11
- allowedTokens?: number;
12
- safetyRatio?: number;
13
- };
@@ -1,55 +0,0 @@
1
- import { computeRequestTokens } from '../../../router/virtual-router/token-estimator.js';
2
- function readSafetyRatioFromEnv() {
3
- const raw = process?.env?.RCC_CONTEXT_TOKEN_SAFETY_RATIO ??
4
- process?.env?.ROUTECODEX_CONTEXT_TOKEN_SAFETY_RATIO ??
5
- '';
6
- const value = Number(raw);
7
- if (!Number.isFinite(value)) {
8
- return 0;
9
- }
10
- // Keep within sane bounds; default is 0 (exact limit).
11
- return Math.max(0, Math.min(0.5, value));
12
- }
13
- export function enforceTargetContextLimitOrThrow(options) {
14
- const maxContextTokensRaw = options.target?.maxContextTokens;
15
- const maxContextTokens = typeof maxContextTokensRaw === 'number' && Number.isFinite(maxContextTokensRaw) && maxContextTokensRaw > 0
16
- ? Math.floor(maxContextTokensRaw)
17
- : undefined;
18
- if (!maxContextTokens) {
19
- return {};
20
- }
21
- const estimatedInputTokens = computeRequestTokens(options.request, '');
22
- if (!(typeof estimatedInputTokens === 'number' && Number.isFinite(estimatedInputTokens) && estimatedInputTokens > 0)) {
23
- return { maxContextTokens };
24
- }
25
- const safetyRatio = readSafetyRatioFromEnv();
26
- const allowedTokens = Math.max(1, Math.floor(maxContextTokens * (1 - safetyRatio)));
27
- if (estimatedInputTokens >= allowedTokens) {
28
- const providerKey = options.target?.providerKey || 'unknown';
29
- const modelId = options.target?.modelId || options.request?.model || 'unknown';
30
- const routeName = options.routeName || 'unknown';
31
- const message = `Context too long for ${providerKey}.${modelId}: ` +
32
- `estimatedInputTokens=${estimatedInputTokens} exceeds allowed=${allowedTokens} ` +
33
- `(maxContextTokens=${maxContextTokens}, safetyRatio=${safetyRatio}, route=${routeName})`;
34
- const err = Object.assign(new Error(message), {
35
- name: 'ContextLimitError',
36
- code: 'CONTEXT_TOO_LONG',
37
- status: 400,
38
- requestId: options.requestId,
39
- providerKey: options.target?.providerKey,
40
- providerType: options.target?.providerType,
41
- routeName: options.routeName,
42
- details: {
43
- estimatedInputTokens,
44
- allowedTokens,
45
- maxContextTokens,
46
- safetyRatio,
47
- providerKey: options.target?.providerKey,
48
- modelId,
49
- routeName
50
- }
51
- });
52
- throw err;
53
- }
54
- return { estimatedInputTokens, maxContextTokens, allowedTokens, safetyRatio };
55
- }
@@ -1,26 +0,0 @@
1
- import type { AdapterContext } from '../types/chat-envelope.js';
2
- import type { JsonObject } from '../types/json.js';
3
- export type ProviderInvoker = (options: {
4
- providerKey: string;
5
- providerType?: string;
6
- modelId?: string;
7
- providerProtocol: string;
8
- payload: JsonObject;
9
- entryEndpoint: string;
10
- requestId: string;
11
- }) => Promise<{
12
- providerResponse: JsonObject;
13
- }>;
14
- export interface ServerSideToolEngineOptions {
15
- chatResponse: JsonObject;
16
- adapterContext: AdapterContext;
17
- entryEndpoint: string;
18
- requestId: string;
19
- providerProtocol: string;
20
- providerInvoker?: ProviderInvoker;
21
- }
22
- export interface ServerSideToolEngineResult {
23
- mode: 'passthrough' | 'web_search_flow';
24
- finalChatResponse: JsonObject;
25
- }
26
- export declare function runServerSideToolEngine(options: ServerSideToolEngineOptions): Promise<ServerSideToolEngineResult>;