@jsonstudio/rcc 0.89.333 → 0.89.548
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/build-info.js +3 -3
- package/dist/build-info.js.map +1 -1
- package/dist/cli.js +110 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/token-daemon.d.ts +2 -0
- package/dist/commands/token-daemon.js +183 -0
- package/dist/commands/token-daemon.js.map +1 -0
- package/dist/index.js +20 -3
- package/dist/index.js.map +1 -1
- package/dist/modules/llmswitch/bridge.d.ts +1 -1
- package/dist/modules/llmswitch/bridge.js +3 -2
- package/dist/modules/llmswitch/bridge.js.map +1 -1
- package/dist/modules/pipeline/utils/colored-logger.js +3 -1
- package/dist/modules/pipeline/utils/colored-logger.js.map +1 -1
- package/dist/providers/auth/gemini-cli-userinfo-helper.js +12 -2
- package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -1
- package/dist/providers/auth/oauth-lifecycle.js +337 -25
- package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
- package/dist/providers/core/config/oauth-flows.d.ts +23 -0
- package/dist/providers/core/config/oauth-flows.js +92 -5
- package/dist/providers/core/config/oauth-flows.js.map +1 -1
- package/dist/providers/core/config/provider-oauth-configs.js +9 -3
- package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
- package/dist/providers/core/config/service-profiles.js +18 -10
- package/dist/providers/core/config/service-profiles.js.map +1 -1
- package/dist/providers/core/runtime/base-provider.d.ts +2 -0
- package/dist/providers/core/runtime/base-provider.js +35 -1
- package/dist/providers/core/runtime/base-provider.js.map +1 -1
- package/dist/providers/core/runtime/gemini-cli-http-provider.js +87 -20
- package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/http-request-executor.d.ts +1 -0
- package/dist/providers/core/runtime/http-request-executor.js +75 -1
- package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
- package/dist/providers/core/runtime/http-transport-provider.d.ts +2 -0
- package/dist/providers/core/runtime/http-transport-provider.js +60 -2
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/runtime/iflow-http-provider.d.ts +4 -0
- package/dist/providers/core/runtime/iflow-http-provider.js +28 -0
- package/dist/providers/core/runtime/iflow-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/rate-limit-manager.d.ts +30 -0
- package/dist/providers/core/runtime/rate-limit-manager.js +136 -0
- package/dist/providers/core/runtime/rate-limit-manager.js.map +1 -0
- package/dist/providers/core/runtime/responses-provider.js +8 -3
- package/dist/providers/core/runtime/responses-provider.js.map +1 -1
- package/dist/providers/core/runtime/vision-debug-utils.d.ts +13 -0
- package/dist/providers/core/runtime/vision-debug-utils.js +114 -0
- package/dist/providers/core/runtime/vision-debug-utils.js.map +1 -0
- package/dist/providers/core/strategies/oauth-auth-code-flow.js +75 -26
- package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
- package/dist/providers/core/utils/http-client.js +2 -1
- package/dist/providers/core/utils/http-client.js.map +1 -1
- package/dist/providers/core/utils/provider-error-reporter.js +31 -5
- package/dist/providers/core/utils/provider-error-reporter.js.map +1 -1
- package/dist/providers/core/utils/provider-type-utils.js +1 -1
- package/dist/providers/core/utils/provider-type-utils.js.map +1 -1
- package/dist/providers/core/utils/snapshot-writer.d.ts +1 -1
- package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
- package/dist/server/handlers/sse-dispatcher.js +22 -2
- package/dist/server/handlers/sse-dispatcher.js.map +1 -1
- package/dist/server/runtime/http-server/index.d.ts +9 -0
- package/dist/server/runtime/http-server/index.js +512 -144
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/provider-utils.js +1 -1
- package/dist/server/runtime/http-server/provider-utils.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.d.ts +10 -0
- package/dist/server/runtime/http-server/request-executor.js +553 -159
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.d.ts +5 -0
- package/dist/server/runtime/http-server/routes.js +29 -0
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/runtime-manager.js +33 -0
- package/dist/server/runtime/http-server/runtime-manager.js.map +1 -1
- package/dist/server/utils/utf8-chunk-buffer.d.ts +43 -0
- package/dist/server/utils/utf8-chunk-buffer.js +132 -0
- package/dist/server/utils/utf8-chunk-buffer.js.map +1 -0
- package/dist/token-daemon/history-store.d.ts +75 -0
- package/dist/token-daemon/history-store.js +207 -0
- package/dist/token-daemon/history-store.js.map +1 -0
- package/dist/token-daemon/index.d.ts +7 -0
- package/dist/token-daemon/index.js +336 -0
- package/dist/token-daemon/index.js.map +1 -0
- package/dist/token-daemon/server-utils.d.ts +33 -0
- package/dist/token-daemon/server-utils.js +155 -0
- package/dist/token-daemon/server-utils.js.map +1 -0
- package/dist/token-daemon/token-daemon.d.ts +23 -0
- package/dist/token-daemon/token-daemon.js +249 -0
- package/dist/token-daemon/token-daemon.js.map +1 -0
- package/dist/token-daemon/token-types.d.ts +44 -0
- package/dist/token-daemon/token-types.js +18 -0
- package/dist/token-daemon/token-types.js.map +1 -0
- package/dist/token-daemon/token-utils.d.ts +17 -0
- package/dist/token-daemon/token-utils.js +153 -0
- package/dist/token-daemon/token-utils.js.map +1 -0
- package/dist/token-portal/local-token-portal.d.ts +1 -0
- package/dist/token-portal/local-token-portal.js +89 -0
- package/dist/token-portal/local-token-portal.js.map +1 -0
- package/dist/token-portal/render.d.ts +10 -0
- package/dist/token-portal/render.js +56 -0
- package/dist/token-portal/render.js.map +1 -0
- package/dist/tools/semantic-replay.js +7 -6
- package/dist/tools/semantic-replay.js.map +1 -1
- package/dist/utils/error-handler-registry.d.ts +36 -0
- package/dist/utils/error-handler-registry.js +93 -7
- package/dist/utils/error-handler-registry.js.map +1 -1
- package/node_modules/@jsonstudio/llms/README.md +2 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/codecs/gemini-openai-codec.js +137 -5
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/gemini-web-search.d.ts +17 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/gemini-web-search.js +68 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-image-content.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-image-content.js +83 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-vision-prompt.d.ts +11 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-vision-prompt.js +177 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-web-search.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-web-search.js +63 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/universal-shape-filter.js +11 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-gemini.json +17 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-glm.json +190 -181
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-iflow.json +195 -195
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwen.json +20 -20
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/responses-c4m.json +42 -42
- package/node_modules/@jsonstudio/llms/dist/conversion/config/sample-config.json +1 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +24 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-types.d.ts +8 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.js +39 -4
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/target-utils.js +6 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process.js +213 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.d.ts +34 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +84 -24
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/server-side-tools.d.ts +26 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/server-side-tools.js +383 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/semantic-mappers/gemini-mapper.js +241 -14
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/semantic-mappers/responses-mapper.js +17 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/standardized-bridge.js +14 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/types/standardized.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +82 -3
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils.js +92 -3
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/bridge-message-utils.js +137 -10
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-output-builder.js +43 -2
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/snapshot-utils.js +17 -47
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-filter-pipeline.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-mapping.js +25 -2
- package/node_modules/@jsonstudio/llms/dist/index.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/index.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/bootstrap.js +308 -43
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/classifier.js +11 -17
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/context-advisor.d.ts +0 -2
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/context-advisor.js +0 -12
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.d.ts +17 -2
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +332 -95
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/features.js +1 -1
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/message-utils.js +36 -24
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/provider-registry.js +2 -1
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-counter.js +14 -3
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.d.ts +66 -2
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.js +2 -1
- package/node_modules/@jsonstudio/llms/dist/servertool/engine.d.ts +27 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/engine.js +60 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/flow-types.d.ts +40 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/flow-types.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.js +194 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/handlers/web-search.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/handlers/web-search.js +638 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/orchestration-types.d.ts +33 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/orchestration-types.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/registry.d.ts +18 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/registry.js +27 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.d.ts +8 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.js +208 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/types.d.ts +88 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/types.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/vision-tool.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/vision-tool.js +185 -0
- package/node_modules/@jsonstudio/llms/dist/sse/json-to-sse/event-generators/responses.js +15 -3
- package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/builders/response-builder.js +6 -3
- package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/gemini-sse-to-json-converter.js +27 -1
- package/node_modules/@jsonstudio/llms/dist/sse/types/gemini-types.d.ts +20 -1
- package/node_modules/@jsonstudio/llms/dist/sse/types/responses-types.js +1 -1
- package/node_modules/@jsonstudio/llms/dist/telemetry/stats-center.d.ts +73 -0
- package/node_modules/@jsonstudio/llms/dist/telemetry/stats-center.js +280 -0
- package/node_modules/@jsonstudio/llms/package.json +1 -1
- package/package.json +3 -2
- package/scripts/pack-mode.mjs +2 -1
- package/scripts/publish-rcc.mjs +20 -4
- package/scripts/test-iflow-web-search.mjs +141 -0
- package/scripts/test-iflow.mjs +93 -1
- package/scripts/tests/virtual-router-health.mjs +141 -6
- package/dist/tools/replay-request.d.ts +0 -0
- package/dist/tools/replay-request.js +0 -2
- package/dist/tools/replay-request.js.map +0 -1
|
@@ -10,6 +10,10 @@ import { validateResponsePayload } from '../../../compat/actions/response-valida
|
|
|
10
10
|
import { writeCompatSnapshot } from '../../../compat/actions/snapshot.js';
|
|
11
11
|
import { applyQwenRequestTransform, applyQwenResponseTransform } from '../../../compat/actions/qwen-transform.js';
|
|
12
12
|
import { extractGlmToolMarkup } from '../../../compat/actions/glm-tool-extraction.js';
|
|
13
|
+
import { applyGlmWebSearchRequestTransform } from '../../../compat/actions/glm-web-search.js';
|
|
14
|
+
import { applyGeminiWebSearchCompat } from '../../../compat/actions/gemini-web-search.js';
|
|
15
|
+
import { applyGlmImageContentTransform } from '../../../compat/actions/glm-image-content.js';
|
|
16
|
+
import { applyGlmVisionPromptTransform } from '../../../compat/actions/glm-vision-prompt.js';
|
|
13
17
|
const RATE_LIMIT_ERROR = 'ERR_COMPAT_RATE_LIMIT_DETECTED';
|
|
14
18
|
const INTERNAL_STATE = Symbol('compat.internal_state');
|
|
15
19
|
export function runRequestCompatPipeline(profileId, payload, options) {
|
|
@@ -157,6 +161,26 @@ function applyMapping(root, mapping, state) {
|
|
|
157
161
|
case 'qwen_response_transform':
|
|
158
162
|
replaceRoot(root, applyQwenResponseTransform(root));
|
|
159
163
|
break;
|
|
164
|
+
case 'glm_web_search_request':
|
|
165
|
+
if (state.direction === 'request') {
|
|
166
|
+
replaceRoot(root, applyGlmWebSearchRequestTransform(root));
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
case 'gemini_web_search_request':
|
|
170
|
+
if (state.direction === 'request') {
|
|
171
|
+
replaceRoot(root, applyGeminiWebSearchCompat(root, state.adapterContext));
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
case 'glm_image_content':
|
|
175
|
+
if (state.direction === 'request') {
|
|
176
|
+
replaceRoot(root, applyGlmImageContentTransform(root));
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
case 'glm_vision_prompt':
|
|
180
|
+
if (state.direction === 'request') {
|
|
181
|
+
replaceRoot(root, applyGlmVisionPromptTransform(root));
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
160
184
|
default:
|
|
161
185
|
break;
|
|
162
186
|
}
|
|
@@ -98,6 +98,14 @@ export type MappingInstruction = {
|
|
|
98
98
|
action: 'qwen_request_transform';
|
|
99
99
|
} | {
|
|
100
100
|
action: 'qwen_response_transform';
|
|
101
|
+
} | {
|
|
102
|
+
action: 'glm_web_search_request';
|
|
103
|
+
} | {
|
|
104
|
+
action: 'glm_image_content';
|
|
105
|
+
} | {
|
|
106
|
+
action: 'glm_vision_prompt';
|
|
107
|
+
} | {
|
|
108
|
+
action: 'gemini_web_search_request';
|
|
101
109
|
};
|
|
102
110
|
export type FilterInstruction = {
|
|
103
111
|
action: 'rate_limit_text';
|
|
@@ -11,6 +11,7 @@ import { GeminiSemanticMapper } from '../semantic-mappers/gemini-mapper.js';
|
|
|
11
11
|
import { ChatFormatAdapter } from '../format-adapters/chat-format-adapter.js';
|
|
12
12
|
import { ChatSemanticMapper } from '../semantic-mappers/chat-mapper.js';
|
|
13
13
|
import { createSnapshotRecorder } from '../snapshot-recorder.js';
|
|
14
|
+
import { shouldRecordSnapshots } from '../../shared/snapshot-utils.js';
|
|
14
15
|
import { runReqInboundStage1FormatParse } from './stages/req_inbound/req_inbound_stage1_format_parse/index.js';
|
|
15
16
|
import { runReqInboundStage2SemanticMap } from './stages/req_inbound/req_inbound_stage2_semantic_map/index.js';
|
|
16
17
|
import { runChatContextCapture, captureResponsesContextSnapshot } from './stages/req_inbound/req_inbound_stage3_context_capture/index.js';
|
|
@@ -93,10 +94,18 @@ export class HubPipeline {
|
|
|
93
94
|
});
|
|
94
95
|
let processedRequest;
|
|
95
96
|
if (normalized.processMode !== 'passthrough') {
|
|
97
|
+
const processMetadata = {
|
|
98
|
+
...(normalized.metadata ?? {})
|
|
99
|
+
};
|
|
100
|
+
const webSearchConfig = this.config.virtualRouter?.webSearch;
|
|
101
|
+
if (webSearchConfig) {
|
|
102
|
+
processMetadata.webSearch = webSearchConfig;
|
|
103
|
+
}
|
|
104
|
+
normalized.metadata = processMetadata;
|
|
96
105
|
const processResult = await runReqProcessStage1ToolGovernance({
|
|
97
106
|
request: standardizedRequest,
|
|
98
107
|
rawPayload: rawRequest,
|
|
99
|
-
metadata:
|
|
108
|
+
metadata: processMetadata,
|
|
100
109
|
entryEndpoint: normalized.entryEndpoint,
|
|
101
110
|
requestId: normalized.id,
|
|
102
111
|
stageRecorder: inboundRecorder
|
|
@@ -111,6 +120,9 @@ export class HubPipeline {
|
|
|
111
120
|
const responsesResume = normalizedMeta && typeof normalizedMeta.responsesResume === 'object'
|
|
112
121
|
? normalizedMeta.responsesResume
|
|
113
122
|
: undefined;
|
|
123
|
+
const stdMetadata = workingRequest?.metadata;
|
|
124
|
+
const serverToolRequired = stdMetadata?.webSearchEnabled === true ||
|
|
125
|
+
stdMetadata?.serverToolRequired === true;
|
|
114
126
|
const metadataInput = {
|
|
115
127
|
requestId: normalized.id,
|
|
116
128
|
entryEndpoint: normalized.entryEndpoint,
|
|
@@ -120,7 +132,8 @@ export class HubPipeline {
|
|
|
120
132
|
providerProtocol: normalized.providerProtocol,
|
|
121
133
|
routeHint: normalized.routeHint,
|
|
122
134
|
stage: normalized.stage,
|
|
123
|
-
responsesResume: responsesResume
|
|
135
|
+
responsesResume: responsesResume,
|
|
136
|
+
...(serverToolRequired ? { serverToolRequired: true } : {})
|
|
124
137
|
};
|
|
125
138
|
const routing = runReqProcessStage2RouteSelect({
|
|
126
139
|
routerEngine: this.routerEngine,
|
|
@@ -221,8 +234,25 @@ export class HubPipeline {
|
|
|
221
234
|
}
|
|
222
235
|
});
|
|
223
236
|
}
|
|
237
|
+
// 为响应侧 servertool/web_search 提供一次性 Chat 请求快照,便于在 Hub 内部实现
|
|
238
|
+
// 第三跳(将工具结果注入消息历史后重新调用主模型)。
|
|
239
|
+
let capturedChatRequest;
|
|
240
|
+
if (normalized.processMode !== 'passthrough') {
|
|
241
|
+
try {
|
|
242
|
+
capturedChatRequest = JSON.parse(JSON.stringify({
|
|
243
|
+
model: workingRequest.model,
|
|
244
|
+
messages: workingRequest.messages,
|
|
245
|
+
tools: workingRequest.tools,
|
|
246
|
+
parameters: workingRequest.parameters
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
capturedChatRequest = undefined;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
224
253
|
const metadata = {
|
|
225
254
|
...normalized.metadata,
|
|
255
|
+
...(capturedChatRequest ? { capturedChatRequest } : {}),
|
|
226
256
|
entryEndpoint: normalized.entryEndpoint,
|
|
227
257
|
providerProtocol: outboundProtocol,
|
|
228
258
|
stream: normalized.stream,
|
|
@@ -342,14 +372,19 @@ export class HubPipeline {
|
|
|
342
372
|
if (typeof metadata.assignedModelId === 'string') {
|
|
343
373
|
adapterContext.modelId = metadata.assignedModelId;
|
|
344
374
|
}
|
|
375
|
+
// 将 serverToolFollowup 等 ServerTool 相关标记从 normalized.metadata 透传到 AdapterContext,
|
|
376
|
+
// 便于响应侧的 convertProviderResponse 正确识别“二跳/内部跳转”并跳过 servertool 编排。
|
|
377
|
+
if (Object.prototype.hasOwnProperty.call(metadata, 'serverToolFollowup')) {
|
|
378
|
+
adapterContext.serverToolFollowup = metadata
|
|
379
|
+
.serverToolFollowup;
|
|
380
|
+
}
|
|
345
381
|
if (target?.compatibilityProfile && typeof target.compatibilityProfile === 'string') {
|
|
346
382
|
adapterContext.compatibilityProfile = target.compatibilityProfile;
|
|
347
383
|
}
|
|
348
384
|
return adapterContext;
|
|
349
385
|
}
|
|
350
386
|
maybeCreateStageRecorder(context, endpoint) {
|
|
351
|
-
|
|
352
|
-
if (flag === '0') {
|
|
387
|
+
if (!shouldRecordSnapshots()) {
|
|
353
388
|
return undefined;
|
|
354
389
|
}
|
|
355
390
|
const effectiveEndpoint = endpoint || context.entryEndpoint || '/v1/chat/completions';
|
|
@@ -9,6 +9,12 @@ export function applyTargetMetadata(metadata, target, routeName, originalModel)
|
|
|
9
9
|
metadata.providerType = target.providerType;
|
|
10
10
|
metadata.modelId = target.modelId;
|
|
11
11
|
metadata.processMode = target.processMode || 'chat';
|
|
12
|
+
if (target.forceWebSearch === true) {
|
|
13
|
+
metadata.forceWebSearch = true;
|
|
14
|
+
}
|
|
15
|
+
if (target.forceVision === true) {
|
|
16
|
+
metadata.forceVision = true;
|
|
17
|
+
}
|
|
12
18
|
if (target.responsesConfig?.toolCallIdStyle) {
|
|
13
19
|
metadata.toolCallIdStyle = target.responsesConfig.toolCallIdStyle;
|
|
14
20
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { runChatRequestToolFilters } from '../../shared/tool-filter-pipeline.js';
|
|
2
2
|
import { ToolGovernanceEngine } from '../tool-governance/index.js';
|
|
3
|
+
import { detectLastAssistantToolCategory } from '../../../router/virtual-router/tool-signals.js';
|
|
3
4
|
const toolGovernanceEngine = new ToolGovernanceEngine();
|
|
4
5
|
export async function runHubChatProcess(options) {
|
|
5
6
|
const startTime = Date.now();
|
|
@@ -51,7 +52,7 @@ async function applyRequestToolGovernance(request, context) {
|
|
|
51
52
|
});
|
|
52
53
|
const governed = normalizeRecord(governedPayload);
|
|
53
54
|
const providerStreamIntent = typeof governed.stream === 'boolean' ? governed.stream : undefined;
|
|
54
|
-
|
|
55
|
+
let merged = {
|
|
55
56
|
...request,
|
|
56
57
|
messages: Array.isArray(governed.messages)
|
|
57
58
|
? governed.messages
|
|
@@ -71,6 +72,14 @@ async function applyRequestToolGovernance(request, context) {
|
|
|
71
72
|
governanceTimestamp: Date.now()
|
|
72
73
|
}
|
|
73
74
|
};
|
|
75
|
+
if (containsImageAttachment(merged.messages)) {
|
|
76
|
+
if (!merged.metadata) {
|
|
77
|
+
merged.metadata = {
|
|
78
|
+
originalEndpoint: request.metadata?.originalEndpoint ?? '/v1/chat/completions'
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
merged.metadata.hasImageAttachment = true;
|
|
82
|
+
}
|
|
74
83
|
if (typeof inboundStreamIntent === 'boolean') {
|
|
75
84
|
merged.metadata = {
|
|
76
85
|
...merged.metadata,
|
|
@@ -92,6 +101,8 @@ async function applyRequestToolGovernance(request, context) {
|
|
|
92
101
|
if (typeof governed.model === 'string' && governed.model.trim()) {
|
|
93
102
|
merged.model = governed.model.trim();
|
|
94
103
|
}
|
|
104
|
+
// Server-side web_search tool injection (config-driven, best-effort).
|
|
105
|
+
merged = maybeInjectWebSearchTool(merged, metadata);
|
|
95
106
|
const { request: sanitized, summary } = toolGovernanceEngine.governRequest(merged, providerProtocol);
|
|
96
107
|
if (summary.applied) {
|
|
97
108
|
sanitized.metadata = {
|
|
@@ -194,6 +205,34 @@ function castSingleTool(tool) {
|
|
|
194
205
|
}
|
|
195
206
|
};
|
|
196
207
|
}
|
|
208
|
+
function containsImageAttachment(messages) {
|
|
209
|
+
if (!Array.isArray(messages)) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
for (const message of messages) {
|
|
213
|
+
if (!message || typeof message !== 'object') {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
const content = message.content;
|
|
217
|
+
if (!Array.isArray(content)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
for (const part of content) {
|
|
221
|
+
if (!part || typeof part !== 'object') {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const typeValue = part.type;
|
|
225
|
+
if (typeof typeValue !== 'string') {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const normalized = typeValue.toLowerCase();
|
|
229
|
+
if (normalized.includes('image')) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
197
236
|
function castCustomTool(tool) {
|
|
198
237
|
if (!isRecord(tool)) {
|
|
199
238
|
return null;
|
|
@@ -274,3 +313,176 @@ function readToolChoice(value) {
|
|
|
274
313
|
function isRecord(value) {
|
|
275
314
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
276
315
|
}
|
|
316
|
+
function maybeInjectWebSearchTool(request, metadata) {
|
|
317
|
+
// ServerTool 二/三跳(serverToolFollowup=true)不再注入 web_search 工具,
|
|
318
|
+
// 以避免在 web_search 流程内部形成循环命中。
|
|
319
|
+
if (metadata.serverToolFollowup === true) {
|
|
320
|
+
return request;
|
|
321
|
+
}
|
|
322
|
+
const rawConfig = metadata.webSearch;
|
|
323
|
+
if (!rawConfig || !Array.isArray(rawConfig.engines) || rawConfig.engines.length === 0) {
|
|
324
|
+
return request;
|
|
325
|
+
}
|
|
326
|
+
const injectPolicy = rawConfig.injectPolicy === 'always' || rawConfig.injectPolicy === 'selective'
|
|
327
|
+
? rawConfig.injectPolicy
|
|
328
|
+
: 'selective';
|
|
329
|
+
if (injectPolicy === 'selective') {
|
|
330
|
+
const hasExplicitIntent = detectWebSearchIntent(request);
|
|
331
|
+
if (!hasExplicitIntent) {
|
|
332
|
+
// 当最近一条用户消息没有明显的“联网搜索”关键词时,
|
|
333
|
+
// 如果上一轮 assistant 的工具调用已经属于搜索类(如 web_search),
|
|
334
|
+
// 则仍然视为 web_search 续写场景,强制注入 web_search 工具,
|
|
335
|
+
// 以便在后续路由中按 servertool 逻辑跳过不适配的 Provider(例如 serverToolsDisabled 的 crs)。
|
|
336
|
+
const assistantMessages = Array.isArray(request.messages)
|
|
337
|
+
? request.messages.filter((msg) => msg && msg.role === 'assistant')
|
|
338
|
+
: [];
|
|
339
|
+
const lastTool = detectLastAssistantToolCategory(assistantMessages);
|
|
340
|
+
const hasSearchToolContext = lastTool?.category === 'search';
|
|
341
|
+
if (!hasSearchToolContext) {
|
|
342
|
+
return request;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const existingTools = Array.isArray(request.tools) ? request.tools : [];
|
|
347
|
+
const hasWebSearch = existingTools.some((tool) => {
|
|
348
|
+
if (!tool || typeof tool !== 'object')
|
|
349
|
+
return false;
|
|
350
|
+
const fn = tool.function;
|
|
351
|
+
return typeof fn?.name === 'string' && fn.name.trim() === 'web_search';
|
|
352
|
+
});
|
|
353
|
+
if (hasWebSearch) {
|
|
354
|
+
return request;
|
|
355
|
+
}
|
|
356
|
+
const engines = rawConfig.engines.filter((engine) => typeof engine?.id === 'string' && !!engine.id.trim() && !engine.serverToolsDisabled);
|
|
357
|
+
if (!engines.length) {
|
|
358
|
+
return request;
|
|
359
|
+
}
|
|
360
|
+
const engineIds = engines.map((engine) => engine.id.trim());
|
|
361
|
+
const engineDescriptions = engines
|
|
362
|
+
.map((engine) => {
|
|
363
|
+
const id = engine.id.trim();
|
|
364
|
+
const desc = typeof engine.description === 'string' && engine.description.trim()
|
|
365
|
+
? engine.description.trim()
|
|
366
|
+
: '';
|
|
367
|
+
return desc ? `${id}: ${desc}` : id;
|
|
368
|
+
})
|
|
369
|
+
.join('; ');
|
|
370
|
+
const parameters = {
|
|
371
|
+
type: 'object',
|
|
372
|
+
properties: {
|
|
373
|
+
engine: {
|
|
374
|
+
type: 'string',
|
|
375
|
+
enum: engineIds,
|
|
376
|
+
description: engineDescriptions
|
|
377
|
+
},
|
|
378
|
+
query: {
|
|
379
|
+
type: 'string',
|
|
380
|
+
description: 'Search query or user question.'
|
|
381
|
+
},
|
|
382
|
+
recency: {
|
|
383
|
+
type: 'string',
|
|
384
|
+
enum: ['oneDay', 'oneWeek', 'oneMonth', 'oneYear', 'noLimit'],
|
|
385
|
+
description: 'Optional recency filter for web search results.'
|
|
386
|
+
},
|
|
387
|
+
count: {
|
|
388
|
+
type: 'integer',
|
|
389
|
+
minimum: 1,
|
|
390
|
+
maximum: 50,
|
|
391
|
+
description: 'Number of results to retrieve.'
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
// 对于 Responses 内建 web_search,required 需要覆盖 properties 中的所有字段,
|
|
395
|
+
// 否则上游会报 "required is required to be supplied and to be an array including every key in properties"。
|
|
396
|
+
required: ['engine', 'query', 'recency', 'count'],
|
|
397
|
+
additionalProperties: false
|
|
398
|
+
};
|
|
399
|
+
const webSearchTool = {
|
|
400
|
+
type: 'function',
|
|
401
|
+
function: {
|
|
402
|
+
name: 'web_search',
|
|
403
|
+
description: 'Perform web search using configured search engines. Use this when the user asks for up-to-date information or news.',
|
|
404
|
+
parameters,
|
|
405
|
+
strict: true
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
const nextMetadata = {
|
|
409
|
+
...(request.metadata ?? {}),
|
|
410
|
+
webSearchEnabled: true
|
|
411
|
+
};
|
|
412
|
+
return {
|
|
413
|
+
...request,
|
|
414
|
+
metadata: nextMetadata,
|
|
415
|
+
tools: [...existingTools, webSearchTool]
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function detectWebSearchIntent(request) {
|
|
419
|
+
const messages = Array.isArray(request.messages) ? request.messages : [];
|
|
420
|
+
if (!messages.length) {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
// 从末尾向前找到最近一条 user 消息,忽略 tool / assistant 的工具调用轮次,
|
|
424
|
+
// 以便在 Responses / 多轮工具调用场景下仍然根据“最近一条用户输入”判断意图。
|
|
425
|
+
let lastUser;
|
|
426
|
+
for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
|
|
427
|
+
const candidate = messages[idx];
|
|
428
|
+
if (candidate && candidate.role === 'user') {
|
|
429
|
+
lastUser = candidate;
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (!lastUser) {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
// 支持多模态 content:既可能是纯文本字符串,也可能是带 image_url 的分段数组。
|
|
437
|
+
let content = '';
|
|
438
|
+
if (typeof lastUser.content === 'string') {
|
|
439
|
+
content = lastUser.content;
|
|
440
|
+
}
|
|
441
|
+
else if (Array.isArray(lastUser.content)) {
|
|
442
|
+
const parts = lastUser.content;
|
|
443
|
+
const texts = [];
|
|
444
|
+
for (const part of parts) {
|
|
445
|
+
if (typeof part === 'string') {
|
|
446
|
+
texts.push(part);
|
|
447
|
+
}
|
|
448
|
+
else if (part && typeof part === 'object') {
|
|
449
|
+
const maybeText = part.text;
|
|
450
|
+
if (typeof maybeText === 'string' && maybeText.trim()) {
|
|
451
|
+
texts.push(maybeText);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
content = texts.join('\n');
|
|
456
|
+
}
|
|
457
|
+
if (!content) {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
const text = content.toLowerCase();
|
|
461
|
+
const keywords = [
|
|
462
|
+
// English
|
|
463
|
+
'web search',
|
|
464
|
+
'web_search',
|
|
465
|
+
'websearch',
|
|
466
|
+
'internet search',
|
|
467
|
+
'search the web',
|
|
468
|
+
'online search',
|
|
469
|
+
'search online',
|
|
470
|
+
'search on the internet',
|
|
471
|
+
'search the internet',
|
|
472
|
+
'web-search',
|
|
473
|
+
'online-search',
|
|
474
|
+
'internet-search',
|
|
475
|
+
// Chinese
|
|
476
|
+
'联网搜索',
|
|
477
|
+
'网络搜索',
|
|
478
|
+
'上网搜索',
|
|
479
|
+
'网上搜索',
|
|
480
|
+
'网上查',
|
|
481
|
+
'网上查找',
|
|
482
|
+
'上网查',
|
|
483
|
+
'上网搜',
|
|
484
|
+
// Command-style
|
|
485
|
+
'/search'
|
|
486
|
+
];
|
|
487
|
+
return keywords.some((keyword) => text.includes(keyword.toLowerCase()));
|
|
488
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import type { AdapterContext } from '../types/chat-envelope.js';
|
|
3
|
+
import type { JsonObject } from '../types/json.js';
|
|
4
|
+
import type { StageRecorder } from '../format-adapters/index.js';
|
|
5
|
+
import type { ProviderInvoker } from '../../../servertool/types.js';
|
|
6
|
+
type ProviderProtocol = 'openai-chat' | 'openai-responses' | 'anthropic-messages' | 'gemini-chat';
|
|
7
|
+
export interface ProviderResponseConversionOptions {
|
|
8
|
+
providerProtocol: ProviderProtocol;
|
|
9
|
+
providerResponse: JsonObject;
|
|
10
|
+
context: AdapterContext;
|
|
11
|
+
entryEndpoint: string;
|
|
12
|
+
wantsStream: boolean;
|
|
13
|
+
stageRecorder?: StageRecorder;
|
|
14
|
+
providerInvoker?: ProviderInvoker;
|
|
15
|
+
/**
|
|
16
|
+
* 可选:由 Host 注入的二次请求入口。Server-side 工具在需要发起
|
|
17
|
+
* followup 请求(例如 web_search 二跳)时,可以通过该回调将构造
|
|
18
|
+
* 好的请求体交给 Host,由 Host 走完整 HubPipeline + VirtualRouter
|
|
19
|
+
* 再返回最终客户端响应形状。
|
|
20
|
+
*/
|
|
21
|
+
reenterPipeline?: (options: {
|
|
22
|
+
entryEndpoint: string;
|
|
23
|
+
requestId: string;
|
|
24
|
+
body: JsonObject;
|
|
25
|
+
metadata?: JsonObject;
|
|
26
|
+
}) => Promise<ProviderResponseConversionResult>;
|
|
27
|
+
}
|
|
28
|
+
export interface ProviderResponseConversionResult {
|
|
29
|
+
body?: JsonObject;
|
|
30
|
+
__sse_responses?: Readable;
|
|
31
|
+
format?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function convertProviderResponse(options: ProviderResponseConversionOptions): Promise<ProviderResponseConversionResult>;
|
|
34
|
+
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { recordStage } from '../pipeline/stages/utils.js';
|
|
1
2
|
import { ChatFormatAdapter } from '../format-adapters/chat-format-adapter.js';
|
|
2
3
|
import { ResponsesFormatAdapter } from '../format-adapters/responses-format-adapter.js';
|
|
3
4
|
import { AnthropicFormatAdapter } from '../format-adapters/anthropic-format-adapter.js';
|
|
@@ -12,44 +13,36 @@ import { runRespProcessStage2Finalize } from '../pipeline/stages/resp_process/re
|
|
|
12
13
|
import { runRespOutboundStage1ClientRemap } from '../pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js';
|
|
13
14
|
import { runRespOutboundStage2SseStream } from '../pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js';
|
|
14
15
|
import { recordResponsesResponse } from '../../shared/responses-conversation-store.js';
|
|
15
|
-
|
|
16
|
-
const envRaw = (process.env.ROUTECODEX_CHAT_REASONING_MODE || process.env.RCC_CHAT_REASONING_MODE || '').trim().toLowerCase();
|
|
17
|
-
const map = {
|
|
18
|
-
keep: 'keep',
|
|
19
|
-
drop: 'drop',
|
|
20
|
-
discard: 'drop',
|
|
21
|
-
text: 'append_to_content',
|
|
22
|
-
append: 'append_to_content',
|
|
23
|
-
append_text: 'append_to_content',
|
|
24
|
-
append_to_content: 'append_to_content'
|
|
25
|
-
};
|
|
26
|
-
if (envRaw && map[envRaw]) {
|
|
27
|
-
return map[envRaw];
|
|
28
|
-
}
|
|
29
|
-
return 'keep';
|
|
30
|
-
}
|
|
16
|
+
import { runServerToolOrchestration } from '../../../servertool/engine.js';
|
|
31
17
|
const PROVIDER_RESPONSE_REGISTRY = {
|
|
32
18
|
'openai-chat': {
|
|
33
|
-
protocol: 'openai-chat',
|
|
34
19
|
createFormatAdapter: () => new ChatFormatAdapter(),
|
|
35
20
|
createMapper: () => new OpenAIChatResponseMapper()
|
|
36
21
|
},
|
|
37
22
|
'openai-responses': {
|
|
38
|
-
protocol: 'openai-responses',
|
|
39
23
|
createFormatAdapter: () => new ResponsesFormatAdapter(),
|
|
40
24
|
createMapper: () => new ResponsesResponseMapper()
|
|
41
25
|
},
|
|
42
26
|
'anthropic-messages': {
|
|
43
|
-
protocol: 'anthropic-messages',
|
|
44
27
|
createFormatAdapter: () => new AnthropicFormatAdapter(),
|
|
45
28
|
createMapper: () => new AnthropicResponseMapper()
|
|
46
29
|
},
|
|
47
30
|
'gemini-chat': {
|
|
48
|
-
protocol: 'gemini-chat',
|
|
49
31
|
createFormatAdapter: () => new GeminiFormatAdapter(),
|
|
50
32
|
createMapper: () => new GeminiResponseMapper()
|
|
51
33
|
}
|
|
52
34
|
};
|
|
35
|
+
function isServerToolFollowup(context) {
|
|
36
|
+
const raw = context.serverToolFollowup;
|
|
37
|
+
if (raw === true) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
if (typeof raw === 'string') {
|
|
41
|
+
const v = raw.trim().toLowerCase();
|
|
42
|
+
return v === '1' || v === 'true';
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
53
46
|
function resolveClientProtocol(entryEndpoint) {
|
|
54
47
|
const lowered = (entryEndpoint || '').toLowerCase();
|
|
55
48
|
if (lowered.includes('/v1/responses'))
|
|
@@ -85,8 +78,28 @@ function applyModelOverride(payload, model) {
|
|
|
85
78
|
/* ignore */
|
|
86
79
|
}
|
|
87
80
|
}
|
|
81
|
+
function resolveChatReasoningMode(_entryEndpoint) {
|
|
82
|
+
// 当前保持默认策略:保留 reasoning_content 字段,不做额外拼接或删除。
|
|
83
|
+
return 'keep';
|
|
84
|
+
}
|
|
88
85
|
export async function convertProviderResponse(options) {
|
|
89
86
|
const clientProtocol = resolveClientProtocol(options.entryEndpoint);
|
|
87
|
+
const hasServerToolSupport = Boolean(options.providerInvoker) || Boolean(options.reenterPipeline);
|
|
88
|
+
const skipServerTools = isServerToolFollowup(options.context) || !hasServerToolSupport;
|
|
89
|
+
// 对于由 server-side 工具触发的内部跳转(二跳/三跳),统一禁用 SSE 聚合输出,
|
|
90
|
+
// 始终返回完整的 ChatCompletion JSON,便于在 llms 内部直接解析,而不是拿到
|
|
91
|
+
// __sse_responses 可读流。
|
|
92
|
+
const wantsStream = isServerToolFollowup(options.context) ? false : options.wantsStream;
|
|
93
|
+
try {
|
|
94
|
+
// eslint-disable-next-line no-console
|
|
95
|
+
console.log(`\x1b[38;5;33m[servertool][orchestrator][debug] requestId=${options.context.requestId} ` +
|
|
96
|
+
`protocol=${options.providerProtocol} endpoint=${options.entryEndpoint} ` +
|
|
97
|
+
`skipServerTools=${skipServerTools} hasInvoker=${Boolean(options.providerInvoker)} ` +
|
|
98
|
+
`hasReenter=${Boolean(options.reenterPipeline)}\x1b[0m`);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
/* logging best-effort */
|
|
102
|
+
}
|
|
90
103
|
const displayModel = extractDisplayModel(options.context);
|
|
91
104
|
const plan = PROVIDER_RESPONSE_REGISTRY[options.providerProtocol];
|
|
92
105
|
if (!plan) {
|
|
@@ -96,7 +109,7 @@ export async function convertProviderResponse(options) {
|
|
|
96
109
|
providerProtocol: options.providerProtocol,
|
|
97
110
|
payload: options.providerResponse,
|
|
98
111
|
adapterContext: options.context,
|
|
99
|
-
wantsStream
|
|
112
|
+
wantsStream,
|
|
100
113
|
stageRecorder: options.stageRecorder
|
|
101
114
|
});
|
|
102
115
|
const formatAdapter = plan.createFormatAdapter();
|
|
@@ -137,8 +150,55 @@ export async function convertProviderResponse(options) {
|
|
|
137
150
|
mapper,
|
|
138
151
|
stageRecorder: options.stageRecorder
|
|
139
152
|
});
|
|
153
|
+
// 记录语义映射后的 ChatCompletion,便于回放 server-side 工具流程。
|
|
154
|
+
recordStage(options.stageRecorder, 'resp_inbound_stage3_semantic_map.chat', chatResponse);
|
|
155
|
+
// 检查是否需要进行 ServerTool 编排
|
|
156
|
+
// 使用新的 ChatEnvelope 级别的 servertool 实现
|
|
157
|
+
let effectiveChatResponse = chatResponse;
|
|
158
|
+
if (!skipServerTools && options.reenterPipeline) {
|
|
159
|
+
try {
|
|
160
|
+
// eslint-disable-next-line no-console
|
|
161
|
+
console.log(`\x1b[38;5;33m[servertool][orchestrator] start requestId=${options.context.requestId} ` +
|
|
162
|
+
`protocol=${options.providerProtocol} endpoint=${options.entryEndpoint}\x1b[0m`);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
/* logging best-effort */
|
|
166
|
+
}
|
|
167
|
+
const orchestration = await runServerToolOrchestration({
|
|
168
|
+
chat: chatResponse,
|
|
169
|
+
adapterContext: options.context,
|
|
170
|
+
requestId: options.context.requestId,
|
|
171
|
+
entryEndpoint: options.entryEndpoint,
|
|
172
|
+
providerProtocol: options.providerProtocol,
|
|
173
|
+
providerInvoker: options.providerInvoker,
|
|
174
|
+
reenterPipeline: options.reenterPipeline
|
|
175
|
+
});
|
|
176
|
+
if (orchestration.executed) {
|
|
177
|
+
const flowLabel = orchestration.flowId ?? 'servertool_flow';
|
|
178
|
+
try {
|
|
179
|
+
// eslint-disable-next-line no-console
|
|
180
|
+
console.log(`\x1b[38;5;33m[servertool][orchestrator] completed requestId=${options.context.requestId} ` +
|
|
181
|
+
`mode=${flowLabel}\x1b[0m`);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
/* logging best-effort */
|
|
185
|
+
}
|
|
186
|
+
effectiveChatResponse = orchestration.chat;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
try {
|
|
190
|
+
// eslint-disable-next-line no-console
|
|
191
|
+
console.log(`\x1b[38;5;33m[servertool][orchestrator] skipped requestId=${options.context.requestId} ` +
|
|
192
|
+
'reason=no_servertool_match\x1b[0m');
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
/* logging best-effort */
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// 如果没有执行 servertool,继续原来的处理流程
|
|
140
200
|
const governanceResult = await runRespProcessStage1ToolGovernance({
|
|
141
|
-
payload:
|
|
201
|
+
payload: effectiveChatResponse,
|
|
142
202
|
entryEndpoint: options.entryEndpoint,
|
|
143
203
|
requestId: options.context.requestId,
|
|
144
204
|
clientProtocol,
|
|
@@ -148,7 +208,7 @@ export async function convertProviderResponse(options) {
|
|
|
148
208
|
payload: governanceResult.governedPayload,
|
|
149
209
|
entryEndpoint: options.entryEndpoint,
|
|
150
210
|
requestId: options.context.requestId,
|
|
151
|
-
wantsStream
|
|
211
|
+
wantsStream,
|
|
152
212
|
reasoningMode: resolveChatReasoningMode(options.entryEndpoint),
|
|
153
213
|
stageRecorder: options.stageRecorder
|
|
154
214
|
});
|
|
@@ -165,7 +225,7 @@ export async function convertProviderResponse(options) {
|
|
|
165
225
|
clientPayload,
|
|
166
226
|
clientProtocol,
|
|
167
227
|
requestId: options.context.requestId,
|
|
168
|
-
wantsStream
|
|
228
|
+
wantsStream,
|
|
169
229
|
stageRecorder: options.stageRecorder
|
|
170
230
|
});
|
|
171
231
|
if (outbound.stream) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AdapterContext } from '../types/chat-envelope.js';
|
|
2
|
+
import type { JsonObject } from '../types/json.js';
|
|
3
|
+
export type ProviderInvoker = (options: {
|
|
4
|
+
providerKey: string;
|
|
5
|
+
providerType?: string;
|
|
6
|
+
modelId?: string;
|
|
7
|
+
providerProtocol: string;
|
|
8
|
+
payload: JsonObject;
|
|
9
|
+
entryEndpoint: string;
|
|
10
|
+
requestId: string;
|
|
11
|
+
}) => Promise<{
|
|
12
|
+
providerResponse: JsonObject;
|
|
13
|
+
}>;
|
|
14
|
+
export interface ServerSideToolEngineOptions {
|
|
15
|
+
chatResponse: JsonObject;
|
|
16
|
+
adapterContext: AdapterContext;
|
|
17
|
+
entryEndpoint: string;
|
|
18
|
+
requestId: string;
|
|
19
|
+
providerProtocol: string;
|
|
20
|
+
providerInvoker?: ProviderInvoker;
|
|
21
|
+
}
|
|
22
|
+
export interface ServerSideToolEngineResult {
|
|
23
|
+
mode: 'passthrough' | 'web_search_flow';
|
|
24
|
+
finalChatResponse: JsonObject;
|
|
25
|
+
}
|
|
26
|
+
export declare function runServerSideToolEngine(options: ServerSideToolEngineOptions): Promise<ServerSideToolEngineResult>;
|