@jsonstudio/llms 0.6.1892 → 0.6.2172

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 (159) hide show
  1. package/dist/conversion/compat/actions/deepseek-web-request.js +16 -2
  2. package/dist/conversion/compat/actions/deepseek-web-response.d.ts +7 -1
  3. package/dist/conversion/compat/actions/deepseek-web-response.js +302 -40
  4. package/dist/conversion/compat/actions/harvest-tool-calls-from-text.d.ts +5 -0
  5. package/dist/conversion/compat/actions/harvest-tool-calls-from-text.js +7 -4
  6. package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +1 -0
  7. package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +12 -0
  8. package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.js +1 -1
  9. package/dist/conversion/compat/actions/tool-text-request-guidance.d.ts +9 -0
  10. package/dist/conversion/compat/actions/tool-text-request-guidance.js +177 -0
  11. package/dist/conversion/compat/antigravity-session-signature.d.ts +6 -0
  12. package/dist/conversion/compat/antigravity-session-signature.js +15 -0
  13. package/dist/conversion/compat/profiles/chat-deepseek-web.json +52 -1
  14. package/dist/conversion/compat/profiles/chat-glm.json +22 -0
  15. package/dist/conversion/compat/profiles/chat-iflow.json +4 -0
  16. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +13 -27
  17. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +10 -1
  18. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +13 -4
  19. package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +1 -53
  20. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +8 -0
  21. package/dist/conversion/hub/pipeline/hub-pipeline.js +8 -4
  22. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +191 -9
  23. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +118 -15
  24. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +65 -2
  25. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage3_servertool_orchestration/index.d.ts +34 -0
  26. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage3_servertool_orchestration/index.js +75 -0
  27. package/dist/conversion/hub/process/chat-process.js +85 -18
  28. package/dist/conversion/hub/response/provider-response.js +21 -50
  29. package/dist/conversion/hub/response/response-runtime.js +71 -10
  30. package/dist/conversion/responses/responses-openai-bridge/response-payload.d.ts +3 -0
  31. package/dist/conversion/responses/responses-openai-bridge/response-payload.js +576 -0
  32. package/dist/conversion/responses/responses-openai-bridge/types.d.ts +42 -0
  33. package/dist/conversion/responses/responses-openai-bridge/types.js +1 -0
  34. package/dist/conversion/responses/responses-openai-bridge.d.ts +3 -44
  35. package/dist/conversion/responses/responses-openai-bridge.js +193 -504
  36. package/dist/conversion/shared/anthropic-message-utils.js +82 -2
  37. package/dist/conversion/shared/bridge-message-utils.js +92 -39
  38. package/dist/conversion/shared/snapshot-hooks.js +8 -13
  39. package/dist/conversion/shared/text-markup-normalizer/extractors-apply-patch.d.ts +2 -0
  40. package/dist/conversion/shared/text-markup-normalizer/extractors-apply-patch.js +129 -0
  41. package/dist/conversion/shared/text-markup-normalizer/extractors-json.d.ts +4 -0
  42. package/dist/conversion/shared/text-markup-normalizer/extractors-json.js +637 -0
  43. package/dist/conversion/shared/text-markup-normalizer/extractors-shared.d.ts +21 -0
  44. package/dist/conversion/shared/text-markup-normalizer/extractors-shared.js +177 -0
  45. package/dist/conversion/shared/text-markup-normalizer/extractors-transcript.d.ts +5 -0
  46. package/dist/conversion/shared/text-markup-normalizer/extractors-transcript.js +385 -0
  47. package/dist/conversion/shared/text-markup-normalizer/extractors-xml.d.ts +10 -0
  48. package/dist/conversion/shared/text-markup-normalizer/extractors-xml.js +602 -0
  49. package/dist/conversion/shared/text-markup-normalizer/extractors.d.ts +5 -0
  50. package/dist/conversion/shared/text-markup-normalizer/extractors.js +4 -0
  51. package/dist/conversion/shared/text-markup-normalizer/normalize.d.ts +2 -0
  52. package/dist/conversion/shared/text-markup-normalizer/normalize.js +76 -0
  53. package/dist/conversion/shared/text-markup-normalizer.d.ts +3 -25
  54. package/dist/conversion/shared/text-markup-normalizer.js +2 -1386
  55. package/dist/conversion/shared/tool-governor.js +136 -10
  56. package/dist/filters/utils/snapshot-writer.js +3 -3
  57. package/dist/router/virtual-router/bootstrap/auth-utils.d.ts +6 -0
  58. package/dist/router/virtual-router/bootstrap/auth-utils.js +288 -0
  59. package/dist/router/virtual-router/bootstrap/claude-code-helpers.d.ts +11 -0
  60. package/dist/router/virtual-router/bootstrap/claude-code-helpers.js +18 -0
  61. package/dist/router/virtual-router/bootstrap/config-defaults.d.ts +5 -0
  62. package/dist/router/virtual-router/bootstrap/config-defaults.js +13 -0
  63. package/dist/router/virtual-router/bootstrap/config-normalizers.d.ts +4 -0
  64. package/dist/router/virtual-router/bootstrap/config-normalizers.js +106 -0
  65. package/dist/router/virtual-router/bootstrap/profile-builder.d.ts +7 -0
  66. package/dist/router/virtual-router/bootstrap/profile-builder.js +68 -0
  67. package/dist/router/virtual-router/bootstrap/provider-normalization.d.ts +40 -0
  68. package/dist/router/virtual-router/bootstrap/provider-normalization.js +212 -0
  69. package/dist/router/virtual-router/bootstrap/responses-helpers.d.ts +15 -0
  70. package/dist/router/virtual-router/bootstrap/responses-helpers.js +65 -0
  71. package/dist/router/virtual-router/bootstrap/routing-config.d.ts +23 -0
  72. package/dist/router/virtual-router/bootstrap/routing-config.js +293 -0
  73. package/dist/router/virtual-router/bootstrap/streaming-helpers.d.ts +12 -0
  74. package/dist/router/virtual-router/bootstrap/streaming-helpers.js +128 -0
  75. package/dist/router/virtual-router/bootstrap/utils.d.ts +5 -0
  76. package/dist/router/virtual-router/bootstrap/utils.js +41 -0
  77. package/dist/router/virtual-router/bootstrap/web-search-config.d.ts +4 -0
  78. package/dist/router/virtual-router/bootstrap/web-search-config.js +131 -0
  79. package/dist/router/virtual-router/bootstrap.d.ts +0 -4
  80. package/dist/router/virtual-router/bootstrap.js +31 -1275
  81. package/dist/router/virtual-router/classifier.js +32 -14
  82. package/dist/router/virtual-router/engine/antigravity/alias-lease.js +2 -2
  83. package/dist/router/virtual-router/engine/cooldown-manager.d.ts +34 -0
  84. package/dist/router/virtual-router/engine/cooldown-manager.js +118 -0
  85. package/dist/router/virtual-router/engine/route-analytics.d.ts +28 -0
  86. package/dist/router/virtual-router/engine/route-analytics.js +44 -0
  87. package/dist/router/virtual-router/engine/routing-pools/index.js +165 -4
  88. package/dist/router/virtual-router/engine/sticky-session-manager.d.ts +29 -0
  89. package/dist/router/virtual-router/engine/sticky-session-manager.js +55 -0
  90. package/dist/router/virtual-router/engine-logging.d.ts +42 -1
  91. package/dist/router/virtual-router/engine-logging.js +82 -15
  92. package/dist/router/virtual-router/engine-selection/multimodal-capability.d.ts +3 -0
  93. package/dist/router/virtual-router/engine-selection/multimodal-capability.js +26 -0
  94. package/dist/router/virtual-router/engine-selection/route-utils.js +6 -2
  95. package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +1 -0
  96. package/dist/router/virtual-router/engine-selection/tier-selection.js +31 -1
  97. package/dist/router/virtual-router/engine.d.ts +21 -7
  98. package/dist/router/virtual-router/engine.js +198 -194
  99. package/dist/router/virtual-router/features.js +12 -4
  100. package/dist/router/virtual-router/message-utils.d.ts +8 -0
  101. package/dist/router/virtual-router/message-utils.js +170 -45
  102. package/dist/router/virtual-router/pre-command-file-resolver.js +40 -2
  103. package/dist/router/virtual-router/routing-instructions.d.ts +8 -0
  104. package/dist/router/virtual-router/routing-instructions.js +18 -2
  105. package/dist/router/virtual-router/routing-stop-message-actions.js +34 -10
  106. package/dist/router/virtual-router/routing-stop-message-state-codec.d.ts +2 -0
  107. package/dist/router/virtual-router/routing-stop-message-state-codec.js +50 -1
  108. package/dist/router/virtual-router/stop-message-state-sync.d.ts +1 -1
  109. package/dist/router/virtual-router/stop-message-state-sync.js +3 -0
  110. package/dist/router/virtual-router/token-counter.js +51 -10
  111. package/dist/router/virtual-router/tool-signals.js +4 -0
  112. package/dist/router/virtual-router/types.d.ts +15 -0
  113. package/dist/servertool/clock/session-scope.d.ts +3 -0
  114. package/dist/servertool/clock/session-scope.js +52 -0
  115. package/dist/servertool/clock/state.js +9 -0
  116. package/dist/servertool/clock/tasks.js +12 -1
  117. package/dist/servertool/clock/types.d.ts +3 -0
  118. package/dist/servertool/engine.js +177 -31
  119. package/dist/servertool/handlers/clock-auto.js +2 -8
  120. package/dist/servertool/handlers/clock.js +6 -9
  121. package/dist/servertool/handlers/recursive-detection-guard.js +53 -14
  122. package/dist/servertool/handlers/stop-message-auto/blocked-report.d.ts +16 -0
  123. package/dist/servertool/handlers/stop-message-auto/blocked-report.js +349 -0
  124. package/dist/servertool/handlers/stop-message-auto/iflow-followup.d.ts +23 -0
  125. package/dist/servertool/handlers/stop-message-auto/iflow-followup.js +503 -0
  126. package/dist/servertool/handlers/stop-message-auto/routing-state.d.ts +38 -0
  127. package/dist/servertool/handlers/stop-message-auto/routing-state.js +149 -0
  128. package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +67 -0
  129. package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +387 -0
  130. package/dist/servertool/handlers/stop-message-auto.d.ts +1 -1
  131. package/dist/servertool/handlers/stop-message-auto.js +80 -556
  132. package/dist/servertool/handlers/stop-message-stage-policy/bd-runtime.d.ts +18 -0
  133. package/dist/servertool/handlers/stop-message-stage-policy/bd-runtime.js +398 -0
  134. package/dist/servertool/handlers/stop-message-stage-policy/decision.d.ts +9 -0
  135. package/dist/servertool/handlers/stop-message-stage-policy/decision.js +127 -0
  136. package/dist/servertool/handlers/stop-message-stage-policy/observation.d.ts +2 -0
  137. package/dist/servertool/handlers/stop-message-stage-policy/observation.js +179 -0
  138. package/dist/servertool/handlers/stop-message-stage-policy/templates.d.ts +4 -0
  139. package/dist/servertool/handlers/stop-message-stage-policy/templates.js +96 -0
  140. package/dist/servertool/handlers/stop-message-stage-policy/text-utils.d.ts +9 -0
  141. package/dist/servertool/handlers/stop-message-stage-policy/text-utils.js +89 -0
  142. package/dist/servertool/handlers/stop-message-stage-policy/types.d.ts +59 -0
  143. package/dist/servertool/handlers/stop-message-stage-policy/types.js +1 -0
  144. package/dist/servertool/handlers/stop-message-stage-policy.d.ts +3 -43
  145. package/dist/servertool/handlers/stop-message-stage-policy.js +2 -684
  146. package/dist/servertool/handlers/web-search.js +117 -0
  147. package/dist/servertool/server-side-tools.d.ts +0 -1
  148. package/dist/servertool/server-side-tools.js +4 -3
  149. package/dist/sse/sse-to-json/builders/response-builder.js +16 -0
  150. package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +1 -0
  151. package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +110 -37
  152. package/dist/telemetry/stats-center.d.ts +9 -0
  153. package/dist/telemetry/stats-center.js +29 -1
  154. package/dist/tools/apply-patch/structured/coercion.js +3 -11
  155. package/dist/tools/exec-command/validator.d.ts +1 -0
  156. package/dist/tools/exec-command/validator.js +132 -0
  157. package/dist/tools/tool-registry.d.ts +1 -0
  158. package/dist/tools/tool-registry.js +1 -1
  159. package/package.json +1 -1
@@ -129,6 +129,12 @@ function buildToolOutputSnapshot(payload, providerProtocol) {
129
129
  const snapshot = {
130
130
  providerProtocol: providerProtocol ?? 'unknown'
131
131
  };
132
+ try {
133
+ normalizeShellLikeToolCallsBeforeGovernance(payload);
134
+ }
135
+ catch {
136
+ // shape repair is best-effort; never block main flow
137
+ }
132
138
  try {
133
139
  injectApplyPatchDiagnostics(payload);
134
140
  }
@@ -141,6 +147,161 @@ function buildToolOutputSnapshot(payload, providerProtocol) {
141
147
  }
142
148
  return snapshot;
143
149
  }
150
+ const SHELL_LIKE_TOOL_NAMES = new Set(['exec_command', 'shell_command', 'shell', 'bash', 'terminal']);
151
+ function readTrimmedString(value) {
152
+ if (typeof value !== 'string') {
153
+ return undefined;
154
+ }
155
+ const trimmed = value.trim();
156
+ return trimmed.length ? trimmed : undefined;
157
+ }
158
+ function isRecord(value) {
159
+ return !!value && typeof value === 'object' && !Array.isArray(value);
160
+ }
161
+ function readStringArrayCommand(value) {
162
+ if (!Array.isArray(value)) {
163
+ return undefined;
164
+ }
165
+ const tokens = value
166
+ .map((item) => (item == null ? '' : String(item).trim()))
167
+ .filter((item) => item.length > 0);
168
+ return tokens.length ? tokens.join(' ') : undefined;
169
+ }
170
+ function parseJsonRecord(value) {
171
+ if (isRecord(value)) {
172
+ return value;
173
+ }
174
+ if (typeof value !== 'string') {
175
+ return undefined;
176
+ }
177
+ const trimmed = value.trim();
178
+ if (!trimmed) {
179
+ return {};
180
+ }
181
+ try {
182
+ const parsed = JSON.parse(trimmed);
183
+ return isRecord(parsed) ? parsed : undefined;
184
+ }
185
+ catch {
186
+ return undefined;
187
+ }
188
+ }
189
+ function readCommandFromArgs(args) {
190
+ const input = isRecord(args.input) ? args.input : undefined;
191
+ const direct = readTrimmedString(args.cmd) ??
192
+ readTrimmedString(args.command) ??
193
+ readTrimmedString(args.script) ??
194
+ readTrimmedString(args.toon) ??
195
+ readStringArrayCommand(args.cmd) ??
196
+ readStringArrayCommand(args.command);
197
+ if (direct) {
198
+ return direct;
199
+ }
200
+ if (!input) {
201
+ return undefined;
202
+ }
203
+ return (readTrimmedString(input.cmd) ??
204
+ readTrimmedString(input.command) ??
205
+ readTrimmedString(input.script) ??
206
+ readStringArrayCommand(input.cmd) ??
207
+ readStringArrayCommand(input.command));
208
+ }
209
+ function readWorkdirFromArgs(args) {
210
+ const input = isRecord(args.input) ? args.input : undefined;
211
+ return (readTrimmedString(args.workdir) ??
212
+ readTrimmedString(args.cwd) ??
213
+ readTrimmedString(args.workDir) ??
214
+ readTrimmedString(input?.workdir) ??
215
+ readTrimmedString(input?.cwd));
216
+ }
217
+ function collectRequestedToolNames(payload) {
218
+ const names = new Set();
219
+ const root = payload;
220
+ const tools = Array.isArray(root.tools) ? root.tools : [];
221
+ for (const tool of tools) {
222
+ if (!isRecord(tool))
223
+ continue;
224
+ const fn = isRecord(tool.function) ? tool.function : undefined;
225
+ const name = readTrimmedString(fn?.name) ?? readTrimmedString(tool.name);
226
+ if (name) {
227
+ names.add(name);
228
+ }
229
+ }
230
+ return names;
231
+ }
232
+ function resolveShellLikeToolName(rawName, requestedToolNames) {
233
+ if (requestedToolNames.size === 0) {
234
+ return rawName;
235
+ }
236
+ if (requestedToolNames.has(rawName)) {
237
+ return rawName;
238
+ }
239
+ if (requestedToolNames.has('exec_command')) {
240
+ return 'exec_command';
241
+ }
242
+ if (requestedToolNames.has('shell_command')) {
243
+ return 'shell_command';
244
+ }
245
+ return rawName;
246
+ }
247
+ function normalizeShellLikeToolCallsBeforeGovernance(payload) {
248
+ const root = payload;
249
+ const messages = Array.isArray(root.messages) ? root.messages : [];
250
+ if (!messages.length) {
251
+ return;
252
+ }
253
+ const requestedToolNames = collectRequestedToolNames(payload);
254
+ for (const message of messages) {
255
+ if (!isRecord(message))
256
+ continue;
257
+ const role = readTrimmedString(message.role)?.toLowerCase();
258
+ if (role !== 'assistant')
259
+ continue;
260
+ const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
261
+ if (!toolCalls.length)
262
+ continue;
263
+ for (const call of toolCalls) {
264
+ if (!isRecord(call))
265
+ continue;
266
+ const fn = isRecord(call.function) ? call.function : undefined;
267
+ if (!fn)
268
+ continue;
269
+ const rawName = readTrimmedString(fn.name);
270
+ if (!rawName)
271
+ continue;
272
+ if (!SHELL_LIKE_TOOL_NAMES.has(rawName.toLowerCase()))
273
+ continue;
274
+ const resolvedName = resolveShellLikeToolName(rawName, requestedToolNames);
275
+ if (resolvedName !== rawName) {
276
+ fn.name = resolvedName;
277
+ }
278
+ const parsedArgs = parseJsonRecord(fn.arguments);
279
+ const args = parsedArgs ?? {};
280
+ const cmd = readCommandFromArgs(args);
281
+ if (!cmd) {
282
+ continue;
283
+ }
284
+ const nextArgs = {
285
+ ...args,
286
+ cmd,
287
+ command: cmd
288
+ };
289
+ const workdir = readWorkdirFromArgs(args);
290
+ if (workdir) {
291
+ nextArgs.workdir = workdir;
292
+ }
293
+ if (Object.prototype.hasOwnProperty.call(nextArgs, 'toon')) {
294
+ delete nextArgs.toon;
295
+ }
296
+ try {
297
+ fn.arguments = JSON.stringify(nextArgs);
298
+ }
299
+ catch {
300
+ fn.arguments = JSON.stringify({ cmd, command: cmd, ...(workdir ? { workdir } : {}) });
301
+ }
302
+ }
303
+ }
304
+ }
144
305
  function collectToolOutputs(payload) {
145
306
  const aggregated = [];
146
307
  const seen = new Set();
@@ -319,7 +480,7 @@ function normalizeToolOutputEntry(entry) {
319
480
  name
320
481
  };
321
482
  }
322
- function buildApplyPatchDiagnostics(output) {
483
+ function buildToolParseDiagnostics(output) {
323
484
  if (!output || typeof output !== 'string') {
324
485
  return undefined;
325
486
  }
@@ -328,10 +489,28 @@ function buildApplyPatchDiagnostics(output) {
328
489
  return undefined;
329
490
  }
330
491
  if (output.includes('missing field `input`')) {
331
- return '\n\n[RouteCodex precheck] apply_patch 参数解析失败:缺少字段 "input"。当前 RouteCodex 期望 { input, patch } 形态,并且两个字段都应包含完整统一 diff 文本。';
492
+ return {
493
+ kind: 'apply_patch',
494
+ text: '\n\n[RouteCodex precheck] apply_patch 参数解析失败:缺少字段 "input"。当前 RouteCodex 期望 { input, patch } 形态,并且两个字段都应包含完整统一 diff 文本。'
495
+ };
332
496
  }
333
497
  if (output.includes('invalid type: map, expected a string')) {
334
- return '\n\n[RouteCodex precheck] apply_patch 参数类型错误:检测到 JSON 对象(map),但客户端期望字符串。请先对参数做 JSON.stringify 再写入 arguments,或直接提供 { patch: "<统一 diff>" } 形式。';
498
+ return {
499
+ kind: 'apply_patch',
500
+ text: '\n\n[RouteCodex precheck] apply_patch 参数类型错误:检测到 JSON 对象(map),但客户端期望字符串。请先对参数做 JSON.stringify 再写入 arguments,或直接提供 { patch: "<统一 diff>" } 形式。'
501
+ };
502
+ }
503
+ if (output.includes('missing field `command`')) {
504
+ return {
505
+ kind: 'shell_like',
506
+ text: '\n\n[RouteCodex precheck] shell/exec 参数解析失败:缺少字段 "command"。请改为 {"tool_calls":[{"name":"shell_command","input":{"command":"<cmd>"}}]};若调用 exec_command,建议同时提供 {"cmd":"<cmd>","command":"<cmd>"}。'
507
+ };
508
+ }
509
+ if (output.includes('missing field `cmd`')) {
510
+ return {
511
+ kind: 'shell_like',
512
+ text: '\n\n[RouteCodex precheck] shell/exec 参数解析失败:缺少字段 "cmd"。exec_command 推荐形状为 {"cmd":"<cmd>","command":"<cmd>","workdir":"<path>"}。'
513
+ };
335
514
  }
336
515
  return undefined;
337
516
  }
@@ -347,17 +526,20 @@ function appendDiagnosticsToRecord(record) {
347
526
  if (!text || text.includes('[RouteCodex precheck]')) {
348
527
  return;
349
528
  }
350
- const diag = buildApplyPatchDiagnostics(text);
529
+ const diag = buildToolParseDiagnostics(text);
351
530
  if (!diag) {
352
531
  return;
353
532
  }
354
- // Some providers / compatibility layers omit `name` on tool outputs.
355
- // When the output text matches apply_patch argument-parse failures, still inject the diagnostics
356
- // to keep user-visible behavior stable across modes.
357
- if (name && name !== 'apply_patch') {
533
+ const normalizedName = name?.toLowerCase();
534
+ if (diag.kind === 'apply_patch' && normalizedName && normalizedName !== 'apply_patch') {
535
+ return;
536
+ }
537
+ if (diag.kind === 'shell_like' &&
538
+ normalizedName &&
539
+ !SHELL_LIKE_TOOL_NAMES.has(normalizedName)) {
358
540
  return;
359
541
  }
360
- const merged = `${text}${diag}`;
542
+ const merged = `${text}${diag.text}`;
361
543
  if (typeof record.output === 'string') {
362
544
  record.output = merged;
363
545
  }
@@ -37,6 +37,66 @@ function resolveProviderType(protocol) {
37
37
  return 'gemini';
38
38
  return undefined;
39
39
  }
40
+ function readFiniteNumber(value) {
41
+ if (typeof value === 'number' && Number.isFinite(value)) {
42
+ return Math.floor(value);
43
+ }
44
+ if (typeof value === 'string') {
45
+ const trimmed = value.trim();
46
+ if (!trimmed.length) {
47
+ return undefined;
48
+ }
49
+ const parsed = Number(trimmed);
50
+ if (Number.isFinite(parsed)) {
51
+ return Math.floor(parsed);
52
+ }
53
+ }
54
+ return undefined;
55
+ }
56
+ function readObject(value) {
57
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
58
+ return undefined;
59
+ }
60
+ return value;
61
+ }
62
+ function extractContextLengthDiagnostics(adapterContext) {
63
+ const ctx = readObject(adapterContext);
64
+ if (!ctx) {
65
+ return {};
66
+ }
67
+ const runtimeNode = readObject(ctx.__rt);
68
+ const targetNode = readObject(ctx.target);
69
+ const estimatedPromptTokens = readFiniteNumber(ctx.estimatedInputTokens) ??
70
+ readFiniteNumber(ctx.requestTokens) ??
71
+ readFiniteNumber(ctx.reqTokens) ??
72
+ readFiniteNumber(runtimeNode?.estimatedInputTokens) ??
73
+ readFiniteNumber(runtimeNode?.requestTokens) ??
74
+ readFiniteNumber(runtimeNode?.reqTokens);
75
+ const maxContextTokens = readFiniteNumber(targetNode?.maxContextTokens) ??
76
+ readFiniteNumber(ctx.maxContextTokens) ??
77
+ readFiniteNumber(runtimeNode?.maxContextTokens);
78
+ return {
79
+ ...(typeof estimatedPromptTokens === 'number' ? { estimatedPromptTokens } : {}),
80
+ ...(typeof maxContextTokens === 'number' ? { maxContextTokens } : {})
81
+ };
82
+ }
83
+ function isContextLengthExceededSignal(args) {
84
+ const code = typeof args.code === 'string' ? args.code.trim().toLowerCase() : '';
85
+ if (code.includes('context_length_exceeded')) {
86
+ return true;
87
+ }
88
+ const message = args.message.toLowerCase();
89
+ if (message.includes('context_length_exceeded') || message.includes('达到对话长度上限') || message.includes('对话长度上限')) {
90
+ return true;
91
+ }
92
+ const finishReason = typeof args.context?.errorData === 'object' &&
93
+ args.context.errorData &&
94
+ !Array.isArray(args.context.errorData) &&
95
+ typeof args.context.errorData.finish_reason === 'string'
96
+ ? String(args.context.errorData.finish_reason).trim().toLowerCase()
97
+ : '';
98
+ return finishReason === 'context_length_exceeded';
99
+ }
40
100
  export async function runRespInboundStage1SseDecode(options) {
41
101
  // Transport compatibility: some HTTP clients return JSON bodies as plain strings when the upstream
42
102
  // mislabels `Content-Type`. Best-effort parse JSON text early so downstream format adapters and
@@ -125,20 +185,36 @@ export async function runRespInboundStage1SseDecode(options) {
125
185
  }
126
186
  catch (error) {
127
187
  const message = error instanceof Error ? error.message : String(error);
188
+ const errRecord = error;
189
+ const upstreamCode = typeof errRecord.code === 'string' ? errRecord.code : undefined;
190
+ const upstreamContext = readObject(errRecord.context);
191
+ const contextLengthExceeded = isContextLengthExceededSignal({
192
+ code: upstreamCode,
193
+ message,
194
+ context: upstreamContext
195
+ });
196
+ const diagnostics = extractContextLengthDiagnostics(options.adapterContext);
128
197
  recordStage(options.stageRecorder, 'chat_process.resp.stage1.sse_decode', {
129
198
  streamDetected: true,
130
199
  decoded: false,
131
200
  protocol: options.providerProtocol,
132
- error: message
201
+ error: message,
202
+ ...(upstreamCode ? { upstreamCode } : {}),
203
+ ...(contextLengthExceeded ? { reason: 'context_length_exceeded' } : {}),
204
+ ...(Object.keys(diagnostics).length ? diagnostics : {})
133
205
  });
134
- throw new ProviderProtocolError(`[chat_process.resp.stage1.sse_decode] Failed to decode SSE payload for protocol ${options.providerProtocol}: ${message}`, {
206
+ throw new ProviderProtocolError(`[chat_process.resp.stage1.sse_decode] Failed to decode SSE payload for protocol ${options.providerProtocol}: ${message}` +
207
+ (contextLengthExceeded ? ' (context too long; please compress conversation context and retry)' : ''), {
135
208
  code: 'SSE_DECODE_ERROR',
136
209
  protocol: options.providerProtocol,
137
210
  providerType: resolveProviderType(options.providerProtocol),
138
211
  details: {
139
212
  phase: 'chat_process.resp.stage1.sse_decode',
140
213
  requestId: options.adapterContext.requestId,
141
- message
214
+ message,
215
+ ...(upstreamCode ? { upstreamCode } : {}),
216
+ ...(contextLengthExceeded ? { reason: 'context_length_exceeded' } : {}),
217
+ ...(Object.keys(diagnostics).length ? diagnostics : {})
142
218
  }
143
219
  });
144
220
  }
@@ -179,43 +255,70 @@ async function tryDecodeJsonBodyFromStream(stream) {
179
255
  if (first.done || first.value == null) {
180
256
  return null;
181
257
  }
258
+ const consumedChunks = [first.value];
182
259
  const firstChunk = first.value;
183
- let prefix = (typeof firstChunk === 'string' ? firstChunk : firstChunk.toString('utf8')).trimStart();
260
+ let prefix = chunkToUtf8(firstChunk).trimStart();
184
261
  prefix = prefix.replace(/^\)\]\}',?\s*/u, '');
185
262
  prefix = prefix.replace(/^data:\s*/iu, '');
186
263
  const looksLikeJson = prefix.startsWith('{') || prefix.startsWith('[');
187
264
  if (!looksLikeJson) {
188
- // Rewind by re-wrapping the iterator so downstream SSE decoder still sees the first chunk.
189
- // eslint-disable-next-line no-param-reassign
190
- stream[Symbol.asyncIterator] = () => replayIterator(firstChunk, iterator);
265
+ rewindStreamWithConsumedChunks(stream, consumedChunks, iterator);
191
266
  return null;
192
267
  }
193
- let body = typeof firstChunk === 'string' ? firstChunk : firstChunk.toString('utf8');
268
+ let body = chunkToUtf8(firstChunk);
194
269
  while (true) {
195
270
  const next = await iterator.next();
196
271
  if (next.done)
197
272
  break;
198
- body += typeof next.value === 'string' ? next.value : Buffer.from(next.value).toString('utf8');
273
+ consumedChunks.push(next.value);
274
+ body += chunkToUtf8(next.value);
199
275
  // Guard: avoid unbounded buffering if the upstream is actually SSE but starts with whitespace.
200
276
  if (body.length > 1024 * 1024) {
277
+ rewindStreamWithConsumedChunks(stream, consumedChunks, iterator);
201
278
  return null;
202
279
  }
203
280
  }
204
281
  try {
205
282
  const parsed = JSON.parse(body);
206
- return (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) ? parsed : null;
283
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
284
+ return parsed;
285
+ }
286
+ rewindStreamWithConsumedChunks(stream, consumedChunks, iterator);
287
+ return null;
207
288
  }
208
289
  catch {
290
+ rewindStreamWithConsumedChunks(stream, consumedChunks, iterator);
209
291
  return null;
210
292
  }
211
293
  }
212
- function replayIterator(firstChunk, iterator) {
213
- let yieldedFirst = false;
294
+ function chunkToUtf8(chunk) {
295
+ if (typeof chunk === 'string') {
296
+ return chunk;
297
+ }
298
+ if (Buffer.isBuffer(chunk)) {
299
+ return chunk.toString('utf8');
300
+ }
301
+ if (chunk instanceof Uint8Array) {
302
+ return Buffer.from(chunk).toString('utf8');
303
+ }
304
+ if (chunk instanceof ArrayBuffer) {
305
+ return Buffer.from(new Uint8Array(chunk)).toString('utf8');
306
+ }
307
+ return String(chunk ?? '');
308
+ }
309
+ function rewindStreamWithConsumedChunks(stream, consumedChunks, iterator) {
310
+ // Rewind by re-wrapping the iterator so downstream SSE decoder still sees everything consumed here.
311
+ // eslint-disable-next-line no-param-reassign
312
+ stream[Symbol.asyncIterator] = () => replayIterator(consumedChunks, iterator);
313
+ }
314
+ function replayIterator(consumedChunks, iterator) {
315
+ let replayIndex = 0;
214
316
  return {
215
317
  async next() {
216
- if (!yieldedFirst) {
217
- yieldedFirst = true;
218
- return { done: false, value: firstChunk };
318
+ if (replayIndex < consumedChunks.length) {
319
+ const value = consumedChunks[replayIndex];
320
+ replayIndex += 1;
321
+ return { done: false, value };
219
322
  }
220
323
  return iterator.next();
221
324
  },
@@ -1,9 +1,18 @@
1
1
  import { runChatResponseToolFilters } from '../../../../../shared/tool-filter-pipeline.js';
2
2
  import { normalizeApplyPatchToolCallsOnResponse } from '../../../../../shared/tool-governor.js';
3
3
  import { buildChatResponseFromResponses } from '../../../../../shared/responses-response-utils.js';
4
+ import { normalizeAssistantTextToToolCalls } from '../../../../../shared/text-markup-normalizer.js';
5
+ import { stripOrphanFunctionCallsTag } from '../../../../../compat/actions/strip-orphan-function-calls-tag.js';
4
6
  import { ToolGovernanceEngine } from '../../../../tool-governance/index.js';
5
7
  import { recordStage } from '../../../stages/utils.js';
6
8
  const toolGovernanceEngine = new ToolGovernanceEngine();
9
+ const TOOL_CALL_JSON_MARKER = /["']tool_calls["']\s*:/i;
10
+ const SHELL_TOOL_NAME_ALIASES = {
11
+ shell_command: 'exec_command',
12
+ shell: 'exec_command',
13
+ bash: 'exec_command',
14
+ terminal: 'exec_command'
15
+ };
7
16
  function isCanonicalChatCompletion(payload) {
8
17
  if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
9
18
  return false;
@@ -36,13 +45,67 @@ function coerceToCanonicalChatCompletion(payload) {
36
45
  }
37
46
  return payload;
38
47
  }
48
+ function maybeHarvestEmptyToolCallsFromJsonContent(payload) {
49
+ if (!payload || typeof payload !== 'object') {
50
+ return payload;
51
+ }
52
+ const choices = Array.isArray(payload.choices) ? payload.choices : [];
53
+ if (!choices.length) {
54
+ return payload;
55
+ }
56
+ for (const choice of choices) {
57
+ if (!choice || typeof choice !== 'object' || Array.isArray(choice)) {
58
+ continue;
59
+ }
60
+ const message = choice.message;
61
+ if (!message || typeof message !== 'object' || Array.isArray(message)) {
62
+ continue;
63
+ }
64
+ const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : undefined;
65
+ if (!toolCalls || toolCalls.length !== 0) {
66
+ continue;
67
+ }
68
+ const content = typeof message.content === 'string' ? String(message.content).trim() : '';
69
+ if (!content || !TOOL_CALL_JSON_MARKER.test(content)) {
70
+ continue;
71
+ }
72
+ const normalized = normalizeAssistantTextToToolCalls(message, {
73
+ jsonToolRepair: {
74
+ toolNameAliases: SHELL_TOOL_NAME_ALIASES,
75
+ argumentAliases: {
76
+ exec_command: {
77
+ cmd: ['cmd', 'command', 'input.command', 'script'],
78
+ command: ['cmd', 'command', 'input.command', 'script'],
79
+ workdir: ['workdir', 'cwd', 'input.cwd']
80
+ }
81
+ }
82
+ }
83
+ });
84
+ const repairedCalls = Array.isArray(normalized.tool_calls) ? normalized.tool_calls : [];
85
+ if (!repairedCalls.length) {
86
+ continue;
87
+ }
88
+ choice.message = normalized;
89
+ const finish = typeof choice.finish_reason === 'string' ? String(choice.finish_reason).trim().toLowerCase() : '';
90
+ if (!finish || finish === 'stop') {
91
+ choice.finish_reason = 'tool_calls';
92
+ }
93
+ }
94
+ return payload;
95
+ }
96
+ function sanitizeResponseShapeBeforeGovernance(payload) {
97
+ return stripOrphanFunctionCallsTag(payload);
98
+ }
39
99
  export async function runRespProcessStage1ToolGovernance(options) {
40
100
  const canonicalInput = coerceToCanonicalChatCompletion(options.payload);
101
+ const shapeSanitizedInput = sanitizeResponseShapeBeforeGovernance(canonicalInput);
102
+ maybeHarvestEmptyToolCallsFromJsonContent(shapeSanitizedInput);
41
103
  recordStage(options.stageRecorder, 'chat_process.resp.stage6.canonicalize_chat_completion', {
42
104
  converted: canonicalInput !== options.payload,
43
- canonicalPayload: canonicalInput
105
+ shapeSanitized: shapeSanitizedInput !== canonicalInput,
106
+ canonicalPayload: shapeSanitizedInput
44
107
  });
45
- const filtered = await runChatResponseToolFilters(canonicalInput, {
108
+ const filtered = await runChatResponseToolFilters(shapeSanitizedInput, {
46
109
  entryEndpoint: options.entryEndpoint,
47
110
  requestId: options.requestId,
48
111
  profile: 'openai-chat'
@@ -0,0 +1,34 @@
1
+ import type { AdapterContext } from '../../../../types/chat-envelope.js';
2
+ import type { JsonObject } from '../../../../types/json.js';
3
+ import type { StageRecorder } from '../../../../format-adapters/index.js';
4
+ import type { ChatCompletionLike } from '../../../../response/response-mappers.js';
5
+ import type { ProviderInvoker } from '../../../../../../servertool/types.js';
6
+ type ProviderProtocol = 'openai-chat' | 'openai-responses' | 'anthropic-messages' | 'gemini-chat';
7
+ type ReenterPipeline = (options: {
8
+ entryEndpoint: string;
9
+ requestId: string;
10
+ body: JsonObject;
11
+ metadata?: JsonObject;
12
+ }) => Promise<{
13
+ body?: JsonObject;
14
+ __sse_responses?: NodeJS.ReadableStream;
15
+ format?: string;
16
+ }>;
17
+ export interface RespProcessStage3ServerToolOrchestrationOptions {
18
+ payload: ChatCompletionLike;
19
+ adapterContext: AdapterContext;
20
+ requestId: string;
21
+ entryEndpoint: string;
22
+ providerProtocol: ProviderProtocol;
23
+ stageRecorder?: StageRecorder;
24
+ providerInvoker?: ProviderInvoker;
25
+ reenterPipeline?: ReenterPipeline;
26
+ }
27
+ export interface RespProcessStage3ServerToolOrchestrationResult {
28
+ payload: ChatCompletionLike;
29
+ executed: boolean;
30
+ flowId?: string;
31
+ skipReason?: 'no_servertool_support' | 'missing_reenter_pipeline';
32
+ }
33
+ export declare function runRespProcessStage3ServerToolOrchestration(options: RespProcessStage3ServerToolOrchestrationOptions): Promise<RespProcessStage3ServerToolOrchestrationResult>;
34
+ export {};
@@ -0,0 +1,75 @@
1
+ import { runServerToolOrchestration } from '../../../../../../servertool/engine.js';
2
+ import { recordStage } from '../../../stages/utils.js';
3
+ function detectProviderResponseShape(payload) {
4
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload))
5
+ return 'unknown';
6
+ const p = payload;
7
+ if (Array.isArray(p.choices))
8
+ return 'openai-chat';
9
+ if ((typeof p.object === 'string' && p.object === 'response') || Array.isArray(p.output))
10
+ return 'openai-responses';
11
+ if (Array.isArray(p.content) || typeof p.stop_reason === 'string')
12
+ return 'anthropic-messages';
13
+ if (Array.isArray(p.candidates))
14
+ return 'gemini-chat';
15
+ return 'unknown';
16
+ }
17
+ export async function runRespProcessStage3ServerToolOrchestration(options) {
18
+ const hasServerToolSupport = Boolean(options.providerInvoker) || Boolean(options.reenterPipeline);
19
+ if (!hasServerToolSupport) {
20
+ recordStage(options.stageRecorder, 'chat_process.resp.stage5.servertool_orchestration', {
21
+ executed: false,
22
+ skipReason: 'no_servertool_support',
23
+ inputShape: detectProviderResponseShape(options.payload)
24
+ });
25
+ return {
26
+ payload: options.payload,
27
+ executed: false,
28
+ skipReason: 'no_servertool_support'
29
+ };
30
+ }
31
+ if (!options.reenterPipeline) {
32
+ recordStage(options.stageRecorder, 'chat_process.resp.stage5.servertool_orchestration', {
33
+ executed: false,
34
+ skipReason: 'missing_reenter_pipeline',
35
+ inputShape: detectProviderResponseShape(options.payload)
36
+ });
37
+ return {
38
+ payload: options.payload,
39
+ executed: false,
40
+ skipReason: 'missing_reenter_pipeline'
41
+ };
42
+ }
43
+ const orchestration = await runServerToolOrchestration({
44
+ chat: options.payload,
45
+ adapterContext: options.adapterContext,
46
+ requestId: options.requestId,
47
+ entryEndpoint: options.entryEndpoint,
48
+ providerProtocol: options.providerProtocol,
49
+ stageRecorder: options.stageRecorder,
50
+ providerInvoker: options.providerInvoker,
51
+ reenterPipeline: options.reenterPipeline
52
+ });
53
+ if (orchestration.executed) {
54
+ const outputPayload = orchestration.chat;
55
+ recordStage(options.stageRecorder, 'chat_process.resp.stage5.servertool_orchestration', {
56
+ executed: true,
57
+ flowId: orchestration.flowId,
58
+ inputShape: detectProviderResponseShape(options.payload),
59
+ outputShape: detectProviderResponseShape(outputPayload)
60
+ });
61
+ return {
62
+ payload: outputPayload,
63
+ executed: true,
64
+ flowId: orchestration.flowId
65
+ };
66
+ }
67
+ recordStage(options.stageRecorder, 'chat_process.resp.stage5.servertool_orchestration', {
68
+ executed: false,
69
+ inputShape: detectProviderResponseShape(options.payload)
70
+ });
71
+ return {
72
+ payload: options.payload,
73
+ executed: false
74
+ };
75
+ }