@jsonstudio/llms 0.6.1172 → 0.6.1397
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.
- package/dist/conversion/codecs/gemini-openai-codec.d.ts +3 -1
- package/dist/conversion/codecs/gemini-openai-codec.js +10 -4
- package/dist/conversion/compat/actions/gemini-web-search.d.ts +1 -1
- package/dist/conversion/compat/actions/gemini-web-search.js +5 -2
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +12 -0
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +199 -0
- package/dist/conversion/compat/actions/iflow-web-search.d.ts +1 -1
- package/dist/conversion/compat/actions/iflow-web-search.js +5 -2
- package/dist/conversion/compat/profiles/chat-gemini.json +5 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +47 -56
- package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +1 -13
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +748 -52
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +18 -38
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +6 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +3 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.d.ts +10 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.js +142 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.d.ts +6 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.js +79 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.d.ts +3 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.js +46 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.d.ts +8 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.js +366 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.d.ts +9 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.js +390 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/node-results.d.ts +3 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/node-results.js +14 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.d.ts +2 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.js +144 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/policy.d.ts +4 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/policy.js +32 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/protocol.d.ts +8 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/protocol.js +63 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.d.ts +2 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.js +43 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.d.ts +1 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.js +29 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.d.ts +2 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.js +16 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/types.d.ts +116 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/types.js +1 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +3 -95
- package/dist/conversion/hub/pipeline/hub-pipeline.js +19 -1281
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +1 -1
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +7 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +65 -1
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +25 -22
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +1 -1
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.d.ts +1 -1
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.js +2 -2
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_thought_signature_inject/index.d.ts +10 -0
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_thought_signature_inject/index.js +172 -0
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +2 -2
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +1 -1
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +1 -1
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +11 -11
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.js +1 -1
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.d.ts +1 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.js +4 -2
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_thought_signature_capture/index.d.ts +10 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_thought_signature_capture/index.js +71 -0
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.d.ts +1 -0
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +17 -9
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js +2 -2
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +40 -2
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.js +1 -1
- package/dist/conversion/hub/pipeline/target-utils.js +9 -5
- package/dist/conversion/hub/pipeline/thought-signature/thought-signature-center.d.ts +14 -0
- package/dist/conversion/hub/pipeline/thought-signature/thought-signature-center.js +289 -0
- package/dist/conversion/hub/process/chat-process.js +256 -16
- package/dist/conversion/hub/response/provider-response.d.ts +8 -0
- package/dist/conversion/hub/response/provider-response.js +91 -27
- package/dist/conversion/hub/response/response-mappers.d.ts +10 -3
- package/dist/conversion/hub/response/response-mappers.js +30 -6
- package/dist/conversion/hub/response/response-runtime.js +4 -38
- package/dist/conversion/hub/snapshot-recorder.js +5 -1
- package/dist/conversion/hub/standardized-bridge.js +23 -15
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +36 -5
- package/dist/conversion/responses/responses-openai-bridge.js +20 -4
- package/dist/conversion/shared/gemini-tool-utils.d.ts +8 -1
- package/dist/conversion/shared/gemini-tool-utils.js +580 -108
- package/dist/conversion/shared/jsonish.js +1 -1
- package/dist/conversion/shared/mcp-injection.js +67 -33
- package/dist/conversion/shared/openai-finalizer.js +2 -1
- package/dist/conversion/shared/openai-message-normalize.js +76 -21
- package/dist/conversion/shared/responses-output-builder.js +6 -0
- package/dist/conversion/shared/runtime-metadata.d.ts +7 -0
- package/dist/conversion/shared/runtime-metadata.js +23 -0
- package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
- package/dist/conversion/shared/text-markup-normalizer.js +284 -4
- package/dist/conversion/shared/tool-canonicalizer.js +2 -1
- package/dist/conversion/shared/tool-governor.js +3 -3
- package/dist/filters/engine.js +5 -5
- package/dist/filters/special/request-tool-list-filter.js +194 -60
- package/dist/filters/special/request-tools-normalize.js +1 -1
- package/dist/filters/special/response-tool-text-canonicalize.d.ts +4 -7
- package/dist/filters/special/response-tool-text-canonicalize.js +7 -35
- package/dist/filters/special/tool-filter-hooks.js +58 -62
- package/dist/guidance/index.js +5 -1
- package/dist/http/sse-response.js +6 -6
- package/dist/router/virtual-router/bootstrap.js +54 -4
- package/dist/router/virtual-router/engine-health.d.ts +1 -1
- package/dist/router/virtual-router/engine-health.js +11 -110
- package/dist/router/virtual-router/engine-selection/alias-selection.d.ts +30 -0
- package/dist/router/virtual-router/engine-selection/alias-selection.js +237 -0
- package/dist/router/virtual-router/engine-selection/context-weight-multipliers.d.ts +11 -0
- package/dist/router/virtual-router/engine-selection/context-weight-multipliers.js +23 -0
- package/dist/router/virtual-router/engine-selection/direct-provider-model.d.ts +9 -0
- package/dist/router/virtual-router/engine-selection/direct-provider-model.js +49 -0
- package/dist/router/virtual-router/engine-selection/instruction-target.d.ts +6 -0
- package/dist/router/virtual-router/engine-selection/instruction-target.js +54 -0
- package/dist/router/virtual-router/engine-selection/key-parsing.d.ts +8 -0
- package/dist/router/virtual-router/engine-selection/key-parsing.js +64 -0
- package/dist/router/virtual-router/engine-selection/route-utils.d.ts +12 -0
- package/dist/router/virtual-router/engine-selection/route-utils.js +150 -0
- package/dist/router/virtual-router/engine-selection/routing-state-filter.d.ts +4 -0
- package/dist/router/virtual-router/engine-selection/routing-state-filter.js +50 -0
- package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +39 -0
- package/dist/router/virtual-router/engine-selection/selection-deps.js +1 -0
- package/dist/router/virtual-router/engine-selection/sticky-pool.d.ts +11 -0
- package/dist/router/virtual-router/engine-selection/sticky-pool.js +109 -0
- package/dist/router/virtual-router/engine-selection/tier-priority.d.ts +12 -0
- package/dist/router/virtual-router/engine-selection/tier-priority.js +55 -0
- package/dist/router/virtual-router/engine-selection/tier-selection-select.d.ts +22 -0
- package/dist/router/virtual-router/engine-selection/tier-selection-select.js +423 -0
- package/dist/router/virtual-router/engine-selection/tier-selection.d.ts +3 -0
- package/dist/router/virtual-router/engine-selection/tier-selection.js +228 -0
- package/dist/router/virtual-router/engine-selection.d.ts +4 -30
- package/dist/router/virtual-router/engine-selection.js +10 -962
- package/dist/router/virtual-router/engine.d.ts +1 -0
- package/dist/router/virtual-router/engine.js +64 -11
- package/dist/router/virtual-router/routing-instructions.js +6 -1
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +5 -0
- package/dist/router/virtual-router/stop-message-state-sync.js +6 -14
- package/dist/router/virtual-router/types.d.ts +38 -1
- package/dist/servertool/clock/config.d.ts +8 -0
- package/dist/servertool/clock/config.js +22 -0
- package/dist/servertool/clock/log.d.ts +3 -0
- package/dist/servertool/clock/log.js +13 -0
- package/dist/servertool/clock/task-store.d.ts +1 -1
- package/dist/servertool/clock/task-store.js +1 -1
- package/dist/servertool/clock/tasks.js +1 -1
- package/dist/servertool/engine.js +146 -21
- package/dist/servertool/handlers/clock-auto.js +11 -6
- package/dist/servertool/handlers/clock.js +36 -10
- package/dist/servertool/handlers/followup-request-builder.js +8 -2
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +15 -9
- package/dist/servertool/handlers/iflow-model-error-retry.js +6 -4
- package/dist/servertool/handlers/recursive-detection-guard.js +4 -2
- package/dist/servertool/handlers/stop-message-auto.js +100 -10
- package/dist/servertool/handlers/vision.js +4 -1
- package/dist/servertool/handlers/web-search.js +3 -1
- package/dist/servertool/pending-session.d.ts +19 -0
- package/dist/servertool/pending-session.js +97 -0
- package/dist/servertool/reenter-backend.js +5 -3
- package/dist/servertool/server-side-tools.js +235 -6
- package/dist/servertool/types.d.ts +13 -0
- package/dist/sse/json-to-sse/event-generators/responses.js +1 -1
- package/dist/sse/shared/chat-serializer.js +2 -2
- package/dist/sse/shared/constants.js +1 -1
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +7 -1
- package/dist/sse/sse-to-json/builders/response-builder.js +16 -0
- package/dist/sse/sse-to-json/responses-sse-to-json-converter.d.ts +1 -1
- package/dist/tools/apply-patch/execution-capturer.js +1 -1
- package/dist/tools/exec-command/normalize.js +4 -0
- package/dist/tools/exec-command/regression-capturer.js +1 -1
- package/package.json +10 -5
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { runChatRequestToolFilters } from '../../shared/tool-filter-pipeline.js';
|
|
2
2
|
import { ToolGovernanceEngine } from '../tool-governance/index.js';
|
|
3
|
+
import { readRuntimeMetadata } from '../../shared/runtime-metadata.js';
|
|
3
4
|
import { ensureApplyPatchSchema } from '../../shared/tool-mapping.js';
|
|
4
5
|
import { normalizeApplyPatchToolCallsOnRequest } from '../../shared/tool-governor.js';
|
|
5
|
-
import { clearClockSession,
|
|
6
|
+
import { clearClockSession, resolveClockConfig, reserveDueTasksForRequest, startClockDaemonIfNeeded } from '../../../servertool/clock/task-store.js';
|
|
7
|
+
import { logClock } from '../../../servertool/clock/log.js';
|
|
8
|
+
import { clearPendingServerToolInjection, loadPendingServerToolInjection } from '../../../servertool/pending-session.js';
|
|
6
9
|
import { isJsonObject } from '../types/json.js';
|
|
7
10
|
import { applyHubOperations } from '../ops/operations.js';
|
|
8
11
|
const toolGovernanceEngine = new ToolGovernanceEngine();
|
|
@@ -83,6 +86,9 @@ async function applyRequestToolGovernance(request, context) {
|
|
|
83
86
|
messages: stripHistoricalImageAttachments(merged.messages)
|
|
84
87
|
};
|
|
85
88
|
merged = normalizeApplyPatchToolCallsOnRequest(merged);
|
|
89
|
+
// Mixed-tools support: if previous response executed servertools alongside client tools,
|
|
90
|
+
// inject servertool tool_call + tool_result messages after the client's tool results on next request.
|
|
91
|
+
merged = await maybeInjectPendingServerToolResultsAfterClientTools(merged, metadata);
|
|
86
92
|
if (containsImageAttachment(merged.messages)) {
|
|
87
93
|
if (!merged.metadata) {
|
|
88
94
|
merged.metadata = {
|
|
@@ -106,7 +112,7 @@ async function applyRequestToolGovernance(request, context) {
|
|
|
106
112
|
// Server-side web_search tool injection (config-driven, best-effort).
|
|
107
113
|
merged = applyHubOperations(merged, buildWebSearchOperations(merged, metadata));
|
|
108
114
|
// Server-side clock tool + scheduled reminders injection (config-driven, best-effort).
|
|
109
|
-
merged = applyHubOperations(merged, buildClockOperations(metadata));
|
|
115
|
+
merged = applyHubOperations(merged, buildClockOperations(merged, metadata));
|
|
110
116
|
merged = await maybeInjectClockRemindersAndApplyDirectives(merged, metadata, context.requestId);
|
|
111
117
|
const { request: sanitized, summary } = toolGovernanceEngine.governRequest(merged, providerProtocol);
|
|
112
118
|
if (summary.applied) {
|
|
@@ -118,7 +124,191 @@ async function applyRequestToolGovernance(request, context) {
|
|
|
118
124
|
}
|
|
119
125
|
};
|
|
120
126
|
}
|
|
121
|
-
return sanitized;
|
|
127
|
+
return pruneUndeclaredToolHistory(sanitized);
|
|
128
|
+
}
|
|
129
|
+
function resolveSessionIdForPending(metadata, request) {
|
|
130
|
+
const candidate = readString(metadata.sessionId) ?? readString(request.metadata?.sessionId);
|
|
131
|
+
return candidate && candidate.trim() ? candidate.trim() : null;
|
|
132
|
+
}
|
|
133
|
+
function pruneUndeclaredToolHistory(request) {
|
|
134
|
+
const tools = Array.isArray(request.tools) ? request.tools : [];
|
|
135
|
+
const allowedToolNames = new Set();
|
|
136
|
+
for (const tool of tools) {
|
|
137
|
+
const name = tool && typeof tool === 'object' && tool.function && typeof tool.function.name === 'string'
|
|
138
|
+
? String(tool.function.name).trim()
|
|
139
|
+
: '';
|
|
140
|
+
if (name) {
|
|
141
|
+
allowedToolNames.add(name);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// If no tools are declared, do not carry any tool calls/results in history.
|
|
145
|
+
const hasAnyTools = allowedToolNames.size > 0;
|
|
146
|
+
let prunedToolCalls = 0;
|
|
147
|
+
let prunedToolResults = 0;
|
|
148
|
+
const prunedToolNames = new Set();
|
|
149
|
+
const keptToolCallIds = new Set();
|
|
150
|
+
const nextMessages = [];
|
|
151
|
+
for (const msg of request.messages) {
|
|
152
|
+
if (!msg || typeof msg !== 'object')
|
|
153
|
+
continue;
|
|
154
|
+
if (msg.role === 'assistant' && Array.isArray(msg.tool_calls) && msg.tool_calls.length) {
|
|
155
|
+
if (!hasAnyTools) {
|
|
156
|
+
prunedToolCalls += msg.tool_calls.length;
|
|
157
|
+
for (const tc of msg.tool_calls) {
|
|
158
|
+
const n = tc && tc.function && typeof tc.function.name === 'string' ? tc.function.name.trim() : '';
|
|
159
|
+
if (n)
|
|
160
|
+
prunedToolNames.add(n);
|
|
161
|
+
}
|
|
162
|
+
// Drop tool_calls when no tools are declared.
|
|
163
|
+
const keptContent = msg.content;
|
|
164
|
+
const hasContent = typeof keptContent === 'string'
|
|
165
|
+
? keptContent.trim().length > 0
|
|
166
|
+
: Array.isArray(keptContent)
|
|
167
|
+
? keptContent.length > 0
|
|
168
|
+
: false;
|
|
169
|
+
if (hasContent) {
|
|
170
|
+
nextMessages.push({ ...msg, tool_calls: [] });
|
|
171
|
+
}
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const keptCalls = [];
|
|
175
|
+
for (const tc of msg.tool_calls) {
|
|
176
|
+
if (!tc || typeof tc !== 'object')
|
|
177
|
+
continue;
|
|
178
|
+
const name = tc.function && typeof tc.function.name === 'string' ? tc.function.name.trim() : '';
|
|
179
|
+
if (!name || !allowedToolNames.has(name)) {
|
|
180
|
+
prunedToolCalls += 1;
|
|
181
|
+
if (name)
|
|
182
|
+
prunedToolNames.add(name);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
keptCalls.push(tc);
|
|
186
|
+
if (typeof tc.id === 'string' && tc.id.trim()) {
|
|
187
|
+
keptToolCallIds.add(tc.id.trim());
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const keptContent = msg.content;
|
|
191
|
+
const hasContent = typeof keptContent === 'string'
|
|
192
|
+
? keptContent.trim().length > 0
|
|
193
|
+
: Array.isArray(keptContent)
|
|
194
|
+
? keptContent.length > 0
|
|
195
|
+
: false;
|
|
196
|
+
if (!hasContent && keptCalls.length === 0) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (keptCalls.length === msg.tool_calls.length) {
|
|
200
|
+
nextMessages.push(msg);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
nextMessages.push({ ...msg, tool_calls: keptCalls.length ? keptCalls : undefined });
|
|
204
|
+
}
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (msg.role === 'tool') {
|
|
208
|
+
const name = typeof msg.name === 'string' ? msg.name.trim() : '';
|
|
209
|
+
const toolCallId = typeof msg.tool_call_id === 'string' ? msg.tool_call_id.trim() : '';
|
|
210
|
+
if (!hasAnyTools) {
|
|
211
|
+
prunedToolResults += 1;
|
|
212
|
+
if (name)
|
|
213
|
+
prunedToolNames.add(name);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (name && !allowedToolNames.has(name)) {
|
|
217
|
+
prunedToolResults += 1;
|
|
218
|
+
prunedToolNames.add(name);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
// If the result references a tool_call_id that no longer exists, drop it to avoid
|
|
222
|
+
// provider-side strict pairing errors (Gemini/Anthropic tool pairing).
|
|
223
|
+
if (toolCallId && !keptToolCallIds.has(toolCallId)) {
|
|
224
|
+
prunedToolResults += 1;
|
|
225
|
+
if (name)
|
|
226
|
+
prunedToolNames.add(name);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
nextMessages.push(msg);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
nextMessages.push(msg);
|
|
233
|
+
}
|
|
234
|
+
if (prunedToolCalls === 0 && prunedToolResults === 0) {
|
|
235
|
+
return request;
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
...request,
|
|
239
|
+
messages: nextMessages,
|
|
240
|
+
metadata: {
|
|
241
|
+
...request.metadata,
|
|
242
|
+
toolHistoryPrune: {
|
|
243
|
+
prunedToolCalls,
|
|
244
|
+
prunedToolResults,
|
|
245
|
+
prunedToolNames: Array.from(prunedToolNames).sort()
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function extractToolCallIdsFromToolMessages(messages) {
|
|
251
|
+
const ids = new Set();
|
|
252
|
+
for (const msg of messages) {
|
|
253
|
+
if (!msg || typeof msg !== 'object')
|
|
254
|
+
continue;
|
|
255
|
+
if (msg.role !== 'tool')
|
|
256
|
+
continue;
|
|
257
|
+
const toolCallId = typeof msg.tool_call_id === 'string'
|
|
258
|
+
? String(msg.tool_call_id).trim()
|
|
259
|
+
: typeof msg.toolCallId === 'string'
|
|
260
|
+
? String(msg.toolCallId).trim()
|
|
261
|
+
: '';
|
|
262
|
+
if (toolCallId)
|
|
263
|
+
ids.add(toolCallId);
|
|
264
|
+
}
|
|
265
|
+
return ids;
|
|
266
|
+
}
|
|
267
|
+
function findLastToolMessageIndex(messages) {
|
|
268
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
269
|
+
const msg = messages[i];
|
|
270
|
+
if (msg && typeof msg === 'object' && msg.role === 'tool') {
|
|
271
|
+
return i;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return -1;
|
|
275
|
+
}
|
|
276
|
+
async function maybeInjectPendingServerToolResultsAfterClientTools(request, metadata) {
|
|
277
|
+
const sessionId = resolveSessionIdForPending(metadata, request);
|
|
278
|
+
if (!sessionId) {
|
|
279
|
+
return request;
|
|
280
|
+
}
|
|
281
|
+
const pending = await loadPendingServerToolInjection(sessionId);
|
|
282
|
+
if (!pending) {
|
|
283
|
+
return request;
|
|
284
|
+
}
|
|
285
|
+
const afterIds = Array.isArray(pending.afterToolCallIds) ? pending.afterToolCallIds : [];
|
|
286
|
+
if (!afterIds.length) {
|
|
287
|
+
return request;
|
|
288
|
+
}
|
|
289
|
+
const messages = Array.isArray(request.messages) ? request.messages : [];
|
|
290
|
+
const toolIds = extractToolCallIdsFromToolMessages(messages);
|
|
291
|
+
const ready = afterIds.every((id) => toolIds.has(id));
|
|
292
|
+
if (!ready) {
|
|
293
|
+
return request;
|
|
294
|
+
}
|
|
295
|
+
const insertAt = findLastToolMessageIndex(messages);
|
|
296
|
+
if (insertAt < 0) {
|
|
297
|
+
return request;
|
|
298
|
+
}
|
|
299
|
+
const inject = Array.isArray(pending.messages) ? pending.messages : [];
|
|
300
|
+
if (!inject.length) {
|
|
301
|
+
return request;
|
|
302
|
+
}
|
|
303
|
+
const nextMessages = messages.slice();
|
|
304
|
+
nextMessages.splice(insertAt + 1, 0, ...inject);
|
|
305
|
+
try {
|
|
306
|
+
await clearPendingServerToolInjection(sessionId);
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// best-effort
|
|
310
|
+
}
|
|
311
|
+
return { ...request, messages: nextMessages };
|
|
122
312
|
}
|
|
123
313
|
function buildProcessedRequest(request) {
|
|
124
314
|
const timestamp = Date.now();
|
|
@@ -423,13 +613,38 @@ function buildToolChoiceOperations(toolChoice) {
|
|
|
423
613
|
}
|
|
424
614
|
];
|
|
425
615
|
}
|
|
616
|
+
function isGeminiAntigravityLike(metadata) {
|
|
617
|
+
const providerProtocol = readString(metadata.providerProtocol) ?? readString(metadata.provider) ?? '';
|
|
618
|
+
if (!providerProtocol.toLowerCase().includes('gemini')) {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
const rt = readRuntimeMetadata(metadata);
|
|
622
|
+
const providerId = readString(metadata.providerId) ??
|
|
623
|
+
readString(rt?.providerId) ??
|
|
624
|
+
'';
|
|
625
|
+
const providerKey = readString(metadata.providerKey) ??
|
|
626
|
+
readString(rt?.providerKey) ??
|
|
627
|
+
'';
|
|
628
|
+
const loweredId = providerId.toLowerCase();
|
|
629
|
+
const loweredKey = providerKey.toLowerCase();
|
|
630
|
+
return (loweredId.includes('antigravity') ||
|
|
631
|
+
loweredId === 'gemini-cli' ||
|
|
632
|
+
loweredKey.startsWith('antigravity.') ||
|
|
633
|
+
loweredKey.startsWith('gemini-cli.'));
|
|
634
|
+
}
|
|
426
635
|
function buildWebSearchOperations(request, metadata) {
|
|
636
|
+
const rt = readRuntimeMetadata(metadata);
|
|
637
|
+
// gcli2api-style: do not force-inject server-side web_search tools for Gemini/Antigravity.
|
|
638
|
+
// Tools must be present only when supplied by the client payload/config, not auto-appended.
|
|
639
|
+
if (isGeminiAntigravityLike(metadata)) {
|
|
640
|
+
return [];
|
|
641
|
+
}
|
|
427
642
|
// ServerTool 二/三跳(serverToolFollowup=true)不再注入 web_search 工具,
|
|
428
643
|
// 以避免在 web_search 流程内部形成循环命中。
|
|
429
|
-
if (
|
|
644
|
+
if (rt?.serverToolFollowup === true) {
|
|
430
645
|
return [];
|
|
431
646
|
}
|
|
432
|
-
const rawConfig =
|
|
647
|
+
const rawConfig = rt?.webSearch;
|
|
433
648
|
if (!rawConfig || !Array.isArray(rawConfig.engines) || rawConfig.engines.length === 0) {
|
|
434
649
|
return [];
|
|
435
650
|
}
|
|
@@ -535,12 +750,20 @@ function buildWebSearchOperations(request, metadata) {
|
|
|
535
750
|
});
|
|
536
751
|
return ops;
|
|
537
752
|
}
|
|
538
|
-
function buildClockOperations(metadata) {
|
|
539
|
-
const
|
|
540
|
-
|
|
753
|
+
function buildClockOperations(request, metadata) {
|
|
754
|
+
const rt = readRuntimeMetadata(metadata);
|
|
755
|
+
// gcli2api-style: do not force-inject server-side clock tools for Gemini/Antigravity.
|
|
756
|
+
if (isGeminiAntigravityLike(metadata)) {
|
|
757
|
+
return [];
|
|
758
|
+
}
|
|
759
|
+
const rawConfig = rt?.clock;
|
|
760
|
+
const clockConfig = resolveClockConfig(rawConfig);
|
|
541
761
|
if (!clockConfig) {
|
|
542
762
|
return [];
|
|
543
763
|
}
|
|
764
|
+
const sessionId = readString(metadata.sessionId);
|
|
765
|
+
const hasSessionId = Boolean(sessionId && sessionId.trim());
|
|
766
|
+
logClock('inject_schema', { hasSessionId });
|
|
544
767
|
const parameters = {
|
|
545
768
|
type: 'object',
|
|
546
769
|
properties: {
|
|
@@ -568,12 +791,13 @@ function buildClockOperations(metadata) {
|
|
|
568
791
|
description: 'Optional suggested tool name (hint only).'
|
|
569
792
|
},
|
|
570
793
|
arguments: {
|
|
571
|
-
type: '
|
|
572
|
-
description: 'Optional suggested tool arguments (hint only).'
|
|
573
|
-
additionalProperties: true
|
|
794
|
+
type: 'string',
|
|
795
|
+
description: 'Optional suggested tool arguments as a JSON string (hint only). Use "{}" when unsure.'
|
|
574
796
|
}
|
|
575
797
|
},
|
|
576
|
-
|
|
798
|
+
// For strict tool schemas (OpenAI Responses), required must include every key in properties.
|
|
799
|
+
// We keep tool/arguments as hints but allow empty string when not used.
|
|
800
|
+
required: ['dueAt', 'task', 'tool', 'arguments'],
|
|
577
801
|
additionalProperties: false
|
|
578
802
|
}
|
|
579
803
|
},
|
|
@@ -582,7 +806,9 @@ function buildClockOperations(metadata) {
|
|
|
582
806
|
description: 'For cancel: taskId to remove.'
|
|
583
807
|
}
|
|
584
808
|
},
|
|
585
|
-
required
|
|
809
|
+
// For strict tool schemas (OpenAI Responses), required must include every key in properties.
|
|
810
|
+
// Fields unused for certain actions can be set to empty string / empty list.
|
|
811
|
+
required: ['action', 'items', 'taskId'],
|
|
586
812
|
additionalProperties: false
|
|
587
813
|
};
|
|
588
814
|
const clockTool = {
|
|
@@ -595,7 +821,10 @@ function buildClockOperations(metadata) {
|
|
|
595
821
|
}
|
|
596
822
|
};
|
|
597
823
|
return [
|
|
598
|
-
{
|
|
824
|
+
{
|
|
825
|
+
op: 'set_request_metadata_fields',
|
|
826
|
+
fields: { clockEnabled: true, ...(hasSessionId ? { serverToolRequired: true } : {}) }
|
|
827
|
+
},
|
|
599
828
|
{ op: 'append_tool_if_missing', toolName: 'clock', tool: clockTool }
|
|
600
829
|
];
|
|
601
830
|
}
|
|
@@ -657,8 +886,13 @@ function findLastUserMessageIndex(messages) {
|
|
|
657
886
|
return -1;
|
|
658
887
|
}
|
|
659
888
|
async function maybeInjectClockRemindersAndApplyDirectives(request, metadata, requestId) {
|
|
660
|
-
const
|
|
661
|
-
|
|
889
|
+
const rt = readRuntimeMetadata(metadata);
|
|
890
|
+
// Do not inject reminders or apply clock directives during internal servertool followup hops.
|
|
891
|
+
if (rt?.serverToolFollowup === true) {
|
|
892
|
+
return request;
|
|
893
|
+
}
|
|
894
|
+
const rawConfig = rt?.clock;
|
|
895
|
+
const clockConfig = resolveClockConfig(rawConfig);
|
|
662
896
|
if (!clockConfig) {
|
|
663
897
|
return request;
|
|
664
898
|
}
|
|
@@ -687,6 +921,7 @@ async function maybeInjectClockRemindersAndApplyDirectives(request, metadata, re
|
|
|
687
921
|
if (sessionId) {
|
|
688
922
|
try {
|
|
689
923
|
await clearClockSession(sessionId);
|
|
924
|
+
logClock('cleared', { sessionId });
|
|
690
925
|
}
|
|
691
926
|
catch {
|
|
692
927
|
// best-effort: user directive should not crash request
|
|
@@ -707,6 +942,11 @@ async function maybeInjectClockRemindersAndApplyDirectives(request, metadata, re
|
|
|
707
942
|
if (!reservation || typeof injectText !== 'string' || !injectText.trim()) {
|
|
708
943
|
return request;
|
|
709
944
|
}
|
|
945
|
+
logClock('inject_due', {
|
|
946
|
+
sessionId,
|
|
947
|
+
reservationId: reservation.reservationId,
|
|
948
|
+
taskIds: Array.isArray(reservation.taskIds) ? reservation.taskIds.length : 0
|
|
949
|
+
});
|
|
710
950
|
const baseMetadata = request.metadata && typeof request.metadata === 'object'
|
|
711
951
|
? request.metadata
|
|
712
952
|
: {
|
|
@@ -10,6 +10,14 @@ export interface ProviderResponseConversionOptions {
|
|
|
10
10
|
context: AdapterContext;
|
|
11
11
|
entryEndpoint: string;
|
|
12
12
|
wantsStream: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Canonical chat semantics from the request-side chat_process output.
|
|
15
|
+
* This is the only allowed carrier for mappable cross-protocol semantics
|
|
16
|
+
* (tool alias maps, client tool schemas, responses resume, etc.).
|
|
17
|
+
*
|
|
18
|
+
* Must not be stuffed into metadata/AdapterContext.
|
|
19
|
+
*/
|
|
20
|
+
requestSemantics?: JsonObject;
|
|
13
21
|
stageRecorder?: StageRecorder;
|
|
14
22
|
providerInvoker?: ProviderInvoker;
|
|
15
23
|
/**
|
|
@@ -8,12 +8,15 @@ import { OpenAIChatResponseMapper, ResponsesResponseMapper, AnthropicResponseMap
|
|
|
8
8
|
import { runRespInboundStage1SseDecode } from '../pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js';
|
|
9
9
|
import { runRespInboundStage2FormatParse } from '../pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.js';
|
|
10
10
|
import { runRespInboundStage3SemanticMap } from '../pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.js';
|
|
11
|
+
import { runRespInboundStage3ThoughtSignatureCapture } from '../pipeline/stages/resp_inbound/resp_inbound_stage3_thought_signature_capture/index.js';
|
|
11
12
|
import { runRespInboundStageCompatResponse } from '../pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js';
|
|
12
13
|
import { runRespProcessStage1ToolGovernance } from '../pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js';
|
|
13
14
|
import { runRespProcessStage2Finalize } from '../pipeline/stages/resp_process/resp_process_stage2_finalize/index.js';
|
|
14
15
|
import { runRespOutboundStage1ClientRemap } from '../pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js';
|
|
15
16
|
import { runRespOutboundStage2SseStream } from '../pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js';
|
|
16
17
|
import { recordResponsesResponse } from '../../shared/responses-conversation-store.js';
|
|
18
|
+
import { ProviderProtocolError } from '../../shared/errors.js';
|
|
19
|
+
import { readRuntimeMetadata } from '../../shared/runtime-metadata.js';
|
|
17
20
|
import { runServerToolOrchestration } from '../../../servertool/engine.js';
|
|
18
21
|
import { commitClockReservation, normalizeClockConfig } from '../../../servertool/clock/task-store.js';
|
|
19
22
|
const PROVIDER_RESPONSE_REGISTRY = {
|
|
@@ -35,7 +38,8 @@ const PROVIDER_RESPONSE_REGISTRY = {
|
|
|
35
38
|
}
|
|
36
39
|
};
|
|
37
40
|
function isServerToolFollowup(context) {
|
|
38
|
-
const
|
|
41
|
+
const rt = readRuntimeMetadata(context);
|
|
42
|
+
const raw = rt?.serverToolFollowup;
|
|
39
43
|
if (raw === true) {
|
|
40
44
|
return true;
|
|
41
45
|
}
|
|
@@ -85,7 +89,8 @@ function coerceClockReservation(value) {
|
|
|
85
89
|
}
|
|
86
90
|
async function maybeCommitClockReservationFromContext(context) {
|
|
87
91
|
try {
|
|
88
|
-
const
|
|
92
|
+
const rt = readRuntimeMetadata(context);
|
|
93
|
+
const clockConfig = normalizeClockConfig(rt?.clock);
|
|
89
94
|
if (!clockConfig) {
|
|
90
95
|
return;
|
|
91
96
|
}
|
|
@@ -114,6 +119,42 @@ function detectProviderResponseShape(payload) {
|
|
|
114
119
|
return 'gemini-chat';
|
|
115
120
|
return 'unknown';
|
|
116
121
|
}
|
|
122
|
+
function isCanonicalChatCompletion(payload) {
|
|
123
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
const obj = payload;
|
|
127
|
+
const choices = Array.isArray(obj.choices) ? obj.choices : [];
|
|
128
|
+
if (!choices.length)
|
|
129
|
+
return false;
|
|
130
|
+
const first = choices[0] && typeof choices[0] === 'object' && !Array.isArray(choices[0]) ? choices[0] : null;
|
|
131
|
+
if (!first)
|
|
132
|
+
return false;
|
|
133
|
+
const msg = first.message;
|
|
134
|
+
return Boolean(msg && typeof msg === 'object' && !Array.isArray(msg));
|
|
135
|
+
}
|
|
136
|
+
async function coerceClientPayloadToCanonicalChatCompletionOrThrow(options) {
|
|
137
|
+
if (isCanonicalChatCompletion(options.payload)) {
|
|
138
|
+
return options.payload;
|
|
139
|
+
}
|
|
140
|
+
const detected = detectProviderResponseShape(options.payload);
|
|
141
|
+
if (detected === 'unknown') {
|
|
142
|
+
throw new ProviderProtocolError(`[hub_response] Non-canonical response payload at ${options.scope}`, {
|
|
143
|
+
code: 'MALFORMED_RESPONSE',
|
|
144
|
+
details: { detected }
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const plan = PROVIDER_RESPONSE_REGISTRY[detected];
|
|
148
|
+
const mapper = plan.createMapper();
|
|
149
|
+
const coerced = await mapper.toChatCompletion({ payload: options.payload }, options.adapterContext, { requestSemantics: options.requestSemantics });
|
|
150
|
+
if (isCanonicalChatCompletion(coerced)) {
|
|
151
|
+
return coerced;
|
|
152
|
+
}
|
|
153
|
+
throw new ProviderProtocolError(`[hub_response] Failed to canonicalize response payload at ${options.scope}`, {
|
|
154
|
+
code: 'MALFORMED_RESPONSE',
|
|
155
|
+
details: { detected }
|
|
156
|
+
});
|
|
157
|
+
}
|
|
117
158
|
function summarizeToolCallsFromProviderResponse(payload) {
|
|
118
159
|
try {
|
|
119
160
|
if (!isJsonRecord(payload))
|
|
@@ -169,6 +210,7 @@ function stripInternalPolicyDebugFields(payload) {
|
|
|
169
210
|
delete target.__responses_reasoning;
|
|
170
211
|
delete target.__responses_payload_snapshot;
|
|
171
212
|
delete target.__responses_passthrough;
|
|
213
|
+
delete target.anthropicToolNameMap;
|
|
172
214
|
}
|
|
173
215
|
function supportsSseProtocol(protocol) {
|
|
174
216
|
return protocol === 'openai-chat' || protocol === 'openai-responses' || protocol === 'anthropic-messages' || protocol === 'gemini-chat';
|
|
@@ -212,23 +254,20 @@ function resolveChatReasoningMode(_entryEndpoint) {
|
|
|
212
254
|
return 'keep';
|
|
213
255
|
}
|
|
214
256
|
export async function convertProviderResponse(options) {
|
|
215
|
-
const
|
|
257
|
+
const isFollowup = isServerToolFollowup(options.context);
|
|
258
|
+
// ServerTool followups are internal hops. They must return canonical OpenAI-chat-like payloads
|
|
259
|
+
// to re-enter the hub chat process deterministically (tools harvesting, governance, etc.).
|
|
260
|
+
// Client protocol remapping happens only on the outermost request.
|
|
261
|
+
const clientProtocol = isFollowup ? 'openai-chat' : resolveClientProtocol(options.entryEndpoint);
|
|
216
262
|
const hasServerToolSupport = Boolean(options.providerInvoker) || Boolean(options.reenterPipeline);
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
// 由具体 handler(例如 gemini_empty_reply_continue / iflow_model_error_retry 等)
|
|
221
|
-
// 自行通过 serverToolFollowup 标记决定是否生效。
|
|
222
|
-
const skipServerTools = !hasServerToolSupport;
|
|
223
|
-
// 对于由 server-side 工具触发的内部跳转(二跳/三跳),统一禁用 SSE 聚合输出,
|
|
224
|
-
// 始终返回完整的 ChatCompletion JSON,便于在 llms 内部直接解析,而不是拿到
|
|
225
|
-
// __sse_responses 可读流。
|
|
226
|
-
const wantsStream = isServerToolFollowup(options.context) ? false : options.wantsStream;
|
|
263
|
+
// ServerTool followup hop: always return a fully materialized ChatCompletion JSON (non-SSE).
|
|
264
|
+
// This is an internal hop used by the servertool engine, and it must be deterministic.
|
|
265
|
+
const wantsStream = isFollowup ? false : options.wantsStream;
|
|
227
266
|
try {
|
|
228
267
|
// eslint-disable-next-line no-console
|
|
229
268
|
console.log(`\x1b[38;5;33m[servertool][orchestrator][debug] requestId=${options.context.requestId} ` +
|
|
230
269
|
`protocol=${options.providerProtocol} endpoint=${options.entryEndpoint} ` +
|
|
231
|
-
`
|
|
270
|
+
`serverTools=${hasServerToolSupport} hasInvoker=${Boolean(options.providerInvoker)} ` +
|
|
232
271
|
`hasReenter=${Boolean(options.reenterPipeline)}\x1b[0m`);
|
|
233
272
|
}
|
|
234
273
|
catch {
|
|
@@ -255,23 +294,17 @@ export async function convertProviderResponse(options) {
|
|
|
255
294
|
formatAdapter,
|
|
256
295
|
stageRecorder: options.stageRecorder
|
|
257
296
|
});
|
|
258
|
-
if (options.providerProtocol === 'openai-responses') {
|
|
259
|
-
try {
|
|
260
|
-
recordResponsesResponse({
|
|
261
|
-
requestId: options.context.requestId,
|
|
262
|
-
response: formatEnvelope.payload
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
catch {
|
|
266
|
-
// ignore conversation capture errors
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
297
|
formatEnvelope.payload = runRespInboundStageCompatResponse({
|
|
270
298
|
payload: formatEnvelope.payload,
|
|
271
299
|
adapterContext: options.context,
|
|
272
300
|
stageRecorder: options.stageRecorder
|
|
273
301
|
});
|
|
274
302
|
stripInternalPolicyDebugFields(formatEnvelope.payload);
|
|
303
|
+
formatEnvelope.payload = await runRespInboundStage3ThoughtSignatureCapture({
|
|
304
|
+
payload: formatEnvelope.payload,
|
|
305
|
+
adapterContext: options.context,
|
|
306
|
+
stageRecorder: options.stageRecorder
|
|
307
|
+
});
|
|
275
308
|
// Phase 2 (shadow): response tool surface mismatch detection (provider inbound).
|
|
276
309
|
// Only records diffs; does not rewrite payload.
|
|
277
310
|
try {
|
|
@@ -311,14 +344,15 @@ export async function convertProviderResponse(options) {
|
|
|
311
344
|
adapterContext: options.context,
|
|
312
345
|
formatEnvelope,
|
|
313
346
|
mapper,
|
|
347
|
+
requestSemantics: options.requestSemantics,
|
|
314
348
|
stageRecorder: options.stageRecorder
|
|
315
349
|
});
|
|
316
350
|
// 记录语义映射后的 ChatCompletion,便于回放 server-side 工具流程。
|
|
317
|
-
recordStage(options.stageRecorder, '
|
|
351
|
+
recordStage(options.stageRecorder, 'chat_process.resp.stage4.semantic_map_to_chat.chat', chatResponse);
|
|
318
352
|
// 检查是否需要进行 ServerTool 编排
|
|
319
353
|
// 使用新的 ChatEnvelope 级别的 servertool 实现
|
|
320
354
|
let effectiveChatResponse = chatResponse;
|
|
321
|
-
if (
|
|
355
|
+
if (hasServerToolSupport) {
|
|
322
356
|
try {
|
|
323
357
|
// eslint-disable-next-line no-console
|
|
324
358
|
console.log(`\x1b[38;5;33m[servertool][orchestrator] start requestId=${options.context.requestId} ` +
|
|
@@ -348,6 +382,12 @@ export async function convertProviderResponse(options) {
|
|
|
348
382
|
/* logging best-effort */
|
|
349
383
|
}
|
|
350
384
|
effectiveChatResponse = orchestration.chat;
|
|
385
|
+
recordStage(options.stageRecorder, 'chat_process.resp.stage5.servertool_orchestration', {
|
|
386
|
+
executed: true,
|
|
387
|
+
flowId: orchestration.flowId,
|
|
388
|
+
inputShape: detectProviderResponseShape(chatResponse),
|
|
389
|
+
outputShape: detectProviderResponseShape(effectiveChatResponse)
|
|
390
|
+
});
|
|
351
391
|
}
|
|
352
392
|
else {
|
|
353
393
|
try {
|
|
@@ -358,8 +398,20 @@ export async function convertProviderResponse(options) {
|
|
|
358
398
|
catch {
|
|
359
399
|
/* logging best-effort */
|
|
360
400
|
}
|
|
401
|
+
recordStage(options.stageRecorder, 'chat_process.resp.stage5.servertool_orchestration', {
|
|
402
|
+
executed: false,
|
|
403
|
+
inputShape: detectProviderResponseShape(chatResponse)
|
|
404
|
+
});
|
|
361
405
|
}
|
|
362
406
|
}
|
|
407
|
+
// Hard gate: response-side chat_process requires an OpenAI-chat-like surface (choices[].message).
|
|
408
|
+
// ServerTool followups must never replace the canonical chat completion with a client-protocol shape.
|
|
409
|
+
effectiveChatResponse = await coerceClientPayloadToCanonicalChatCompletionOrThrow({
|
|
410
|
+
payload: effectiveChatResponse,
|
|
411
|
+
adapterContext: options.context,
|
|
412
|
+
requestSemantics: options.requestSemantics,
|
|
413
|
+
scope: 'chat_process.response.entry'
|
|
414
|
+
});
|
|
363
415
|
// 如果没有执行 servertool,继续原来的处理流程
|
|
364
416
|
const governanceResult = await runRespProcessStage1ToolGovernance({
|
|
365
417
|
payload: effectiveChatResponse,
|
|
@@ -382,10 +434,22 @@ export async function convertProviderResponse(options) {
|
|
|
382
434
|
clientProtocol,
|
|
383
435
|
requestId: clientFacingRequestId,
|
|
384
436
|
adapterContext: options.context,
|
|
437
|
+
requestSemantics: options.requestSemantics,
|
|
385
438
|
stageRecorder: options.stageRecorder
|
|
386
439
|
});
|
|
387
440
|
applyModelOverride(clientPayload, displayModel);
|
|
388
441
|
stripInternalPolicyDebugFields(clientPayload);
|
|
442
|
+
if (clientProtocol === 'openai-responses') {
|
|
443
|
+
try {
|
|
444
|
+
recordResponsesResponse({
|
|
445
|
+
requestId: options.context.requestId,
|
|
446
|
+
response: clientPayload
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
// ignore conversation capture errors
|
|
451
|
+
}
|
|
452
|
+
}
|
|
389
453
|
// Phase 2 (shadow): response tool surface mismatch detection (client outbound).
|
|
390
454
|
try {
|
|
391
455
|
if (options.stageRecorder && isToolSurfaceShadowEnabled()) {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { FormatEnvelope } from '../types/format-envelope.js';
|
|
2
2
|
import type { AdapterContext } from '../types/chat-envelope.js';
|
|
3
3
|
import type { JsonObject } from '../types/json.js';
|
|
4
|
+
import type { ChatSemantics } from '../types/chat-envelope.js';
|
|
4
5
|
export type ChatCompletionLike = JsonObject;
|
|
5
6
|
export interface ResponseMapper {
|
|
6
|
-
toChatCompletion(format: FormatEnvelope, ctx: AdapterContext
|
|
7
|
+
toChatCompletion(format: FormatEnvelope, ctx: AdapterContext, options?: {
|
|
8
|
+
requestSemantics?: ChatSemantics | JsonObject;
|
|
9
|
+
}): Promise<ChatCompletionLike> | ChatCompletionLike;
|
|
7
10
|
}
|
|
8
11
|
export declare class OpenAIChatResponseMapper implements ResponseMapper {
|
|
9
12
|
toChatCompletion(format: FormatEnvelope, _ctx: AdapterContext): ChatCompletionLike;
|
|
@@ -12,8 +15,12 @@ export declare class ResponsesResponseMapper implements ResponseMapper {
|
|
|
12
15
|
toChatCompletion(format: FormatEnvelope, _ctx: AdapterContext): ChatCompletionLike;
|
|
13
16
|
}
|
|
14
17
|
export declare class AnthropicResponseMapper implements ResponseMapper {
|
|
15
|
-
toChatCompletion(format: FormatEnvelope,
|
|
18
|
+
toChatCompletion(format: FormatEnvelope, _ctx: AdapterContext, options?: {
|
|
19
|
+
requestSemantics?: ChatSemantics | JsonObject;
|
|
20
|
+
}): ChatCompletionLike;
|
|
16
21
|
}
|
|
17
22
|
export declare class GeminiResponseMapper implements ResponseMapper {
|
|
18
|
-
toChatCompletion(format: FormatEnvelope,
|
|
23
|
+
toChatCompletion(format: FormatEnvelope, ctx: AdapterContext, options?: {
|
|
24
|
+
requestSemantics?: ChatSemantics | JsonObject;
|
|
25
|
+
}): ChatCompletionLike;
|
|
19
26
|
}
|