@jsonstudio/llms 0.6.1164 → 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.
- 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/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 +523 -50
- 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 +134 -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 +384 -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_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_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/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 +85 -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 +65 -5
- package/dist/router/virtual-router/context-advisor.d.ts +4 -0
- package/dist/router/virtual-router/context-advisor.js +3 -0
- package/dist/router/virtual-router/context-weighted.d.ts +31 -0
- package/dist/router/virtual-router/context-weighted.js +54 -0
- 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 +15 -0
- package/dist/router/virtual-router/engine-selection/alias-selection.js +156 -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 +400 -0
- package/dist/router/virtual-router/engine-selection/tier-selection.d.ts +3 -0
- package/dist/router/virtual-router/engine-selection/tier-selection.js +225 -0
- package/dist/router/virtual-router/engine-selection.d.ts +4 -30
- package/dist/router/virtual-router/engine-selection.js +10 -815
- package/dist/router/virtual-router/engine.d.ts +1 -0
- package/dist/router/virtual-router/engine.js +55 -10
- 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 +53 -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
|
/**
|
|
@@ -14,6 +14,8 @@ import { runRespProcessStage2Finalize } from '../pipeline/stages/resp_process/re
|
|
|
14
14
|
import { runRespOutboundStage1ClientRemap } from '../pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js';
|
|
15
15
|
import { runRespOutboundStage2SseStream } from '../pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js';
|
|
16
16
|
import { recordResponsesResponse } from '../../shared/responses-conversation-store.js';
|
|
17
|
+
import { ProviderProtocolError } from '../../shared/errors.js';
|
|
18
|
+
import { readRuntimeMetadata } from '../../shared/runtime-metadata.js';
|
|
17
19
|
import { runServerToolOrchestration } from '../../../servertool/engine.js';
|
|
18
20
|
import { commitClockReservation, normalizeClockConfig } from '../../../servertool/clock/task-store.js';
|
|
19
21
|
const PROVIDER_RESPONSE_REGISTRY = {
|
|
@@ -35,7 +37,8 @@ const PROVIDER_RESPONSE_REGISTRY = {
|
|
|
35
37
|
}
|
|
36
38
|
};
|
|
37
39
|
function isServerToolFollowup(context) {
|
|
38
|
-
const
|
|
40
|
+
const rt = readRuntimeMetadata(context);
|
|
41
|
+
const raw = rt?.serverToolFollowup;
|
|
39
42
|
if (raw === true) {
|
|
40
43
|
return true;
|
|
41
44
|
}
|
|
@@ -85,7 +88,8 @@ function coerceClockReservation(value) {
|
|
|
85
88
|
}
|
|
86
89
|
async function maybeCommitClockReservationFromContext(context) {
|
|
87
90
|
try {
|
|
88
|
-
const
|
|
91
|
+
const rt = readRuntimeMetadata(context);
|
|
92
|
+
const clockConfig = normalizeClockConfig(rt?.clock);
|
|
89
93
|
if (!clockConfig) {
|
|
90
94
|
return;
|
|
91
95
|
}
|
|
@@ -114,6 +118,42 @@ function detectProviderResponseShape(payload) {
|
|
|
114
118
|
return 'gemini-chat';
|
|
115
119
|
return 'unknown';
|
|
116
120
|
}
|
|
121
|
+
function isCanonicalChatCompletion(payload) {
|
|
122
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const obj = payload;
|
|
126
|
+
const choices = Array.isArray(obj.choices) ? obj.choices : [];
|
|
127
|
+
if (!choices.length)
|
|
128
|
+
return false;
|
|
129
|
+
const first = choices[0] && typeof choices[0] === 'object' && !Array.isArray(choices[0]) ? choices[0] : null;
|
|
130
|
+
if (!first)
|
|
131
|
+
return false;
|
|
132
|
+
const msg = first.message;
|
|
133
|
+
return Boolean(msg && typeof msg === 'object' && !Array.isArray(msg));
|
|
134
|
+
}
|
|
135
|
+
async function coerceClientPayloadToCanonicalChatCompletionOrThrow(options) {
|
|
136
|
+
if (isCanonicalChatCompletion(options.payload)) {
|
|
137
|
+
return options.payload;
|
|
138
|
+
}
|
|
139
|
+
const detected = detectProviderResponseShape(options.payload);
|
|
140
|
+
if (detected === 'unknown') {
|
|
141
|
+
throw new ProviderProtocolError(`[hub_response] Non-canonical response payload at ${options.scope}`, {
|
|
142
|
+
code: 'MALFORMED_RESPONSE',
|
|
143
|
+
details: { detected }
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const plan = PROVIDER_RESPONSE_REGISTRY[detected];
|
|
147
|
+
const mapper = plan.createMapper();
|
|
148
|
+
const coerced = await mapper.toChatCompletion({ payload: options.payload }, options.adapterContext, { requestSemantics: options.requestSemantics });
|
|
149
|
+
if (isCanonicalChatCompletion(coerced)) {
|
|
150
|
+
return coerced;
|
|
151
|
+
}
|
|
152
|
+
throw new ProviderProtocolError(`[hub_response] Failed to canonicalize response payload at ${options.scope}`, {
|
|
153
|
+
code: 'MALFORMED_RESPONSE',
|
|
154
|
+
details: { detected }
|
|
155
|
+
});
|
|
156
|
+
}
|
|
117
157
|
function summarizeToolCallsFromProviderResponse(payload) {
|
|
118
158
|
try {
|
|
119
159
|
if (!isJsonRecord(payload))
|
|
@@ -169,6 +209,7 @@ function stripInternalPolicyDebugFields(payload) {
|
|
|
169
209
|
delete target.__responses_reasoning;
|
|
170
210
|
delete target.__responses_payload_snapshot;
|
|
171
211
|
delete target.__responses_passthrough;
|
|
212
|
+
delete target.anthropicToolNameMap;
|
|
172
213
|
}
|
|
173
214
|
function supportsSseProtocol(protocol) {
|
|
174
215
|
return protocol === 'openai-chat' || protocol === 'openai-responses' || protocol === 'anthropic-messages' || protocol === 'gemini-chat';
|
|
@@ -212,23 +253,20 @@ function resolveChatReasoningMode(_entryEndpoint) {
|
|
|
212
253
|
return 'keep';
|
|
213
254
|
}
|
|
214
255
|
export async function convertProviderResponse(options) {
|
|
215
|
-
const
|
|
256
|
+
const isFollowup = isServerToolFollowup(options.context);
|
|
257
|
+
// ServerTool followups are internal hops. They must return canonical OpenAI-chat-like payloads
|
|
258
|
+
// to re-enter the hub chat process deterministically (tools harvesting, governance, etc.).
|
|
259
|
+
// Client protocol remapping happens only on the outermost request.
|
|
260
|
+
const clientProtocol = isFollowup ? 'openai-chat' : resolveClientProtocol(options.entryEndpoint);
|
|
216
261
|
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;
|
|
262
|
+
// ServerTool followup hop: always return a fully materialized ChatCompletion JSON (non-SSE).
|
|
263
|
+
// This is an internal hop used by the servertool engine, and it must be deterministic.
|
|
264
|
+
const wantsStream = isFollowup ? false : options.wantsStream;
|
|
227
265
|
try {
|
|
228
266
|
// eslint-disable-next-line no-console
|
|
229
267
|
console.log(`\x1b[38;5;33m[servertool][orchestrator][debug] requestId=${options.context.requestId} ` +
|
|
230
268
|
`protocol=${options.providerProtocol} endpoint=${options.entryEndpoint} ` +
|
|
231
|
-
`
|
|
269
|
+
`serverTools=${hasServerToolSupport} hasInvoker=${Boolean(options.providerInvoker)} ` +
|
|
232
270
|
`hasReenter=${Boolean(options.reenterPipeline)}\x1b[0m`);
|
|
233
271
|
}
|
|
234
272
|
catch {
|
|
@@ -255,17 +293,6 @@ export async function convertProviderResponse(options) {
|
|
|
255
293
|
formatAdapter,
|
|
256
294
|
stageRecorder: options.stageRecorder
|
|
257
295
|
});
|
|
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
296
|
formatEnvelope.payload = runRespInboundStageCompatResponse({
|
|
270
297
|
payload: formatEnvelope.payload,
|
|
271
298
|
adapterContext: options.context,
|
|
@@ -311,14 +338,15 @@ export async function convertProviderResponse(options) {
|
|
|
311
338
|
adapterContext: options.context,
|
|
312
339
|
formatEnvelope,
|
|
313
340
|
mapper,
|
|
341
|
+
requestSemantics: options.requestSemantics,
|
|
314
342
|
stageRecorder: options.stageRecorder
|
|
315
343
|
});
|
|
316
344
|
// 记录语义映射后的 ChatCompletion,便于回放 server-side 工具流程。
|
|
317
|
-
recordStage(options.stageRecorder, '
|
|
345
|
+
recordStage(options.stageRecorder, 'chat_process.resp.stage4.semantic_map_to_chat.chat', chatResponse);
|
|
318
346
|
// 检查是否需要进行 ServerTool 编排
|
|
319
347
|
// 使用新的 ChatEnvelope 级别的 servertool 实现
|
|
320
348
|
let effectiveChatResponse = chatResponse;
|
|
321
|
-
if (
|
|
349
|
+
if (hasServerToolSupport) {
|
|
322
350
|
try {
|
|
323
351
|
// eslint-disable-next-line no-console
|
|
324
352
|
console.log(`\x1b[38;5;33m[servertool][orchestrator] start requestId=${options.context.requestId} ` +
|
|
@@ -348,6 +376,12 @@ export async function convertProviderResponse(options) {
|
|
|
348
376
|
/* logging best-effort */
|
|
349
377
|
}
|
|
350
378
|
effectiveChatResponse = orchestration.chat;
|
|
379
|
+
recordStage(options.stageRecorder, 'chat_process.resp.stage5.servertool_orchestration', {
|
|
380
|
+
executed: true,
|
|
381
|
+
flowId: orchestration.flowId,
|
|
382
|
+
inputShape: detectProviderResponseShape(chatResponse),
|
|
383
|
+
outputShape: detectProviderResponseShape(effectiveChatResponse)
|
|
384
|
+
});
|
|
351
385
|
}
|
|
352
386
|
else {
|
|
353
387
|
try {
|
|
@@ -358,8 +392,20 @@ export async function convertProviderResponse(options) {
|
|
|
358
392
|
catch {
|
|
359
393
|
/* logging best-effort */
|
|
360
394
|
}
|
|
395
|
+
recordStage(options.stageRecorder, 'chat_process.resp.stage5.servertool_orchestration', {
|
|
396
|
+
executed: false,
|
|
397
|
+
inputShape: detectProviderResponseShape(chatResponse)
|
|
398
|
+
});
|
|
361
399
|
}
|
|
362
400
|
}
|
|
401
|
+
// Hard gate: response-side chat_process requires an OpenAI-chat-like surface (choices[].message).
|
|
402
|
+
// ServerTool followups must never replace the canonical chat completion with a client-protocol shape.
|
|
403
|
+
effectiveChatResponse = await coerceClientPayloadToCanonicalChatCompletionOrThrow({
|
|
404
|
+
payload: effectiveChatResponse,
|
|
405
|
+
adapterContext: options.context,
|
|
406
|
+
requestSemantics: options.requestSemantics,
|
|
407
|
+
scope: 'chat_process.response.entry'
|
|
408
|
+
});
|
|
363
409
|
// 如果没有执行 servertool,继续原来的处理流程
|
|
364
410
|
const governanceResult = await runRespProcessStage1ToolGovernance({
|
|
365
411
|
payload: effectiveChatResponse,
|
|
@@ -382,10 +428,22 @@ export async function convertProviderResponse(options) {
|
|
|
382
428
|
clientProtocol,
|
|
383
429
|
requestId: clientFacingRequestId,
|
|
384
430
|
adapterContext: options.context,
|
|
431
|
+
requestSemantics: options.requestSemantics,
|
|
385
432
|
stageRecorder: options.stageRecorder
|
|
386
433
|
});
|
|
387
434
|
applyModelOverride(clientPayload, displayModel);
|
|
388
435
|
stripInternalPolicyDebugFields(clientPayload);
|
|
436
|
+
if (clientProtocol === 'openai-responses') {
|
|
437
|
+
try {
|
|
438
|
+
recordResponsesResponse({
|
|
439
|
+
requestId: options.context.requestId,
|
|
440
|
+
response: clientPayload
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
// ignore conversation capture errors
|
|
445
|
+
}
|
|
446
|
+
}
|
|
389
447
|
// Phase 2 (shadow): response tool surface mismatch detection (client outbound).
|
|
390
448
|
try {
|
|
391
449
|
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
|
}
|
|
@@ -12,18 +12,25 @@ export class ResponsesResponseMapper {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
export class AnthropicResponseMapper {
|
|
15
|
-
toChatCompletion(format,
|
|
16
|
-
const aliasMap =
|
|
15
|
+
toChatCompletion(format, _ctx, options) {
|
|
16
|
+
const aliasMap = extractToolNameAliasMapFromSemantics(options?.requestSemantics);
|
|
17
17
|
return buildOpenAIChatFromAnthropicMessage(format.payload ?? {}, { aliasMap });
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
export class GeminiResponseMapper {
|
|
21
|
-
toChatCompletion(format,
|
|
22
|
-
|
|
21
|
+
toChatCompletion(format, ctx, options) {
|
|
22
|
+
const aliasMap = extractToolNameAliasMapFromSemantics(options?.requestSemantics) ?? extractGeminiToolNameAliasMapFromContext(ctx);
|
|
23
|
+
return buildOpenAIChatFromGeminiResponse(format.payload ?? {}, { aliasMap });
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
|
-
function
|
|
26
|
-
|
|
26
|
+
function extractToolNameAliasMapFromSemantics(semantics) {
|
|
27
|
+
if (!semantics || typeof semantics !== 'object' || Array.isArray(semantics)) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const toolsNode = semantics.tools;
|
|
31
|
+
const candidate = toolsNode && typeof toolsNode === 'object' && !Array.isArray(toolsNode)
|
|
32
|
+
? toolsNode.toolNameAliasMap
|
|
33
|
+
: undefined;
|
|
27
34
|
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
|
|
28
35
|
return undefined;
|
|
29
36
|
}
|
|
@@ -41,3 +48,20 @@ function extractAnthropicAliasMap(ctx) {
|
|
|
41
48
|
}
|
|
42
49
|
return Object.keys(normalized).length ? normalized : undefined;
|
|
43
50
|
}
|
|
51
|
+
function extractGeminiToolNameAliasMapFromContext(ctx) {
|
|
52
|
+
const candidate = ctx?.geminiToolNameAliasMap;
|
|
53
|
+
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const normalized = {};
|
|
57
|
+
for (const [key, value] of Object.entries(candidate)) {
|
|
58
|
+
if (typeof key !== 'string' || typeof value !== 'string')
|
|
59
|
+
continue;
|
|
60
|
+
const trimmedKey = key.trim();
|
|
61
|
+
const trimmedValue = value.trim();
|
|
62
|
+
if (!trimmedKey.length || !trimmedValue.length)
|
|
63
|
+
continue;
|
|
64
|
+
normalized[trimmedKey] = trimmedValue;
|
|
65
|
+
}
|
|
66
|
+
return Object.keys(normalized).length ? normalized : undefined;
|
|
67
|
+
}
|