@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
@@ -2,6 +2,8 @@ import { registerServerToolHandler } from '../registry.js';
2
2
  import { cloneJson } from '../server-side-tools.js';
3
3
  import { loadRoutingInstructionStateSync, saveRoutingInstructionStateAsync } from '../../router/virtual-router/sticky-session-store.js';
4
4
  import { isCompactionRequest } from './compaction-detect.js';
5
+ import { buildResponsesRequestFromChat, captureResponsesContext, buildChatRequestFromResponses } from '../../conversion/responses/responses-openai-bridge.js';
6
+ import { buildAnthropicRequestFromOpenAIChat } from '../../conversion/codecs/anthropic-openai-codec.js';
5
7
  const STOPMESSAGE_DEBUG = (process.env.ROUTECODEX_STOPMESSAGE_DEBUG || '').trim() === '1';
6
8
  function debugLog(message, extra) {
7
9
  if (!STOPMESSAGE_DEBUG) {
@@ -113,17 +115,13 @@ const handler = async (ctx) => {
113
115
  return null;
114
116
  }
115
117
  const entryEndpoint = resolveEntryEndpoint(record);
116
- const followupPayload = buildStopMessageFollowupPayload(captured, text, ctx.base);
118
+ const followupPayload = buildStopMessageFollowupPayload(captured, text, ctx.base, entryEndpoint);
117
119
  if (!followupPayload) {
118
120
  debugLog('skip_failed_build_followup', {
119
121
  stickyKey
120
122
  });
121
123
  return null;
122
124
  }
123
- const nextUsed = used + 1;
124
- state.stopMessageUsed = nextUsed;
125
- state.stopMessageLastUsedAt = Date.now();
126
- saveRoutingInstructionStateAsync(stickyKey, state);
127
125
  const followupMetadata = {
128
126
  serverToolFollowup: true,
129
127
  stream: false,
@@ -132,9 +130,6 @@ const handler = async (ctx) => {
132
130
  serverToolOriginalEntryEndpoint: entryEndpoint,
133
131
  ...(connectionState ? { clientConnectionState: connectionState } : {})
134
132
  };
135
- if (shouldForceChatProtocol(entryEndpoint)) {
136
- followupMetadata.serverToolFollowupProtocol = 'openai-chat';
137
- }
138
133
  return {
139
134
  chatResponse: ctx.base,
140
135
  execution: {
@@ -168,38 +163,53 @@ function isStopFinishReason(base) {
168
163
  }
169
164
  const payload = base;
170
165
  const choicesRaw = payload.choices;
171
- if (!Array.isArray(choicesRaw) || !choicesRaw.length) {
172
- return false;
173
- }
174
- const first = choicesRaw[0];
175
- if (!first || typeof first !== 'object' || Array.isArray(first)) {
176
- return false;
166
+ if (Array.isArray(choicesRaw) && choicesRaw.length) {
167
+ const first = choicesRaw[0];
168
+ if (!first || typeof first !== 'object' || Array.isArray(first)) {
169
+ return false;
170
+ }
171
+ const finishReasonRaw = first.finish_reason;
172
+ const finishReason = typeof finishReasonRaw === 'string' && finishReasonRaw.trim()
173
+ ? finishReasonRaw.trim().toLowerCase()
174
+ : '';
175
+ // 将模型视为“自然结束”的场景:
176
+ // - OpenAI 兼容:finish_reason === 'stop'
177
+ // - 截断场景:finish_reason === 'length'(例如 Gemini/Claude 流式输出被 max token 截断)
178
+ // 统一视作可触发 stopMessage 的终止点;仅排除显式的 tool_calls。
179
+ if (!finishReason || finishReason === 'tool_calls') {
180
+ return false;
181
+ }
182
+ if (finishReason !== 'stop' && finishReason !== 'length') {
183
+ return false;
184
+ }
185
+ const message = first.message &&
186
+ typeof first.message === 'object' &&
187
+ !Array.isArray(first.message)
188
+ ? first.message
189
+ : null;
190
+ if (!message) {
191
+ return false;
192
+ }
193
+ const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
194
+ if (toolCalls.length > 0) {
195
+ // 如果当前响应仍在发起工具调用,则由工具执行驱动后续轮次,不触发 stopMessage。
196
+ return false;
197
+ }
198
+ return true;
177
199
  }
178
- const finishReasonRaw = first.finish_reason;
179
- const finishReason = typeof finishReasonRaw === 'string' && finishReasonRaw.trim()
180
- ? finishReasonRaw.trim().toLowerCase()
181
- : '';
182
- // 将模型视为“自然结束”的场景:
183
- // - OpenAI 兼容:finish_reason === 'stop'
184
- // - 截断场景:finish_reason === 'length'(例如 Gemini/Claude 流式输出被 max token 截断)
185
- // 统一视作可触发 stopMessage 的终止点;仅排除显式的 tool_calls。
186
- if (!finishReason || finishReason === 'tool_calls') {
200
+ // OpenAI Responses shape: status completed + no required_action + no tool-like output entries
201
+ const statusRaw = typeof payload.status === 'string' ? payload.status.trim().toLowerCase() : '';
202
+ if (statusRaw && statusRaw !== 'completed') {
187
203
  return false;
188
204
  }
189
- if (finishReason !== 'stop' && finishReason !== 'length') {
205
+ if (payload.required_action && typeof payload.required_action === 'object') {
190
206
  return false;
191
207
  }
192
- const message = first.message &&
193
- typeof first.message === 'object' &&
194
- !Array.isArray(first.message)
195
- ? first.message
196
- : null;
197
- if (!message) {
208
+ const outputRaw = Array.isArray(payload.output) ? payload.output : [];
209
+ if (!outputRaw.length) {
198
210
  return false;
199
211
  }
200
- const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
201
- if (toolCalls.length > 0) {
202
- // 如果当前响应仍在发起工具调用,则由工具执行驱动后续轮次,不触发 stopMessage。
212
+ if (outputRaw.some((item) => hasToolLikeOutput(item))) {
203
213
  return false;
204
214
  }
205
215
  return true;
@@ -214,49 +224,211 @@ function getCapturedRequest(adapterContext) {
214
224
  }
215
225
  return captured;
216
226
  }
217
- function buildStopMessageFollowupPayload(source, text, baseResponse) {
218
- if (!source || typeof source !== 'object') {
219
- return null;
220
- }
221
- const payload = {};
222
- if (typeof source.model === 'string' && source.model.trim()) {
223
- payload.model = source.model.trim();
227
+ function extractChatSeedFromCapturedRequest(source) {
228
+ const model = typeof source.model === 'string' && source.model.trim()
229
+ ? source.model.trim()
230
+ : undefined;
231
+ const rawMessages = Array.isArray(source.messages)
232
+ ? source.messages
233
+ : null;
234
+ if (rawMessages) {
235
+ const tools = Array.isArray(source.tools)
236
+ ? cloneJson(source.tools)
237
+ : undefined;
238
+ return {
239
+ ...(model ? { model } : {}),
240
+ messages: cloneJson(rawMessages),
241
+ ...(tools ? { tools } : {})
242
+ };
224
243
  }
225
- const rawMessages = source.messages;
226
- const messages = Array.isArray(rawMessages) ? cloneJson(rawMessages) : [];
227
- try {
228
- if (baseResponse && typeof baseResponse === 'object' && !Array.isArray(baseResponse)) {
229
- const base = baseResponse;
230
- const choices = Array.isArray(base.choices) ? base.choices : [];
231
- const primary = choices[0] && typeof choices[0] === 'object' ? choices[0] : null;
232
- const msg = primary &&
233
- primary.message &&
234
- typeof primary.message === 'object' &&
235
- !Array.isArray(primary.message)
236
- ? primary.message
237
- : null;
238
- if (msg) {
239
- messages.push(cloneJson(msg));
244
+ const rawInput = Array.isArray(source.input)
245
+ ? source.input
246
+ : null;
247
+ if (rawInput) {
248
+ try {
249
+ const ctx = captureResponsesContext(source);
250
+ if (!ctx.isResponsesPayload) {
251
+ return null;
240
252
  }
253
+ const rebuilt = buildChatRequestFromResponses(source, ctx).request;
254
+ const rebuiltModel = typeof rebuilt.model === 'string' && rebuilt.model.trim().length ? String(rebuilt.model).trim() : model;
255
+ const rebuiltMessages = Array.isArray(rebuilt.messages) ? rebuilt.messages : [];
256
+ const rebuiltTools = Array.isArray(rebuilt.tools) ? rebuilt.tools : undefined;
257
+ return {
258
+ ...(rebuiltModel ? { model: rebuiltModel } : {}),
259
+ messages: cloneJson(rebuiltMessages),
260
+ ...(rebuiltTools ? { tools: cloneJson(rebuiltTools) } : {})
261
+ };
262
+ }
263
+ catch {
264
+ return null;
241
265
  }
242
266
  }
243
- catch {
244
- // best-effort: 如果无法解析上一条响应,就只使用已捕获的请求消息
267
+ return null;
268
+ }
269
+ function buildStopMessageFollowupPayload(source, text, baseResponse, entryEndpoint) {
270
+ if (!source || typeof source !== 'object') {
271
+ return null;
245
272
  }
273
+ const chatSeed = extractChatSeedFromCapturedRequest(source);
274
+ const model = chatSeed?.model;
275
+ const originalMessages = chatSeed?.messages ? cloneJson(chatSeed.messages) : [];
276
+ const originalTools = chatSeed?.tools ? cloneJson(chatSeed.tools) : undefined;
277
+ const originalParameters = (() => {
278
+ const direct = source.parameters;
279
+ if (direct && typeof direct === 'object' && !Array.isArray(direct)) {
280
+ return cloneJson(direct);
281
+ }
282
+ // Backward/compat: captured request might be a raw `/v1/responses` payload with
283
+ // top-level parameters (max_output_tokens, temperature, ...), not nested under `parameters`.
284
+ const record = source;
285
+ const allowed = new Set([
286
+ 'temperature',
287
+ 'top_p',
288
+ 'max_output_tokens',
289
+ 'seed',
290
+ 'logit_bias',
291
+ 'user',
292
+ 'parallel_tool_calls',
293
+ 'tool_choice',
294
+ 'response_format',
295
+ 'stream'
296
+ ]);
297
+ const out = {};
298
+ if (record.max_output_tokens === undefined && record.max_tokens !== undefined) {
299
+ out.max_output_tokens = record.max_tokens;
300
+ }
301
+ for (const key of Object.keys(record)) {
302
+ if (!allowed.has(key))
303
+ continue;
304
+ out[key] = record[key];
305
+ }
306
+ return Object.keys(out).length ? cloneJson(out) : undefined;
307
+ })();
308
+ const parametersForFollowup = originalParameters
309
+ ? (() => {
310
+ const cloned = cloneJson(originalParameters);
311
+ // Followup requests are always non-streaming (servertool orchestration enforces this),
312
+ // so remove any inherited stream hints to avoid conflicting flags.
313
+ delete cloned.stream;
314
+ return cloned;
315
+ })()
316
+ : undefined;
317
+ const assistantMessage = extractAssistantMessage(baseResponse);
318
+ const messages = assistantMessage
319
+ ? [...originalMessages, assistantMessage]
320
+ : [...originalMessages];
246
321
  messages.push({
247
322
  role: 'user',
248
323
  content: text
249
324
  });
250
- payload.messages = messages;
251
- if (Array.isArray(source.tools) && source.tools.length) {
252
- payload.tools = cloneJson(source.tools);
325
+ // Build canonical OpenAI Chat payload first (deep cloned).
326
+ const chatPayload = {
327
+ ...(model ? { model } : {}),
328
+ messages,
329
+ ...(originalTools ? { tools: originalTools } : {})
330
+ };
331
+ const normalizedEntry = typeof entryEndpoint === 'string' ? entryEndpoint.trim().toLowerCase() : '';
332
+ if (normalizedEntry.includes('/v1/responses')) {
333
+ return buildResponsesRequestFromChat(chatPayload, {
334
+ stream: false,
335
+ ...(parametersForFollowup ? { parameters: parametersForFollowup } : {})
336
+ }).request;
337
+ }
338
+ if (normalizedEntry.includes('/v1/messages')) {
339
+ const anthropicChatPayload = {
340
+ ...chatPayload,
341
+ ...(parametersForFollowup ? parametersForFollowup : {})
342
+ };
343
+ return buildAnthropicRequestFromOpenAIChat(anthropicChatPayload);
344
+ }
345
+ const openaiChatPayload = {
346
+ ...chatPayload,
347
+ ...(parametersForFollowup ? parametersForFollowup : {})
348
+ };
349
+ return openaiChatPayload;
350
+ }
351
+ function extractAssistantMessage(baseResponse) {
352
+ if (!baseResponse || typeof baseResponse !== 'object' || Array.isArray(baseResponse)) {
353
+ return null;
354
+ }
355
+ const base = baseResponse;
356
+ const choices = Array.isArray(base.choices) ? base.choices : [];
357
+ if (choices.length > 0) {
358
+ const first = choices[0] && typeof choices[0] === 'object' && !Array.isArray(choices[0])
359
+ ? choices[0]
360
+ : null;
361
+ const msg = first &&
362
+ first.message &&
363
+ typeof first.message === 'object' &&
364
+ !Array.isArray(first.message)
365
+ ? first.message
366
+ : null;
367
+ if (msg) {
368
+ return cloneJson(msg);
369
+ }
370
+ }
371
+ const outputText = extractResponsesOutputText(base);
372
+ if (outputText.length > 0) {
373
+ return {
374
+ role: 'assistant',
375
+ content: outputText
376
+ };
377
+ }
378
+ return null;
379
+ }
380
+ function extractResponsesOutputText(base) {
381
+ const raw = base.output_text;
382
+ if (typeof raw === 'string') {
383
+ return raw.trim();
384
+ }
385
+ if (Array.isArray(raw)) {
386
+ const texts = raw
387
+ .map((entry) => (typeof entry === 'string' ? entry : ''))
388
+ .filter((entry) => entry.trim().length > 0);
389
+ if (texts.length > 0) {
390
+ return texts.join('\n').trim();
391
+ }
253
392
  }
254
- const parameters = source.parameters;
255
- if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
256
- const params = cloneJson(parameters);
257
- Object.assign(payload, params);
393
+ const output = Array.isArray(base.output) ? (base.output) : [];
394
+ const chunks = [];
395
+ for (const item of output) {
396
+ if (!item || typeof item !== 'object' || Array.isArray(item))
397
+ continue;
398
+ if (typeof item.type !== 'string')
399
+ continue;
400
+ const type = String(item.type).trim().toLowerCase();
401
+ if (type !== 'message')
402
+ continue;
403
+ const content = Array.isArray(item.content) ? (item.content) : [];
404
+ for (const part of content) {
405
+ if (!part || typeof part !== 'object' || Array.isArray(part))
406
+ continue;
407
+ const pType = typeof part.type === 'string'
408
+ ? String(part.type).trim().toLowerCase()
409
+ : '';
410
+ if (pType === 'output_text') {
411
+ const text = typeof part.text === 'string' ? String(part.text) : '';
412
+ if (text.trim().length)
413
+ chunks.push(text.trim());
414
+ }
415
+ }
258
416
  }
259
- return payload;
417
+ return chunks.join('\n').trim();
418
+ }
419
+ function hasToolLikeOutput(value) {
420
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
421
+ return false;
422
+ }
423
+ const typeRaw = value.type;
424
+ const type = typeof typeRaw === 'string' ? typeRaw.trim().toLowerCase() : '';
425
+ if (!type) {
426
+ return false;
427
+ }
428
+ return (type === 'tool_call' ||
429
+ type === 'tool_use' ||
430
+ type === 'function_call' ||
431
+ type.includes('tool'));
260
432
  }
261
433
  function resolveClientConnectionState(value) {
262
434
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
@@ -372,13 +544,3 @@ function resolveEntryEndpoint(record) {
372
544
  }
373
545
  return '/v1/chat/completions';
374
546
  }
375
- function shouldForceChatProtocol(entryEndpoint) {
376
- if (typeof entryEndpoint !== 'string') {
377
- return false;
378
- }
379
- const normalized = entryEndpoint.trim().toLowerCase();
380
- if (!normalized) {
381
- return false;
382
- }
383
- return !normalized.includes('/v1/chat/completions');
384
- }
@@ -1,5 +1,6 @@
1
1
  import { registerServerToolHandler } from '../registry.js';
2
2
  import { cloneJson, extractTextFromChatLike } from '../server-side-tools.js';
3
+ import { buildEntryAwareFollowupPayload, dropToolByFunctionName, extractCapturedChatSeed } from './followup-request-builder.js';
3
4
  const FLOW_ID = 'vision_flow';
4
5
  const handler = async (ctx) => {
5
6
  if (!ctx.options.reenterPipeline) {
@@ -36,7 +37,7 @@ const handler = async (ctx) => {
36
37
  if (!visionSummary) {
37
38
  return null;
38
39
  }
39
- const followupPayload = buildVisionFollowupPayload(captured, visionSummary);
40
+ const followupPayload = buildVisionFollowupPayload(captured, visionSummary, ctx.options.entryEndpoint || ctx.adapterContext?.entryEndpoint || '/v1/chat/completions');
40
41
  if (!followupPayload) {
41
42
  return null;
42
43
  }
@@ -44,8 +45,7 @@ const handler = async (ctx) => {
44
45
  flowId: FLOW_ID,
45
46
  followup: {
46
47
  requestIdSuffix: ':vision_followup',
47
- payload: followupPayload,
48
- metadata: buildFollowupMetadata(ctx.adapterContext, 'vision')
48
+ payload: followupPayload
49
49
  }
50
50
  };
51
51
  return {
@@ -194,24 +194,20 @@ function buildVisionUserMessage(source) {
194
194
  }
195
195
  return message;
196
196
  }
197
- function buildVisionFollowupPayload(source, summary) {
198
- if (!source || typeof source !== 'object') {
197
+ function buildVisionFollowupPayload(source, summary, entryEndpoint) {
198
+ const seed = extractCapturedChatSeed(source);
199
+ if (!seed) {
199
200
  return null;
200
201
  }
201
- const payload = {};
202
- if (typeof source.model === 'string' && source.model.trim()) {
203
- payload.model = source.model.trim();
204
- }
205
- payload.messages = injectVisionSummary(source.messages, summary);
206
- if (Array.isArray(source.tools) && source.tools.length) {
207
- payload.tools = cloneJson(source.tools);
208
- }
209
- const parameters = source.parameters;
210
- if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
211
- const params = cloneJson(parameters);
212
- Object.assign(payload, params);
213
- }
214
- return payload;
202
+ const messages = injectVisionSummary(seed.messages, summary);
203
+ const filteredTools = dropToolByFunctionName(seed.tools, 'vision');
204
+ return buildEntryAwareFollowupPayload({
205
+ entryEndpoint,
206
+ model: seed.model,
207
+ messages,
208
+ ...(filteredTools ? { tools: filteredTools } : {}),
209
+ ...(seed.parameters ? { parameters: seed.parameters } : {})
210
+ });
215
211
  }
216
212
  function injectVisionSummary(source, summary) {
217
213
  const messages = Array.isArray(source) ? cloneJson(source) : [];
@@ -282,11 +278,3 @@ function injectVisionSummary(source, summary) {
282
278
  }
283
279
  return messages;
284
280
  }
285
- function buildFollowupMetadata(adapterContext, toolName) {
286
- const ctx = adapterContext && typeof adapterContext === 'object' ? adapterContext : null;
287
- const routeId = ctx && typeof ctx.routeId === 'string' && ctx.routeId.trim() ? ctx.routeId.trim() : '';
288
- if (!routeId || routeId.toLowerCase() === toolName.toLowerCase()) {
289
- return undefined;
290
- }
291
- return { routeHint: routeId };
292
- }
@@ -1,6 +1,7 @@
1
1
  import { buildOpenAIChatFromGeminiResponse } from '../../conversion/codecs/gemini-openai-codec.js';
2
2
  import { registerServerToolHandler } from '../registry.js';
3
3
  import { cloneJson, extractTextFromChatLike } from '../server-side-tools.js';
4
+ import { buildEntryAwareFollowupPayload, dropToolByFunctionName, extractCapturedChatSeed } from './followup-request-builder.js';
4
5
  const FLOW_ID = 'web_search_flow';
5
6
  const handler = async (ctx) => {
6
7
  const toolCall = ctx.toolCall;
@@ -53,14 +54,17 @@ const handler = async (ctx) => {
53
54
  chosenResult = lastFailure.result;
54
55
  }
55
56
  const patched = injectWebSearchToolResult(ctx.base, toolCall, chosenEngine, query, chosenResult);
56
- const followupPayload = buildWebSearchFollowupPayload(ctx.adapterContext, patched);
57
+ const followupPayload = buildWebSearchFollowupPayload(ctx.adapterContext, patched, ctx.options.entryEndpoint || ctx.adapterContext?.entryEndpoint || '/v1/chat/completions');
57
58
  const execution = {
58
59
  flowId: FLOW_ID,
59
60
  followup: followupPayload
60
61
  ? {
61
62
  requestIdSuffix: ':web_search_followup',
62
63
  payload: followupPayload,
63
- metadata: buildFollowupMetadata(ctx.adapterContext, 'web_search')
64
+ metadata: {
65
+ // keep minimal; servertool engine will inject the standard followup metadata defaults
66
+ serverToolFollowup: true
67
+ }
64
68
  }
65
69
  : undefined,
66
70
  context: {
@@ -565,19 +569,14 @@ function injectWebSearchToolResult(base, toolCall, engine, query, backendResult)
565
569
  ];
566
570
  return cloned;
567
571
  }
568
- function buildWebSearchFollowupPayload(adapterContext, chatResponse) {
572
+ function buildWebSearchFollowupPayload(adapterContext, chatResponse, entryEndpoint) {
569
573
  const captured = adapterContext && typeof adapterContext === 'object'
570
574
  ? adapterContext.capturedChatRequest
571
575
  : undefined;
572
- if (!captured || typeof captured !== 'object') {
576
+ const seed = extractCapturedChatSeed(captured);
577
+ if (!seed) {
573
578
  return null;
574
579
  }
575
- const originalMessages = Array.isArray(captured.messages)
576
- ? cloneJson(captured.messages)
577
- : [];
578
- const originalTools = Array.isArray(captured.tools)
579
- ? cloneJson(captured.tools)
580
- : [];
581
580
  const assistantMessage = extractAssistantMessage(chatResponse);
582
581
  if (!assistantMessage) {
583
582
  return null;
@@ -586,32 +585,15 @@ function buildWebSearchFollowupPayload(adapterContext, chatResponse) {
586
585
  if (!toolMessages.length) {
587
586
  return null;
588
587
  }
589
- const reconstructed = [...originalMessages, assistantMessage, ...toolMessages];
590
- const filteredTools = originalTools.filter((tool) => {
591
- if (!tool || typeof tool !== 'object')
592
- return false;
593
- const fn = tool.function;
594
- const name = fn && typeof fn.name === 'string'
595
- ? fn.name
596
- : '';
597
- if (!name)
598
- return true;
599
- return name !== 'web_search';
588
+ const reconstructed = [...seed.messages, assistantMessage, ...toolMessages];
589
+ const filteredTools = dropToolByFunctionName(seed.tools, 'web_search');
590
+ return buildEntryAwareFollowupPayload({
591
+ entryEndpoint,
592
+ model: seed.model,
593
+ messages: reconstructed,
594
+ ...(filteredTools ? { tools: filteredTools } : {}),
595
+ ...(seed.parameters ? { parameters: seed.parameters } : {})
600
596
  });
601
- const payload = {
602
- messages: reconstructed
603
- };
604
- if (typeof captured.model === 'string' && captured.model.trim()) {
605
- payload.model = captured.model.trim();
606
- }
607
- if (filteredTools.length) {
608
- payload.tools = filteredTools;
609
- }
610
- const parameters = captured.parameters;
611
- if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
612
- Object.assign(payload, cloneJson(parameters));
613
- }
614
- return payload;
615
597
  }
616
598
  function extractAssistantMessage(chatResponse) {
617
599
  const choices = Array.isArray(chatResponse.choices)
@@ -680,14 +662,6 @@ function logServerToolWebSearch(engine, requestId, query) {
680
662
  // eslint-disable-next-line no-console
681
663
  console.log(`\x1b[31m${vrLine}\x1b[0m`);
682
664
  }
683
- function buildFollowupMetadata(adapterContext, toolName) {
684
- const ctx = adapterContext && typeof adapterContext === 'object' ? adapterContext : null;
685
- const routeId = ctx && typeof ctx.routeId === 'string' && ctx.routeId.trim() ? ctx.routeId.trim() : '';
686
- if (!routeId || routeId.toLowerCase() === toolName.toLowerCase()) {
687
- return undefined;
688
- }
689
- return { routeHint: routeId };
690
- }
691
665
  function findWebSearchArray(payload) {
692
666
  let current = payload;
693
667
  const visited = new Set();
@@ -3,7 +3,10 @@ import type { ServerSideToolEngineOptions, ServerSideToolEngineResult, ToolCall
3
3
  import './handlers/web-search.js';
4
4
  import './handlers/vision.js';
5
5
  import './handlers/iflow-model-error-retry.js';
6
+ import './handlers/gemini-empty-reply-continue.js';
6
7
  import './handlers/stop-message-auto.js';
8
+ import './handlers/exec-command-guard.js';
9
+ import './handlers/apply-patch-guard.js';
7
10
  export declare function runServerSideToolEngine(options: ServerSideToolEngineOptions): Promise<ServerSideToolEngineResult>;
8
11
  export declare function extractToolCalls(chatResponse: JsonObject): ToolCall[];
9
12
  export declare function cloneJson<T>(value: T): T;
@@ -3,7 +3,10 @@ import { ProviderProtocolError } from '../conversion/shared/errors.js';
3
3
  import './handlers/web-search.js';
4
4
  import './handlers/vision.js';
5
5
  import './handlers/iflow-model-error-retry.js';
6
+ import './handlers/gemini-empty-reply-continue.js';
6
7
  import './handlers/stop-message-auto.js';
8
+ import './handlers/exec-command-guard.js';
9
+ import './handlers/apply-patch-guard.js';
7
10
  export async function runServerSideToolEngine(options) {
8
11
  const base = asObject(options.chatResponse);
9
12
  if (!base) {
@@ -1,5 +1,6 @@
1
1
  import { PassThrough } from 'node:stream';
2
- import { AnthropicMessageResponse, AnthropicJsonToSseOptions, DEFAULT_ANTHROPIC_CONVERSION_CONFIG } from '../types/index.js';
2
+ import { DEFAULT_ANTHROPIC_CONVERSION_CONFIG } from '../types/index.js';
3
+ import type { AnthropicMessageResponse, AnthropicJsonToSseOptions } from '../types/index.js';
3
4
  export declare class AnthropicJsonToSseConverter {
4
5
  private config;
5
6
  private contexts;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Chat JSON → SSE转换器(重构版本)
3
+ * 使用函数化架构:事件生成 + 序列化 + 写入分离
4
+ */
5
+ import { DEFAULT_CHAT_CONVERSION_CONFIG } from '../types/index.js';
6
+ import type { ChatCompletionRequest, ChatCompletionResponse, ChatJsonToSseContext, ChatJsonToSseOptions, ChatSseEventStream } from '../types/index.js';
7
+ /**
8
+ * 重构后的Chat JSON到SSE转换器
9
+ * 采用函数化架构,专注于编排而非具体业务逻辑
10
+ */
11
+ export declare class ChatJsonToSseConverterRefactored {
12
+ private config;
13
+ private contexts;
14
+ constructor(config?: Partial<typeof DEFAULT_CHAT_CONVERSION_CONFIG>);
15
+ /**
16
+ * 将Chat Completion请求转换为SSE流
17
+ */
18
+ convertRequestToJsonToSse(request: ChatCompletionRequest, options: ChatJsonToSseOptions): Promise<ChatSseEventStream>;
19
+ /**
20
+ * 将Chat Completion响应转换为SSE流
21
+ */
22
+ convertResponseToJsonToSse(response: ChatCompletionResponse, options: ChatJsonToSseOptions): Promise<ChatSseEventStream>;
23
+ /**
24
+ * 使用函数化架构处理请求转换
25
+ */
26
+ private processRequestToSseWithFunctions;
27
+ /**
28
+ * 使用函数化架构处理响应转换
29
+ */
30
+ private processResponseToSseWithFunctions;
31
+ /**
32
+ * 验证请求格式
33
+ */
34
+ private validateRequest;
35
+ /**
36
+ * 验证响应格式
37
+ */
38
+ private validateResponse;
39
+ /**
40
+ * 更新统计信息
41
+ */
42
+ private updateStats;
43
+ /**
44
+ * 包装错误
45
+ */
46
+ private wrapError;
47
+ /**
48
+ * 处理流错误
49
+ */
50
+ private handleStreamError;
51
+ /**
52
+ * 完成流
53
+ */
54
+ private completeStream;
55
+ /**
56
+ * 中止流
57
+ */
58
+ private abortStream;
59
+ /**
60
+ * 创建请求上下文
61
+ */
62
+ private createRequestContext;
63
+ /**
64
+ * 创建响应上下文
65
+ */
66
+ private createResponseContext;
67
+ /**
68
+ * 获取上下文
69
+ */
70
+ getContext(requestId: string): ChatJsonToSseContext | undefined;
71
+ /**
72
+ * 清理上下文
73
+ */
74
+ clearContext(requestId: string): void;
75
+ /**
76
+ * 获取所有活跃的上下文
77
+ */
78
+ getActiveContexts(): Map<string, ChatJsonToSseContext>;
79
+ }
80
+ export declare const ChatJsonToSseConverter: typeof ChatJsonToSseConverterRefactored;