@jsonstudio/llms 0.6.802 → 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 (186) 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 +58 -3
  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 +98 -11
  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 +112 -18
  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/pipeline/context-limit.d.ts +0 -13
  152. package/dist/conversion/hub/pipeline/context-limit.js +0 -55
  153. package/dist/conversion/hub/response/server-side-tools.d.ts +0 -26
  154. package/dist/conversion/hub/response/server-side-tools.js +0 -383
  155. package/dist/conversion/shared/bridge-conversation-store.d.ts +0 -41
  156. package/dist/conversion/shared/bridge-conversation-store.js +0 -279
  157. package/dist/conversion/shared/bridge-request-adapter.d.ts +0 -28
  158. package/dist/conversion/shared/bridge-request-adapter.js +0 -430
  159. package/dist/conversion/shared/responses-id-utils.js +0 -42
  160. package/dist/conversion/shared/responses-instructions.js +0 -113
  161. package/dist/conversion/shared/responses-message-utils.d.ts +0 -15
  162. package/dist/conversion/shared/responses-message-utils.js +0 -206
  163. package/dist/conversion/shared/responses-metadata.js +0 -1
  164. package/dist/conversion/shared/responses-output-utils.d.ts +0 -7
  165. package/dist/conversion/shared/responses-output-utils.js +0 -108
  166. package/dist/conversion/shared/responses-types.d.ts +0 -33
  167. package/dist/conversion/shared/tool-normalizers.d.ts +0 -4
  168. package/dist/conversion/shared/tool-normalizers.js +0 -84
  169. package/dist/filters/special/request-streaming-to-nonstreaming.d.ts +0 -13
  170. package/dist/filters/special/request-streaming-to-nonstreaming.js +0 -39
  171. package/dist/filters/special/response-apply-patch-toon-decode.d.ts +0 -23
  172. package/dist/filters/special/response-apply-patch-toon-decode.js +0 -460
  173. package/dist/filters/special/response-tool-arguments-toon-decode.d.ts +0 -10
  174. package/dist/filters/special/response-tool-arguments-toon-decode.js +0 -154
  175. package/dist/servertool/flow-types.d.ts +0 -40
  176. package/dist/servertool/flow-types.js +0 -1
  177. package/dist/servertool/orchestration-types.d.ts +0 -33
  178. package/dist/servertool/orchestration-types.js +0 -1
  179. package/dist/servertool/vision-tool.d.ts +0 -2
  180. package/dist/servertool/vision-tool.js +0 -185
  181. package/dist/tools/patch-args-normalizer.d.ts +0 -15
  182. package/dist/tools/patch-args-normalizer.js +0 -472
  183. package/dist/utils/toon.d.ts +0 -4
  184. package/dist/utils/toon.js +0 -75
  185. /package/dist/{conversion/compat/actions/apply-patch-format-fixer.d.ts → servertool/handlers/apply-patch-guard.d.ts} +0 -0
  186. /package/dist/{conversion/shared/responses-types.js → servertool/handlers/exec-command-guard.d.ts} +0 -0
@@ -1,6 +1,7 @@
1
1
  import { registerServerToolHandler } from '../registry.js';
2
2
  import { cloneJson } from '../server-side-tools.js';
3
3
  import { isCompactionRequest } from './compaction-detect.js';
4
+ import { buildResponsesRequestFromChat, captureResponsesContext, buildChatRequestFromResponses } from '../../conversion/responses/responses-openai-bridge.js';
4
5
  const FLOW_ID = 'gemini_empty_reply_continue';
5
6
  const handler = async (ctx) => {
6
7
  if (!ctx.options.reenterPipeline) {
@@ -31,41 +32,12 @@ const handler = async (ctx) => {
31
32
  if (!isAntigravity && !isGeminiCli) {
32
33
  return null;
33
34
  }
34
- // 仅在 finish_reason=stop 且第一条消息内容为空、无 tool_calls 时触发。
35
+ // 支持两种客户端协议形状:
36
+ // - OpenAI Chat: choices[0].message.content
37
+ // - OpenAI Responses: output/output_text/status
35
38
  const base = ctx.base;
36
- const choices = Array.isArray(base.choices) ? base.choices : [];
37
- if (!choices.length) {
38
- return null;
39
- }
40
- const firstRaw = choices[0];
41
- if (!firstRaw || typeof firstRaw !== 'object') {
42
- return null;
43
- }
44
- const first = firstRaw;
45
- const finishReasonRaw = typeof first.finish_reason === 'string' && first.finish_reason.trim()
46
- ? first.finish_reason.trim()
47
- : '';
48
- const finishReason = finishReasonRaw.toLowerCase();
49
- const isStop = finishReason === 'stop';
50
- const isMaxTokens = finishReason === 'length'; // 映射自 Gemini 的 MAX_TOKENS
51
- if (!isStop && !isMaxTokens) {
52
- return null;
53
- }
54
- const message = first.message && typeof first.message === 'object' && !Array.isArray(first.message)
55
- ? first.message
56
- : null;
57
- if (!message) {
58
- return null;
59
- }
60
- const contentRaw = message.content;
61
- const contentText = typeof contentRaw === 'string' ? contentRaw.trim() : '';
62
- // 对于 finish_reason=stop,仅在真正“空回复”时触发;
63
- // 对于 finish_reason=length(MAX_TOKENS 截断),允许已有内容,视为需要自动续写。
64
- if (isStop && contentText.length > 0) {
65
- return null;
66
- }
67
- const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
68
- if (toolCalls.length > 0) {
39
+ const emptyDecision = decideEmptyReply(base);
40
+ if (!emptyDecision.shouldTrigger) {
69
41
  return null;
70
42
  }
71
43
  // 统计连续空回复次数,超过上限后不再自动续写,而是返回一个可重试错误。
@@ -100,7 +72,8 @@ const handler = async (ctx) => {
100
72
  }
101
73
  };
102
74
  }
103
- const followupPayload = buildContinueFollowupPayload(captured);
75
+ const assistantMessage = extractAssistantMessage(ctx.base);
76
+ const followupPayload = buildContinueFollowupPayload(captured, assistantMessage);
104
77
  if (!followupPayload) {
105
78
  return null;
106
79
  }
@@ -114,6 +87,9 @@ const handler = async (ctx) => {
114
87
  metadata: {
115
88
  serverToolFollowup: true,
116
89
  stream: false,
90
+ preserveRouteHint: false,
91
+ disableStickyRoutes: true,
92
+ serverToolOriginalEntryEndpoint: ctx.options.entryEndpoint,
117
93
  geminiEmptyReplyCount: nextCount
118
94
  }
119
95
  }
@@ -121,6 +97,115 @@ const handler = async (ctx) => {
121
97
  };
122
98
  };
123
99
  registerServerToolHandler('gemini_empty_reply_continue', handler, { trigger: 'auto' });
100
+ function decideEmptyReply(base) {
101
+ // 1) OpenAI Chat shape
102
+ const choices = Array.isArray(base.choices) ? base.choices : [];
103
+ if (choices.length > 0) {
104
+ const firstRaw = choices[0];
105
+ if (!firstRaw || typeof firstRaw !== 'object') {
106
+ return { shouldTrigger: false };
107
+ }
108
+ const first = firstRaw;
109
+ const finishReasonRaw = typeof first.finish_reason === 'string' && first.finish_reason.trim()
110
+ ? first.finish_reason.trim()
111
+ : '';
112
+ const finishReason = finishReasonRaw.toLowerCase();
113
+ const isStop = finishReason === 'stop';
114
+ const isMaxTokens = finishReason === 'length'; // 映射自 Gemini 的 MAX_TOKENS
115
+ if (!isStop && !isMaxTokens) {
116
+ return { shouldTrigger: false };
117
+ }
118
+ const message = first.message && typeof first.message === 'object' && !Array.isArray(first.message)
119
+ ? first.message
120
+ : null;
121
+ if (!message) {
122
+ return { shouldTrigger: false };
123
+ }
124
+ const contentRaw = message.content;
125
+ const contentText = typeof contentRaw === 'string' ? contentRaw.trim() : '';
126
+ // 对于 finish_reason=stop,仅在真正“空回复”时触发;
127
+ // 对于 finish_reason=length(MAX_TOKENS 截断),允许已有内容,视为需要自动续写。
128
+ if (isStop && contentText.length > 0) {
129
+ return { shouldTrigger: false };
130
+ }
131
+ const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
132
+ if (toolCalls.length > 0) {
133
+ return { shouldTrigger: false };
134
+ }
135
+ return { shouldTrigger: true };
136
+ }
137
+ // 2) OpenAI Responses shape
138
+ const statusRaw = typeof base.status === 'string' ? base.status.trim().toLowerCase() : '';
139
+ if (statusRaw && statusRaw !== 'completed') {
140
+ return { shouldTrigger: false };
141
+ }
142
+ if (base.required_action && typeof base.required_action === 'object') {
143
+ return { shouldTrigger: false };
144
+ }
145
+ const outputText = extractResponsesOutputText(base);
146
+ if (outputText.length > 0) {
147
+ return { shouldTrigger: false };
148
+ }
149
+ const outputRaw = Array.isArray(base.output) ? base.output : [];
150
+ if (outputRaw.some((item) => hasToolLikeOutput(item))) {
151
+ return { shouldTrigger: false };
152
+ }
153
+ // 允许 output 为空或仅包含空消息:视作空回复,触发自动续写。
154
+ return { shouldTrigger: true };
155
+ }
156
+ function extractResponsesOutputText(base) {
157
+ const raw = base.output_text;
158
+ if (typeof raw === 'string') {
159
+ return raw.trim();
160
+ }
161
+ if (Array.isArray(raw)) {
162
+ const texts = raw
163
+ .map((entry) => (typeof entry === 'string' ? entry : ''))
164
+ .filter((entry) => entry.trim().length > 0);
165
+ if (texts.length > 0) {
166
+ return texts.join('\n').trim();
167
+ }
168
+ }
169
+ const output = Array.isArray(base.output) ? (base.output) : [];
170
+ const chunks = [];
171
+ for (const item of output) {
172
+ if (!item || typeof item !== 'object' || Array.isArray(item))
173
+ continue;
174
+ if (typeof item.type !== 'string')
175
+ continue;
176
+ const type = String(item.type).trim().toLowerCase();
177
+ if (type !== 'message')
178
+ continue;
179
+ const content = Array.isArray(item.content) ? (item.content) : [];
180
+ for (const part of content) {
181
+ if (!part || typeof part !== 'object' || Array.isArray(part))
182
+ continue;
183
+ const pType = typeof part.type === 'string'
184
+ ? String(part.type).trim().toLowerCase()
185
+ : '';
186
+ if (pType === 'output_text') {
187
+ const text = typeof part.text === 'string' ? String(part.text) : '';
188
+ if (text.trim().length)
189
+ chunks.push(text.trim());
190
+ }
191
+ }
192
+ }
193
+ return chunks.join('\n').trim();
194
+ }
195
+ function hasToolLikeOutput(value) {
196
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
197
+ return false;
198
+ }
199
+ const typeRaw = value.type;
200
+ const type = typeof typeRaw === 'string' ? typeRaw.trim().toLowerCase() : '';
201
+ if (!type) {
202
+ return false;
203
+ }
204
+ return (type === 'tool_call' ||
205
+ type === 'tool_use' ||
206
+ type === 'function_call' ||
207
+ type.includes('tool'));
208
+ }
124
209
  function getCapturedRequest(adapterContext) {
125
210
  if (!adapterContext || typeof adapterContext !== 'object') {
126
211
  return null;
@@ -131,30 +216,111 @@ function getCapturedRequest(adapterContext) {
131
216
  }
132
217
  return captured;
133
218
  }
134
- function buildContinueFollowupPayload(source) {
219
+ function extractChatSeedFromCapturedRequest(source) {
220
+ const model = typeof source.model === 'string' && source.model.trim()
221
+ ? source.model.trim()
222
+ : undefined;
223
+ const rawMessages = Array.isArray(source.messages)
224
+ ? source.messages
225
+ : null;
226
+ if (rawMessages) {
227
+ const tools = Array.isArray(source.tools)
228
+ ? cloneJson(source.tools)
229
+ : undefined;
230
+ return {
231
+ ...(model ? { model } : {}),
232
+ messages: cloneJson(rawMessages),
233
+ ...(tools ? { tools } : {})
234
+ };
235
+ }
236
+ const rawInput = Array.isArray(source.input)
237
+ ? source.input
238
+ : null;
239
+ if (rawInput) {
240
+ try {
241
+ const ctx = captureResponsesContext(source);
242
+ if (!ctx.isResponsesPayload) {
243
+ return null;
244
+ }
245
+ const rebuilt = buildChatRequestFromResponses(source, ctx).request;
246
+ const rebuiltModel = typeof rebuilt.model === 'string' && rebuilt.model.trim().length ? String(rebuilt.model).trim() : model;
247
+ const rebuiltMessages = Array.isArray(rebuilt.messages) ? rebuilt.messages : [];
248
+ const rebuiltTools = Array.isArray(rebuilt.tools) ? rebuilt.tools : undefined;
249
+ return {
250
+ ...(rebuiltModel ? { model: rebuiltModel } : {}),
251
+ messages: cloneJson(rebuiltMessages),
252
+ ...(rebuiltTools ? { tools: cloneJson(rebuiltTools) } : {})
253
+ };
254
+ }
255
+ catch {
256
+ return null;
257
+ }
258
+ }
259
+ return null;
260
+ }
261
+ function buildContinueFollowupPayload(source, assistant) {
135
262
  if (!source || typeof source !== 'object') {
136
263
  return null;
137
264
  }
138
- const payload = {};
139
- if (typeof source.model === 'string' && source.model.trim()) {
140
- payload.model = source.model.trim();
265
+ const chatSeed = extractChatSeedFromCapturedRequest(source);
266
+ const model = chatSeed?.model;
267
+ const originalMessages = chatSeed?.messages ? cloneJson(chatSeed.messages) : [];
268
+ const originalTools = chatSeed?.tools ? cloneJson(chatSeed.tools) : undefined;
269
+ const originalParameters = (() => {
270
+ const direct = source.parameters;
271
+ if (direct && typeof direct === 'object' && !Array.isArray(direct)) {
272
+ return cloneJson(direct);
273
+ }
274
+ // Backward/compat: captured request might be a raw `/v1/responses` payload with
275
+ // top-level parameters (max_output_tokens, temperature, ...), not nested under `parameters`.
276
+ const record = source;
277
+ const allowed = new Set([
278
+ 'temperature',
279
+ 'top_p',
280
+ 'max_output_tokens',
281
+ 'seed',
282
+ 'logit_bias',
283
+ 'user',
284
+ 'parallel_tool_calls',
285
+ 'tool_choice',
286
+ 'response_format',
287
+ 'stream'
288
+ ]);
289
+ const out = {};
290
+ if (record.max_output_tokens === undefined && record.max_tokens !== undefined) {
291
+ out.max_output_tokens = record.max_tokens;
292
+ }
293
+ for (const key of Object.keys(record)) {
294
+ if (!allowed.has(key))
295
+ continue;
296
+ out[key] = record[key];
297
+ }
298
+ return Object.keys(out).length ? cloneJson(out) : undefined;
299
+ })();
300
+ const parametersForFollowup = originalParameters
301
+ ? (() => {
302
+ const cloned = cloneJson(originalParameters);
303
+ delete cloned.stream;
304
+ return cloned;
305
+ })()
306
+ : undefined;
307
+ const messages = [...originalMessages];
308
+ if (assistant && typeof assistant === 'object' && !Array.isArray(assistant)) {
309
+ messages.push(cloneJson(assistant));
141
310
  }
142
- const rawMessages = source.messages;
143
- const messages = Array.isArray(rawMessages) ? cloneJson(rawMessages) : [];
144
311
  messages.push({
145
312
  role: 'user',
146
313
  content: '继续执行'
147
314
  });
148
- payload.messages = messages;
149
- if (Array.isArray(source.tools) && source.tools.length) {
150
- payload.tools = cloneJson(source.tools);
151
- }
152
- const parameters = source.parameters;
153
- if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
154
- const params = cloneJson(parameters);
155
- Object.assign(payload, params);
156
- }
157
- return payload;
315
+ const chatPayload = {
316
+ ...(model ? { model } : {}),
317
+ messages,
318
+ ...(originalTools ? { tools: originalTools } : {})
319
+ };
320
+ return buildResponsesRequestFromChat(chatPayload, {
321
+ stream: false,
322
+ ...(parametersForFollowup ? { parameters: parametersForFollowup } : {})
323
+ }).request;
158
324
  }
159
325
  function hasCompactionFlag(record) {
160
326
  const flag = record.compactionRequest;
@@ -166,3 +332,38 @@ function hasCompactionFlag(record) {
166
332
  }
167
333
  return false;
168
334
  }
335
+ function extractAssistantMessage(baseResponse) {
336
+ if (!baseResponse || typeof baseResponse !== 'object' || Array.isArray(baseResponse)) {
337
+ return null;
338
+ }
339
+ const base = baseResponse;
340
+ // OpenAI Chat shape
341
+ const choices = Array.isArray(base.choices) ? base.choices : [];
342
+ if (choices.length > 0) {
343
+ const first = choices[0] && typeof choices[0] === 'object' && !Array.isArray(choices[0])
344
+ ? choices[0]
345
+ : null;
346
+ const msg = first &&
347
+ first.message &&
348
+ typeof first.message === 'object' &&
349
+ !Array.isArray(first.message)
350
+ ? first.message
351
+ : null;
352
+ if (msg) {
353
+ const content = typeof msg.content === 'string' ? msg.content.trim() : '';
354
+ if (!content) {
355
+ return null;
356
+ }
357
+ return cloneJson(msg);
358
+ }
359
+ }
360
+ // OpenAI Responses shape
361
+ const outputText = extractResponsesOutputText(base);
362
+ if (outputText.length > 0) {
363
+ return {
364
+ role: 'assistant',
365
+ content: outputText
366
+ };
367
+ }
368
+ return null;
369
+ }
@@ -1,6 +1,6 @@
1
1
  import { registerServerToolHandler } from '../registry.js';
2
- import { cloneJson } from '../server-side-tools.js';
3
2
  import { isCompactionRequest } from './compaction-detect.js';
3
+ import { buildEntryAwareFollowupPayload, extractCapturedChatSeed } from './followup-request-builder.js';
4
4
  const FLOW_ID = 'iflow_model_error_retry';
5
5
  const handler = async (ctx) => {
6
6
  if (!ctx.options.reenterPipeline) {
@@ -46,7 +46,7 @@ const handler = async (ctx) => {
46
46
  if (isCompactionRequest(captured)) {
47
47
  return null;
48
48
  }
49
- const followupPayload = buildRetryFollowupPayload(captured);
49
+ const followupPayload = buildRetryFollowupPayload(captured, ctx.options.entryEndpoint || ctx.adapterContext?.entryEndpoint || '/v1/chat/completions');
50
50
  if (!followupPayload) {
51
51
  return null;
52
52
  }
@@ -75,28 +75,18 @@ function getCapturedRequest(adapterContext) {
75
75
  }
76
76
  return captured;
77
77
  }
78
- function buildRetryFollowupPayload(source) {
79
- if (!source || typeof source !== 'object') {
78
+ function buildRetryFollowupPayload(source, entryEndpoint) {
79
+ const seed = extractCapturedChatSeed(source);
80
+ if (!seed) {
80
81
  return null;
81
82
  }
82
- const payload = {};
83
- if (typeof source.model === 'string' && source.model.trim()) {
84
- payload.model = source.model.trim();
85
- }
86
- const rawMessages = source.messages;
87
- if (Array.isArray(rawMessages)) {
88
- payload.messages = cloneJson(rawMessages);
89
- }
90
- const rawTools = source.tools;
91
- if (Array.isArray(rawTools) && rawTools.length) {
92
- payload.tools = cloneJson(rawTools);
93
- }
94
- const parameters = source.parameters;
95
- if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
96
- const params = cloneJson(parameters);
97
- Object.assign(payload, params);
98
- }
99
- return payload;
83
+ return buildEntryAwareFollowupPayload({
84
+ entryEndpoint,
85
+ model: seed.model,
86
+ messages: seed.messages,
87
+ ...(seed.tools ? { tools: seed.tools } : {}),
88
+ ...(seed.parameters ? { parameters: seed.parameters } : {})
89
+ });
100
90
  }
101
91
  function hasCompactionFlag(record) {
102
92
  const flag = record.compactionRequest;