@jsonstudio/llms 0.6.1172 → 0.6.1354

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 (160) hide show
  1. package/dist/conversion/codecs/gemini-openai-codec.d.ts +3 -1
  2. package/dist/conversion/codecs/gemini-openai-codec.js +10 -4
  3. package/dist/conversion/compat/actions/gemini-web-search.d.ts +1 -1
  4. package/dist/conversion/compat/actions/gemini-web-search.js +5 -2
  5. package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +12 -0
  6. package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +199 -0
  7. package/dist/conversion/compat/actions/iflow-web-search.d.ts +1 -1
  8. package/dist/conversion/compat/actions/iflow-web-search.js +5 -2
  9. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +47 -56
  10. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +1 -13
  11. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +523 -50
  12. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +18 -38
  13. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +6 -0
  14. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +3 -0
  15. package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.d.ts +10 -0
  16. package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.js +134 -0
  17. package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.d.ts +6 -0
  18. package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.js +79 -0
  19. package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.d.ts +3 -0
  20. package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.js +46 -0
  21. package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.d.ts +8 -0
  22. package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.js +366 -0
  23. package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.d.ts +9 -0
  24. package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.js +384 -0
  25. package/dist/conversion/hub/pipeline/hub-pipeline/node-results.d.ts +3 -0
  26. package/dist/conversion/hub/pipeline/hub-pipeline/node-results.js +14 -0
  27. package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.d.ts +2 -0
  28. package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.js +144 -0
  29. package/dist/conversion/hub/pipeline/hub-pipeline/policy.d.ts +4 -0
  30. package/dist/conversion/hub/pipeline/hub-pipeline/policy.js +32 -0
  31. package/dist/conversion/hub/pipeline/hub-pipeline/protocol.d.ts +8 -0
  32. package/dist/conversion/hub/pipeline/hub-pipeline/protocol.js +63 -0
  33. package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.d.ts +2 -0
  34. package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.js +43 -0
  35. package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.d.ts +1 -0
  36. package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.js +29 -0
  37. package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.d.ts +2 -0
  38. package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.js +16 -0
  39. package/dist/conversion/hub/pipeline/hub-pipeline/types.d.ts +116 -0
  40. package/dist/conversion/hub/pipeline/hub-pipeline/types.js +1 -0
  41. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +3 -95
  42. package/dist/conversion/hub/pipeline/hub-pipeline.js +19 -1281
  43. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +1 -1
  44. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +7 -0
  45. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +65 -1
  46. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +25 -22
  47. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +1 -1
  48. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.d.ts +1 -1
  49. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.js +2 -2
  50. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +2 -2
  51. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +1 -1
  52. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +1 -1
  53. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +11 -11
  54. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.js +1 -1
  55. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.d.ts +1 -0
  56. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.js +4 -2
  57. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.d.ts +1 -0
  58. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +17 -9
  59. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js +2 -2
  60. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +40 -2
  61. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.js +1 -1
  62. package/dist/conversion/hub/pipeline/target-utils.js +9 -5
  63. package/dist/conversion/hub/process/chat-process.js +256 -16
  64. package/dist/conversion/hub/response/provider-response.d.ts +8 -0
  65. package/dist/conversion/hub/response/provider-response.js +85 -27
  66. package/dist/conversion/hub/response/response-mappers.d.ts +10 -3
  67. package/dist/conversion/hub/response/response-mappers.js +30 -6
  68. package/dist/conversion/hub/response/response-runtime.js +4 -38
  69. package/dist/conversion/hub/snapshot-recorder.js +5 -1
  70. package/dist/conversion/hub/standardized-bridge.js +23 -15
  71. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +36 -5
  72. package/dist/conversion/responses/responses-openai-bridge.js +20 -4
  73. package/dist/conversion/shared/gemini-tool-utils.d.ts +8 -1
  74. package/dist/conversion/shared/gemini-tool-utils.js +580 -108
  75. package/dist/conversion/shared/jsonish.js +1 -1
  76. package/dist/conversion/shared/mcp-injection.js +67 -33
  77. package/dist/conversion/shared/openai-finalizer.js +2 -1
  78. package/dist/conversion/shared/openai-message-normalize.js +76 -21
  79. package/dist/conversion/shared/responses-output-builder.js +6 -0
  80. package/dist/conversion/shared/runtime-metadata.d.ts +7 -0
  81. package/dist/conversion/shared/runtime-metadata.js +23 -0
  82. package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
  83. package/dist/conversion/shared/text-markup-normalizer.js +284 -4
  84. package/dist/conversion/shared/tool-canonicalizer.js +2 -1
  85. package/dist/conversion/shared/tool-governor.js +3 -3
  86. package/dist/filters/engine.js +5 -5
  87. package/dist/filters/special/request-tool-list-filter.js +194 -60
  88. package/dist/filters/special/request-tools-normalize.js +1 -1
  89. package/dist/filters/special/response-tool-text-canonicalize.d.ts +4 -7
  90. package/dist/filters/special/response-tool-text-canonicalize.js +7 -35
  91. package/dist/filters/special/tool-filter-hooks.js +58 -62
  92. package/dist/guidance/index.js +5 -1
  93. package/dist/http/sse-response.js +6 -6
  94. package/dist/router/virtual-router/bootstrap.js +48 -4
  95. package/dist/router/virtual-router/engine-health.d.ts +1 -1
  96. package/dist/router/virtual-router/engine-health.js +11 -110
  97. package/dist/router/virtual-router/engine-selection/alias-selection.d.ts +15 -0
  98. package/dist/router/virtual-router/engine-selection/alias-selection.js +156 -0
  99. package/dist/router/virtual-router/engine-selection/context-weight-multipliers.d.ts +11 -0
  100. package/dist/router/virtual-router/engine-selection/context-weight-multipliers.js +23 -0
  101. package/dist/router/virtual-router/engine-selection/direct-provider-model.d.ts +9 -0
  102. package/dist/router/virtual-router/engine-selection/direct-provider-model.js +49 -0
  103. package/dist/router/virtual-router/engine-selection/instruction-target.d.ts +6 -0
  104. package/dist/router/virtual-router/engine-selection/instruction-target.js +54 -0
  105. package/dist/router/virtual-router/engine-selection/key-parsing.d.ts +8 -0
  106. package/dist/router/virtual-router/engine-selection/key-parsing.js +64 -0
  107. package/dist/router/virtual-router/engine-selection/route-utils.d.ts +12 -0
  108. package/dist/router/virtual-router/engine-selection/route-utils.js +150 -0
  109. package/dist/router/virtual-router/engine-selection/routing-state-filter.d.ts +4 -0
  110. package/dist/router/virtual-router/engine-selection/routing-state-filter.js +50 -0
  111. package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +39 -0
  112. package/dist/router/virtual-router/engine-selection/selection-deps.js +1 -0
  113. package/dist/router/virtual-router/engine-selection/sticky-pool.d.ts +11 -0
  114. package/dist/router/virtual-router/engine-selection/sticky-pool.js +109 -0
  115. package/dist/router/virtual-router/engine-selection/tier-priority.d.ts +12 -0
  116. package/dist/router/virtual-router/engine-selection/tier-priority.js +55 -0
  117. package/dist/router/virtual-router/engine-selection/tier-selection-select.d.ts +22 -0
  118. package/dist/router/virtual-router/engine-selection/tier-selection-select.js +400 -0
  119. package/dist/router/virtual-router/engine-selection/tier-selection.d.ts +3 -0
  120. package/dist/router/virtual-router/engine-selection/tier-selection.js +225 -0
  121. package/dist/router/virtual-router/engine-selection.d.ts +4 -30
  122. package/dist/router/virtual-router/engine-selection.js +10 -962
  123. package/dist/router/virtual-router/engine.d.ts +1 -0
  124. package/dist/router/virtual-router/engine.js +55 -10
  125. package/dist/router/virtual-router/routing-instructions.js +6 -1
  126. package/dist/router/virtual-router/stop-message-state-sync.d.ts +5 -0
  127. package/dist/router/virtual-router/stop-message-state-sync.js +6 -14
  128. package/dist/router/virtual-router/types.d.ts +25 -1
  129. package/dist/servertool/clock/config.d.ts +8 -0
  130. package/dist/servertool/clock/config.js +22 -0
  131. package/dist/servertool/clock/log.d.ts +3 -0
  132. package/dist/servertool/clock/log.js +13 -0
  133. package/dist/servertool/clock/task-store.d.ts +1 -1
  134. package/dist/servertool/clock/task-store.js +1 -1
  135. package/dist/servertool/clock/tasks.js +1 -1
  136. package/dist/servertool/engine.js +146 -21
  137. package/dist/servertool/handlers/clock-auto.js +11 -6
  138. package/dist/servertool/handlers/clock.js +36 -10
  139. package/dist/servertool/handlers/followup-request-builder.js +8 -2
  140. package/dist/servertool/handlers/gemini-empty-reply-continue.js +15 -9
  141. package/dist/servertool/handlers/iflow-model-error-retry.js +6 -4
  142. package/dist/servertool/handlers/recursive-detection-guard.js +4 -2
  143. package/dist/servertool/handlers/stop-message-auto.js +100 -10
  144. package/dist/servertool/handlers/vision.js +4 -1
  145. package/dist/servertool/handlers/web-search.js +3 -1
  146. package/dist/servertool/pending-session.d.ts +19 -0
  147. package/dist/servertool/pending-session.js +97 -0
  148. package/dist/servertool/reenter-backend.js +5 -3
  149. package/dist/servertool/server-side-tools.js +235 -6
  150. package/dist/servertool/types.d.ts +13 -0
  151. package/dist/sse/json-to-sse/event-generators/responses.js +1 -1
  152. package/dist/sse/shared/chat-serializer.js +2 -2
  153. package/dist/sse/shared/constants.js +1 -1
  154. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +7 -1
  155. package/dist/sse/sse-to-json/builders/response-builder.js +16 -0
  156. package/dist/sse/sse-to-json/responses-sse-to-json-converter.d.ts +1 -1
  157. package/dist/tools/apply-patch/execution-capturer.js +1 -1
  158. package/dist/tools/exec-command/normalize.js +4 -0
  159. package/dist/tools/exec-command/regression-capturer.js +1 -1
  160. package/package.json +10 -5
@@ -10,6 +10,122 @@ import './handlers/clock-auto.js';
10
10
  import './handlers/exec-command-guard.js';
11
11
  import './handlers/apply-patch-guard.js';
12
12
  import './handlers/recursive-detection-guard.js';
13
+ function extractToolCallsFromMessage(message) {
14
+ const toolCalls = getArray(message.tool_calls);
15
+ const out = [];
16
+ for (const raw of toolCalls) {
17
+ const tc = asObject(raw);
18
+ if (!tc)
19
+ continue;
20
+ const id = typeof tc.id === 'string' && String(tc.id).trim() ? String(tc.id).trim() : '';
21
+ const fn = asObject(tc.function) ??
22
+ asObject(tc.functionCall) ??
23
+ asObject(tc.function_call);
24
+ const name = fn && typeof fn.name === 'string' && String(fn.name).trim() ? String(fn.name).trim() : '';
25
+ const rawArgs = (fn ? fn.arguments : undefined) ??
26
+ (fn ? fn.args : undefined) ??
27
+ (fn ? fn.input : undefined) ??
28
+ tc.arguments ??
29
+ tc.args ??
30
+ tc.input;
31
+ let args = '';
32
+ if (typeof rawArgs === 'string') {
33
+ args = rawArgs;
34
+ }
35
+ else if (rawArgs && typeof rawArgs === 'object') {
36
+ try {
37
+ args = JSON.stringify(rawArgs);
38
+ }
39
+ catch {
40
+ args = '';
41
+ }
42
+ }
43
+ else if (rawArgs !== undefined && rawArgs !== null) {
44
+ args = String(rawArgs);
45
+ }
46
+ if (!id || !name)
47
+ continue;
48
+ out.push({ raw: tc, parsed: { id, name, arguments: args } });
49
+ }
50
+ return out;
51
+ }
52
+ function buildAssistantToolCallMessage(toolCalls) {
53
+ const calls = toolCalls.map((tc) => ({
54
+ id: tc.id,
55
+ type: 'function',
56
+ function: {
57
+ name: tc.name,
58
+ arguments: tc.arguments
59
+ }
60
+ }));
61
+ return { role: 'assistant', content: null, tool_calls: calls };
62
+ }
63
+ function appendToolOutput(base, toolCallId, name, content) {
64
+ const outputs = Array.isArray(base.tool_outputs) ? base.tool_outputs : [];
65
+ outputs.push({ tool_call_id: toolCallId, name, content });
66
+ base.tool_outputs = outputs;
67
+ }
68
+ function buildToolMessagesFromOutputs(base, allowIds) {
69
+ const outputs = Array.isArray(base.tool_outputs) ? base.tool_outputs : [];
70
+ const out = [];
71
+ for (const entry of outputs) {
72
+ if (!entry || typeof entry !== 'object')
73
+ continue;
74
+ const toolCallId = typeof entry.tool_call_id === 'string' ? String(entry.tool_call_id) : '';
75
+ if (!toolCallId || !allowIds.has(toolCallId))
76
+ continue;
77
+ const name = typeof entry.name === 'string' && String(entry.name).trim()
78
+ ? String(entry.name).trim()
79
+ : 'tool';
80
+ const content = typeof entry.content === 'string' ? String(entry.content) : JSON.stringify(entry.content ?? {});
81
+ out.push({ role: 'tool', tool_call_id: toolCallId, name, content });
82
+ }
83
+ return out;
84
+ }
85
+ function stripToolOutputs(base) {
86
+ try {
87
+ delete base.tool_outputs;
88
+ }
89
+ catch {
90
+ /* ignore */
91
+ }
92
+ }
93
+ function replaceJsonObjectInPlace(target, next) {
94
+ try {
95
+ for (const key of Object.keys(target)) {
96
+ delete target[key];
97
+ }
98
+ for (const [k, v] of Object.entries(next)) {
99
+ target[k] = v;
100
+ }
101
+ }
102
+ catch {
103
+ // ignore
104
+ }
105
+ }
106
+ function filterOutExecutedToolCalls(chatResponse, executedIds) {
107
+ const choices = getArray(chatResponse.choices);
108
+ for (const choice of choices) {
109
+ const choiceObj = asObject(choice);
110
+ if (!choiceObj)
111
+ continue;
112
+ const message = asObject(choiceObj.message);
113
+ if (!message)
114
+ continue;
115
+ const toolCalls = getArray(message.tool_calls);
116
+ if (!toolCalls.length)
117
+ continue;
118
+ const next = toolCalls.filter((tc) => {
119
+ if (!tc || typeof tc !== 'object' || Array.isArray(tc))
120
+ return true;
121
+ const id = typeof tc.id === 'string' ? String(tc.id).trim() : '';
122
+ if (!id)
123
+ return true;
124
+ return !executedIds.has(id);
125
+ });
126
+ message.tool_calls = next;
127
+ }
128
+ }
13
129
  export async function runServerSideToolEngine(options) {
14
130
  const base = asObject(options.chatResponse);
15
131
  if (!base) {
@@ -31,20 +147,133 @@ export async function runServerSideToolEngine(options) {
31
147
  providerInvoker: typeof options.providerInvoker === 'function'
32
148
  }
33
149
  };
34
- for (const toolCall of toolCalls) {
150
+ // Tool-call servertools: execute all executable servertool calls first, then decide:
151
+ // - if only servertools were present -> followup via reenter (existing behavior)
152
+ // - if mixed (servertool + client tools) -> persist servertool results, return remaining tool_calls to client
153
+ const executedToolCalls = [];
154
+ const executedIds = new Set();
155
+ const baseForExecution = cloneJson(base);
156
+ const executedFlowIds = [];
157
+ let lastExecution;
158
+ const attemptedToolCallsByMessage = [];
159
+ const choices = getArray(base.choices);
160
+ for (const choice of choices) {
161
+ const choiceObj = asObject(choice);
162
+ if (!choiceObj)
163
+ continue;
164
+ const message = asObject(choiceObj.message);
165
+ if (!message)
166
+ continue;
167
+ attemptedToolCallsByMessage.push(...extractToolCallsFromMessage(message));
168
+ }
169
+ for (const { parsed: toolCall } of attemptedToolCallsByMessage) {
35
170
  const entry = getServerToolHandler(toolCall.name);
36
- if (!entry || entry.trigger !== 'tool_call')
171
+ if (!entry || entry.trigger !== 'tool_call') {
37
172
  continue;
38
- const ctx = { ...contextBase, toolCall };
39
- const planned = await runHandler(entry.handler, ctx);
173
+ }
174
+ const ctx = { ...contextBase, base: baseForExecution, toolCall };
175
+ let planned = null;
176
+ let lastErr;
177
+ for (let attempt = 1; attempt <= 2; attempt += 1) {
178
+ try {
179
+ planned = await runHandler(entry.handler, ctx);
180
+ lastErr = undefined;
181
+ break;
182
+ }
183
+ catch (err) {
184
+ lastErr = err;
185
+ }
186
+ }
40
187
  const result = planned ? await materializePlannedResult(planned, options) : null;
41
188
  if (result) {
189
+ replaceJsonObjectInPlace(baseForExecution, cloneJson(result.chatResponse));
190
+ executedToolCalls.push(toolCall);
191
+ executedIds.add(toolCall.id);
192
+ if (result.execution?.flowId && typeof result.execution.flowId === 'string' && result.execution.flowId.trim()) {
193
+ executedFlowIds.push(result.execution.flowId.trim());
194
+ }
195
+ lastExecution = result.execution;
196
+ continue;
197
+ }
198
+ if (lastErr) {
199
+ // Handler failed: report tool error as a tool_output, but do not crash the whole pipeline.
200
+ const message = lastErr instanceof Error ? lastErr.message : String(lastErr ?? 'unknown');
201
+ appendToolOutput(baseForExecution, toolCall.id, toolCall.name, JSON.stringify({ ok: false, tool: toolCall.name, message, retryable: true }));
202
+ executedToolCalls.push(toolCall);
203
+ executedIds.add(toolCall.id);
204
+ executedFlowIds.push(`${toolCall.name}_error`);
205
+ }
206
+ }
207
+ if (executedToolCalls.length > 0) {
208
+ const clientResponse = cloneJson(base);
209
+ filterOutExecutedToolCalls(clientResponse, executedIds);
210
+ stripToolOutputs(clientResponse);
211
+ // Determine whether any non-executed tool_calls remain (client tools).
212
+ const remainingToolCalls = [];
213
+ const remainingChoices = getArray(clientResponse.choices);
214
+ for (const choice of remainingChoices) {
215
+ const choiceObj = asObject(choice);
216
+ if (!choiceObj)
217
+ continue;
218
+ const message = asObject(choiceObj.message);
219
+ if (!message)
220
+ continue;
221
+ const toolCallsArr = getArray(message.tool_calls);
222
+ for (const tc of toolCallsArr) {
223
+ if (!tc || typeof tc !== 'object' || Array.isArray(tc))
224
+ continue;
225
+ const id = typeof tc.id === 'string' ? String(tc.id).trim() : '';
226
+ if (id)
227
+ remainingToolCalls.push(id);
228
+ }
229
+ }
230
+ if (remainingToolCalls.length > 0) {
231
+ const sessionId = options.adapterContext && typeof options.adapterContext.sessionId === 'string'
232
+ ? String(options.adapterContext.sessionId).trim()
233
+ : '';
234
+ const allowIds = new Set(executedToolCalls.map((t) => t.id));
235
+ const injectionMessages = [
236
+ buildAssistantToolCallMessage(executedToolCalls),
237
+ ...buildToolMessagesFromOutputs(baseForExecution, allowIds)
238
+ ];
42
239
  return {
43
240
  mode: 'tool_flow',
44
- finalChatResponse: result.chatResponse,
45
- execution: result.execution
241
+ finalChatResponse: clientResponse,
242
+ execution: { flowId: 'servertool_mixed' },
243
+ ...(sessionId && injectionMessages.length
244
+ ? {
245
+ pendingInjection: {
246
+ sessionId,
247
+ afterToolCallIds: remainingToolCalls,
248
+ messages: injectionMessages
249
+ }
250
+ }
251
+ : {})
46
252
  };
47
253
  }
254
+ // Servertool-only: keep tool_outputs and ask orchestration layer to followup/reenter.
255
+ const genericFollowup = {
256
+ requestIdSuffix: ':servertool_followup',
257
+ injection: {
258
+ ops: [
259
+ { op: 'append_assistant_message', required: true },
260
+ { op: 'append_tool_messages_from_tool_outputs', required: true }
261
+ ]
262
+ }
263
+ };
264
+ const followup = executedToolCalls.length === 1 && lastExecution?.followup ? lastExecution.followup : genericFollowup;
265
+ const flowId = executedToolCalls.length === 1
266
+ ? (lastExecution?.flowId ?? executedFlowIds[0] ?? executedToolCalls[0].name)
267
+ : 'servertool_multi';
268
+ return {
269
+ mode: 'tool_flow',
270
+ finalChatResponse: baseForExecution,
271
+ execution: {
272
+ ...(lastExecution && executedToolCalls.length === 1 ? lastExecution : { flowId }),
273
+ flowId,
274
+ followup
275
+ }
276
+ };
48
277
  }
49
278
  for (const entry of listAutoServerToolHandlers()) {
50
279
  const planned = await runHandler(entry.handler, contextBase);
@@ -50,6 +50,8 @@ export interface ServerSideToolEngineOptions {
50
50
  }>;
51
51
  }
52
52
  export type ServerToolFollowupInjectionOp = {
53
+ op: 'preserve_tools';
54
+ } | {
53
55
  op: 'append_assistant_message';
54
56
  required?: boolean;
55
57
  } | {
@@ -155,6 +157,17 @@ export interface ServerSideToolEngineResult {
155
157
  mode: 'passthrough' | 'tool_flow';
156
158
  finalChatResponse: JsonObject;
157
159
  execution?: ServerToolExecution;
160
+ /**
161
+ * When present, indicates a "mixed tools" flow:
162
+ * - servertools were executed and their tool results are persisted
163
+ * - remaining (non-servertool) tool_calls must be returned to client
164
+ * - on next request, servertool results will be injected after client tool results
165
+ */
166
+ pendingInjection?: {
167
+ sessionId: string;
168
+ afterToolCallIds: string[];
169
+ messages: JsonObject[];
170
+ };
158
171
  }
159
172
  /**
160
173
  * ServerToolHandlerContext:单个工具 handler 的上下文入参。
@@ -3,7 +3,7 @@
3
3
  * 提供纯函数,将Responses响应数据转换为SSE事件对象,不处理流写入
4
4
  */
5
5
  import { TimeUtils, StringUtils } from '../../shared/utils.js';
6
- const TEXT_CHUNK_BOUNDARY = /[\n\r\t,。、“”‘’!?,\.\-:\u3000\s]/;
6
+ const TEXT_CHUNK_BOUNDARY = /[\n\r\t,。、“”‘’!?,.\-:\u3000\s]/;
7
7
  function cloneRegex(source) {
8
8
  return new RegExp(source.source, source.flags);
9
9
  }
@@ -27,13 +27,13 @@ export function toSSETextStream(objectEventStream) {
27
27
  try {
28
28
  out.write('data: {"error":"chat sse serialization failed"}\n\n');
29
29
  }
30
- catch { }
30
+ catch { /* ignore */ }
31
31
  }
32
32
  finally {
33
33
  try {
34
34
  out.end();
35
35
  }
36
- catch { }
36
+ catch { /* ignore */ }
37
37
  }
38
38
  })();
39
39
  return out;
@@ -306,7 +306,7 @@ export const LOG_LEVELS = {
306
306
  // 正则表达式常量
307
307
  export const REGEX_PATTERNS = {
308
308
  // 文本分词边界
309
- WORD_BOUNDARY: /[\n\r\t,。、"''!?,\.\-:\u3000\s]/,
309
+ WORD_BOUNDARY: /[\n\r\t,。、"''!?,.\-:\u3000\s]/,
310
310
  SENTENCE_BOUNDARY: /[。!?.!?]/,
311
311
  PARAGRAPH_BOUNDARY: /[\n\r]{2,}/,
312
312
  // JSON验证
@@ -1,11 +1,17 @@
1
+ import { DEFAULT_ANTHROPIC_CONVERSION_CONFIG } from '../types/index.js';
1
2
  import type { AnthropicMessageResponse, SseToAnthropicJsonOptions } from '../types/index.js';
3
+ type AnthropicSseToJsonConverterConfig = {
4
+ enableEventValidation: boolean;
5
+ strictMode: boolean;
6
+ } & typeof DEFAULT_ANTHROPIC_CONVERSION_CONFIG;
2
7
  export declare class AnthropicSseToJsonConverter {
3
8
  private config;
4
9
  private contexts;
5
- constructor(config?: Partial<typeof this.config>);
10
+ constructor(config?: Partial<AnthropicSseToJsonConverterConfig>);
6
11
  convertSseToJson(sseStream: AsyncIterable<string | Buffer>, options: SseToAnthropicJsonOptions): Promise<AnthropicMessageResponse>;
7
12
  private createContext;
8
13
  private chunkStrings;
9
14
  private updateStats;
10
15
  private wrapError;
11
16
  }
17
+ export {};
@@ -825,6 +825,22 @@ export class ResponsesResponseBuilder {
825
825
  }
826
826
  return { success: true, response: this.response };
827
827
  }
828
+ // 进一步容错:少数上游会在输出流结束前没有发送 output_item.done/response.completed/response.done,
829
+ // 但已发送 output_item.added/content_part.* 等事件。此时 outputItemBuilders 中已存在内容,
830
+ // 直接按当前聚合结果构建 output 并视为 completed,避免 SSE_TO_JSON_ERROR: Building not completed。
831
+ if (this.outputItemBuilders.size > 0) {
832
+ this.response.status = 'completed';
833
+ try {
834
+ const cur = this.response.output;
835
+ if (!Array.isArray(cur) || cur.length === 0) {
836
+ this.response.output = this.buildOutputItems();
837
+ }
838
+ }
839
+ catch {
840
+ this.response.output = this.buildOutputItems();
841
+ }
842
+ return { success: true, response: this.response };
843
+ }
828
844
  }
829
845
  catch { /* ignore */ }
830
846
  return { success: false, error: new Error('Building not completed') };
@@ -10,7 +10,7 @@ import type { ResponsesResponse, SseToResponsesJsonContext, SseToResponsesJsonOp
10
10
  export declare class ResponsesSseToJsonConverterRefactored {
11
11
  private config;
12
12
  private contexts;
13
- constructor(config?: Partial<typeof this.config>);
13
+ constructor(config?: Partial<ResponsesSseToJsonConverterRefactored['config']>);
14
14
  /**
15
15
  * 将SSE流转换为Responses响应
16
16
  */
@@ -147,7 +147,7 @@ export function captureApplyPatchExecutionFailuresFromProcessedRequest(processed
147
147
  entryEndpoint,
148
148
  providerKey,
149
149
  model,
150
- source: 'req_process_stage1_tool_governance',
150
+ source: 'chat_process.req.stage4.tool_governance',
151
151
  meta: { applyPatchToolMode: resolvedMode },
152
152
  toolCallId,
153
153
  toolCallArgs
@@ -90,6 +90,10 @@ export function normalizeExecCommandArgs(args) {
90
90
  const tty = asBoolean(base.tty);
91
91
  if (tty !== undefined)
92
92
  normalized.tty = tty;
93
+ const timeoutMs = asFiniteNumber(base.timeout_ms) ??
94
+ asFiniteNumber(base.timeoutMs);
95
+ if (timeoutMs !== undefined)
96
+ normalized.timeout_ms = timeoutMs;
93
97
  const shell = asNonEmptyString(base.shell);
94
98
  if (shell)
95
99
  normalized.shell = shell;
@@ -79,7 +79,7 @@ function redactSecrets(text) {
79
79
  next = next.replace(/\bcr_[A-Za-z0-9]{20,}\b/g, 'cr_<REDACTED>');
80
80
  next = next.replace(/\bAIza[0-9A-Za-z_-]{20,}\b/g, 'AIza<REDACTED>');
81
81
  // Authorization headers.
82
- next = next.replace(/(authorization\"?\s*:\s*\"?bearer\s+)[^\"]+/gi, '$1<REDACTED>');
82
+ next = next.replace(/(authorization"?\s*:\s*"?bearer\s+)[^"]+/gi, '$1<REDACTED>');
83
83
  next = next.replace(/(authorization:\s*bearer\s+)[^\s]+/gi, '$1<REDACTED>');
84
84
  // Shell exports of *_KEY/*_TOKEN/*_SECRET.
85
85
  next = next.replace(/(\bexport\s+[A-Z0-9_]*(?:KEY|TOKEN|SECRET)[A-Z0-9_]*=)[^\s'"]+/gi, '$1<REDACTED>');
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@jsonstudio/llms",
3
- "version": "0.6.1172",
3
+ "version": "0.6.1354",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "scripts": {
9
- "build": "node scripts/clean-dist.mjs && node scripts/bump-version.mjs && tsc -p tsconfig.json && node scripts/tools/copy-compat-profiles.mjs",
10
- "build:dev": "node scripts/clean-dist.mjs && node scripts/bump-version.mjs && tsc -p tsconfig.json && node scripts/tools/copy-compat-profiles.mjs",
9
+ "build": "node scripts/clean-dist.mjs && node scripts/bump-version.mjs && node ./node_modules/typescript/bin/tsc -p tsconfig.json && node scripts/tools/copy-compat-profiles.mjs",
10
+ "build:dev": "node scripts/clean-dist.mjs && node scripts/bump-version.mjs && node ./node_modules/typescript/bin/tsc -p tsconfig.json && node scripts/tools/copy-compat-profiles.mjs",
11
+ "build:ci": "node scripts/clean-dist.mjs && node ./node_modules/typescript/bin/tsc -p tsconfig.json && node scripts/tools/copy-compat-profiles.mjs",
12
+ "build:coverage": "node scripts/clean-dist.mjs && node ./node_modules/typescript/bin/tsc -p tsconfig.coverage.json && node scripts/tools/copy-compat-profiles.mjs",
11
13
  "lint": "eslint --no-eslintrc -c .eslintrc.json src --ext .ts --no-cache",
12
14
  "lint:fix": "eslint --no-eslintrc -c .eslintrc.json src --ext .ts --no-cache --fix",
13
15
  "postbuild": "node scripts/tests/run-matrix-ci.mjs",
@@ -25,8 +27,10 @@
25
27
  "test:looprt:gemini": "npm run build && node scripts/tests/loop-rt-gemini.mjs",
26
28
  "replay:responses:chat-sse": "node scripts/exp3-responses-sse-to-chat-sse.mjs",
27
29
  "replay:responses:loop": "node scripts/exp4-responses-sse-loop.mjs",
28
- "test:virtual-router": "npm run build:dev && node scripts/tests/virtual-router-prefer-autoclear.mjs && node scripts/tests/virtual-router-dry-run.mjs && node scripts/tests/virtual-router-context.mjs",
29
- "test:virtual-router-health": "npm run build:dev && node scripts/tests/virtual-router-health.mjs"
30
+ "test:virtual-router": "npm run build:dev && node scripts/tests/virtual-router-prefer-autoclear.mjs && node scripts/tests/virtual-router-dry-run.mjs && node scripts/tests/virtual-router-context.mjs && node scripts/tests/virtual-router-antigravity-alias-pin.mjs",
31
+ "test:virtual-router-health": "npm run build:dev && node scripts/tests/virtual-router-health.mjs",
32
+ "test:golden": "npm run build:ci && node scripts/tests/chat-golden-roundtrip.mjs && node scripts/tests/anthropic-golden-roundtrip.mjs",
33
+ "test:coverage": "node scripts/run-ci-coverage.mjs"
30
34
  },
31
35
  "dependencies": {
32
36
  "ajv": "^8.17.1",
@@ -55,6 +59,7 @@
55
59
  "@types/node": "^20.11.25",
56
60
  "@typescript-eslint/eslint-plugin": "^6.7.4",
57
61
  "@typescript-eslint/parser": "^6.7.4",
62
+ "c8": "^10.1.3",
58
63
  "eslint": "^8.50.0",
59
64
  "typescript": "^5.4.0"
60
65
  },