@jsonstudio/rcc 0.89.333 → 0.89.524
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 +62 -0
- 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 +4 -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 +261 -22
- 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/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 +41 -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 +37 -2
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- 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/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/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 +69 -0
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/runtime-manager.js +18 -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/index.d.ts +7 -0
- package/dist/token-daemon/index.js +242 -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 +20 -0
- package/dist/token-daemon/token-daemon.js +144 -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/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 +2 -2
- package/scripts/pack-mode.mjs +2 -1
- package/scripts/publish-rcc.mjs +20 -4
- 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
|
@@ -4,6 +4,7 @@ import { normalizeChatMessageContent } from '../shared/chat-output-normalizer.js
|
|
|
4
4
|
import { mapBridgeToolsToChat } from '../shared/tool-mapping.js';
|
|
5
5
|
import { prepareGeminiToolsForBridge } from '../shared/gemini-tool-utils.js';
|
|
6
6
|
import { registerResponsesReasoning, consumeResponsesReasoning, registerResponsesOutputTextMeta, consumeResponsesOutputTextMeta, consumeResponsesPayloadSnapshot, registerResponsesPayloadSnapshot, consumeResponsesPassthrough, registerResponsesPassthrough } from '../shared/responses-reasoning-registry.js';
|
|
7
|
+
const DUMMY_THOUGHT_SIGNATURE = 'skip_thought_signature_validator';
|
|
7
8
|
function isObject(v) {
|
|
8
9
|
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
9
10
|
}
|
|
@@ -54,6 +55,32 @@ function mapChatRoleToGemini(role) {
|
|
|
54
55
|
return 'tool';
|
|
55
56
|
return 'user';
|
|
56
57
|
}
|
|
58
|
+
function coerceThoughtSignature(value) {
|
|
59
|
+
if (typeof value === 'string' && value.trim().length) {
|
|
60
|
+
return value.trim();
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
function extractThoughtSignatureFromToolCall(tc) {
|
|
65
|
+
if (!tc || typeof tc !== 'object') {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
const direct = coerceThoughtSignature(tc.thought_signature ?? tc.thoughtSignature);
|
|
69
|
+
if (direct) {
|
|
70
|
+
return direct;
|
|
71
|
+
}
|
|
72
|
+
const extraContent = tc.extra_content ?? tc.extraContent;
|
|
73
|
+
if (extraContent && typeof extraContent === 'object') {
|
|
74
|
+
const googleNode = extraContent.google ?? extraContent.Google;
|
|
75
|
+
if (googleNode && typeof googleNode === 'object') {
|
|
76
|
+
const googleSig = coerceThoughtSignature(googleNode.thought_signature ?? googleNode.thoughtSignature);
|
|
77
|
+
if (googleSig) {
|
|
78
|
+
return googleSig;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
57
84
|
export function buildOpenAIChatFromGeminiRequest(payload) {
|
|
58
85
|
const messages = [];
|
|
59
86
|
// systemInstruction → Chat system 消息
|
|
@@ -156,16 +183,22 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
156
183
|
const textParts = [];
|
|
157
184
|
const reasoningParts = [];
|
|
158
185
|
const toolCalls = [];
|
|
186
|
+
const toolResultTexts = [];
|
|
187
|
+
const toolOutputs = [];
|
|
188
|
+
// 为当前响应内生成稳定的工具调用 ID,避免下游 ServerTool 因缺少 id 而跳过。
|
|
189
|
+
let toolCallCounter = 0;
|
|
159
190
|
for (const part of parts) {
|
|
160
191
|
if (!part || typeof part !== 'object')
|
|
161
192
|
continue;
|
|
162
193
|
const pObj = part;
|
|
194
|
+
// 1. Text part
|
|
163
195
|
if (typeof pObj.text === 'string') {
|
|
164
196
|
const t = pObj.text;
|
|
165
197
|
if (t && t.trim().length)
|
|
166
198
|
textParts.push(t);
|
|
167
199
|
continue;
|
|
168
200
|
}
|
|
201
|
+
// 2. Content array (nested structure)
|
|
169
202
|
if (Array.isArray(pObj.content)) {
|
|
170
203
|
for (const inner of pObj.content) {
|
|
171
204
|
if (typeof inner === 'string') {
|
|
@@ -177,16 +210,26 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
177
210
|
}
|
|
178
211
|
continue;
|
|
179
212
|
}
|
|
213
|
+
// 3. Reasoning part (channel mode)
|
|
180
214
|
if (typeof pObj.reasoning === 'string') {
|
|
181
215
|
reasoningParts.push(pObj.reasoning);
|
|
182
216
|
continue;
|
|
183
217
|
}
|
|
218
|
+
// 4. Thought part (thinking/extended thinking)
|
|
219
|
+
if (typeof pObj.thought === 'string') {
|
|
220
|
+
const thoughtText = pObj.thought.trim();
|
|
221
|
+
if (thoughtText.length) {
|
|
222
|
+
reasoningParts.push(thoughtText);
|
|
223
|
+
}
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
// 5. Function call (tool call)
|
|
184
227
|
if (pObj.functionCall && typeof pObj.functionCall === 'object') {
|
|
185
228
|
const fc = pObj.functionCall;
|
|
186
229
|
const name = typeof fc.name === 'string' ? String(fc.name) : undefined;
|
|
187
230
|
if (!name)
|
|
188
231
|
continue;
|
|
189
|
-
|
|
232
|
+
let id = typeof fc.id === 'string' && fc.id.trim().length ? String(fc.id).trim() : undefined;
|
|
190
233
|
const argsRaw = (fc.args ?? fc.arguments);
|
|
191
234
|
let argsStr;
|
|
192
235
|
if (typeof argsRaw === 'string') {
|
|
@@ -195,15 +238,91 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
195
238
|
else {
|
|
196
239
|
argsStr = safeJson(argsRaw);
|
|
197
240
|
}
|
|
198
|
-
|
|
241
|
+
const thoughtSignature = coerceThoughtSignature(pObj.thoughtSignature);
|
|
242
|
+
// Gemini 某些响应中的 functionCall 不带 id,但下游 servertool 需要稳定的
|
|
243
|
+
// tool_call.id 才会识别为有效工具调用;此处在缺失时生成本响应内唯一的占位 ID。
|
|
244
|
+
if (!id) {
|
|
245
|
+
const suffix = toolCallCounter++;
|
|
246
|
+
id = `gemini_tool_${suffix}`;
|
|
247
|
+
}
|
|
248
|
+
const toolCall = {
|
|
199
249
|
id,
|
|
200
250
|
type: 'function',
|
|
201
251
|
function: { name, arguments: argsStr }
|
|
202
|
-
}
|
|
252
|
+
};
|
|
253
|
+
if (thoughtSignature) {
|
|
254
|
+
toolCall.thought_signature = thoughtSignature;
|
|
255
|
+
toolCall.extra_content = {
|
|
256
|
+
google: {
|
|
257
|
+
thought_signature: thoughtSignature
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
toolCalls.push(toolCall);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
// 6. Function response (tool result)
|
|
265
|
+
if (pObj.functionResponse && typeof pObj.functionResponse === 'object') {
|
|
266
|
+
const fr = pObj.functionResponse;
|
|
267
|
+
const callId = typeof fr.id === 'string' && fr.id.trim().length ? String(fr.id) : undefined;
|
|
268
|
+
const name = typeof fr.name === 'string' && fr.name.trim().length ? String(fr.name) : undefined;
|
|
269
|
+
const resp = fr.response;
|
|
270
|
+
let contentStr = '';
|
|
271
|
+
if (typeof resp === 'string') {
|
|
272
|
+
contentStr = resp;
|
|
273
|
+
}
|
|
274
|
+
else if (resp != null) {
|
|
275
|
+
try {
|
|
276
|
+
contentStr = JSON.stringify(resp);
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
contentStr = String(resp);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (contentStr && contentStr.trim().length) {
|
|
283
|
+
toolResultTexts.push(contentStr);
|
|
284
|
+
if (callId || name) {
|
|
285
|
+
const entry = {
|
|
286
|
+
tool_call_id: callId ?? undefined,
|
|
287
|
+
id: callId ?? undefined,
|
|
288
|
+
content: contentStr
|
|
289
|
+
};
|
|
290
|
+
if (name) {
|
|
291
|
+
entry.name = name;
|
|
292
|
+
}
|
|
293
|
+
toolOutputs.push(entry);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
// 7. Executable code (code_interpreter)
|
|
299
|
+
if (pObj.executableCode && typeof pObj.executableCode === 'object') {
|
|
300
|
+
const code = pObj.executableCode;
|
|
301
|
+
const language = typeof code.language === 'string' ? code.language : 'python';
|
|
302
|
+
const codeText = typeof code.code === 'string' ? code.code : '';
|
|
303
|
+
if (codeText.trim().length) {
|
|
304
|
+
// Append as text with code block formatting
|
|
305
|
+
textParts.push(`\`\`\`${language}\n${codeText}\n\`\`\``);
|
|
306
|
+
}
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
// 8. Code execution result
|
|
310
|
+
if (pObj.codeExecutionResult && typeof pObj.codeExecutionResult === 'object') {
|
|
311
|
+
const result = pObj.codeExecutionResult;
|
|
312
|
+
const outcome = typeof result.outcome === 'string' ? result.outcome : '';
|
|
313
|
+
const output = typeof result.output === 'string' ? result.output : '';
|
|
314
|
+
if (output.trim().length) {
|
|
315
|
+
textParts.push(`[Code Output${outcome ? ` (${outcome})` : ''}]:\n${output}`);
|
|
316
|
+
}
|
|
203
317
|
continue;
|
|
204
318
|
}
|
|
205
319
|
}
|
|
320
|
+
const hasToolCalls = toolCalls.length > 0;
|
|
206
321
|
const finish_reason = (() => {
|
|
322
|
+
// If the model is emitting tool calls, treat this turn as a tool_calls
|
|
323
|
+
// completion so downstream tool governance can continue the loop.
|
|
324
|
+
if (hasToolCalls)
|
|
325
|
+
return 'tool_calls';
|
|
207
326
|
const fr = String(primary?.finishReason || '').toUpperCase();
|
|
208
327
|
if (fr === 'MAX_TOKENS')
|
|
209
328
|
return 'length';
|
|
@@ -228,9 +347,14 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
228
347
|
usage.total_tokens = totalTokens;
|
|
229
348
|
const combinedText = textParts.join('\n');
|
|
230
349
|
const normalized = combinedText.length ? normalizeChatMessageContent(combinedText) : { contentText: undefined, reasoningText: undefined };
|
|
350
|
+
const baseContent = normalized.contentText ?? combinedText ?? '';
|
|
351
|
+
const toolResultBlock = toolResultTexts.length ? toolResultTexts.join('\n') : '';
|
|
352
|
+
const finalContent = toolResultBlock && baseContent
|
|
353
|
+
? `${baseContent}\n${toolResultBlock}`
|
|
354
|
+
: baseContent || toolResultBlock;
|
|
231
355
|
const chatMsg = {
|
|
232
356
|
role,
|
|
233
|
-
content:
|
|
357
|
+
content: finalContent
|
|
234
358
|
};
|
|
235
359
|
if (typeof normalized.reasoningText === 'string' && normalized.reasoningText.trim().length) {
|
|
236
360
|
reasoningParts.push(normalized.reasoningText.trim());
|
|
@@ -277,6 +401,9 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
277
401
|
if (Object.keys(usage).length > 0) {
|
|
278
402
|
chatResp.usage = usage;
|
|
279
403
|
}
|
|
404
|
+
if (toolOutputs.length > 0) {
|
|
405
|
+
chatResp.tool_outputs = toolOutputs;
|
|
406
|
+
}
|
|
280
407
|
const preservedReasoning = consumeResponsesReasoning(chatResp.id);
|
|
281
408
|
if (preservedReasoning && preservedReasoning.length) {
|
|
282
409
|
chatResp.__responses_reasoning = preservedReasoning;
|
|
@@ -398,7 +525,12 @@ export function buildGeminiFromOpenAIChat(chatResp) {
|
|
|
398
525
|
const id = typeof tc.id === 'string' ? String(tc.id) : undefined;
|
|
399
526
|
if (id)
|
|
400
527
|
functionCall.id = id;
|
|
401
|
-
|
|
528
|
+
const thoughtSignature = extractThoughtSignatureFromToolCall(tc) ?? DUMMY_THOUGHT_SIGNATURE;
|
|
529
|
+
const partEntry = { functionCall };
|
|
530
|
+
if (thoughtSignature) {
|
|
531
|
+
partEntry.thoughtSignature = thoughtSignature;
|
|
532
|
+
}
|
|
533
|
+
parts.push(partEntry);
|
|
402
534
|
}
|
|
403
535
|
const candidate = {
|
|
404
536
|
content: {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { JsonObject } from '../../hub/types/json.js';
|
|
2
|
+
import type { AdapterContext } from '../../hub/types/chat-envelope.js';
|
|
3
|
+
/**
|
|
4
|
+
* Gemini web_search 请求适配(作用于 Gemini 请求 payload,而不是 ChatEnvelope):
|
|
5
|
+
*
|
|
6
|
+
* - 仅在 routeId 以 `web_search` 开头时生效(来自 AdapterContext.routeId);
|
|
7
|
+
* - 针对 Gemini 请求中的 `tools` 字段进行清洗:
|
|
8
|
+
* - 保留 name === 'web_search' 的 functionDeclarations(如果存在);
|
|
9
|
+
* - 丢弃其它 Codex 自身工具(exec_command / MCP 等),避免 Cloud Code 报
|
|
10
|
+
* “Multiple tools are supported only when they are all search tools.”;
|
|
11
|
+
* - 如果清洗后没有任何工具,则删除 `tools` 字段。
|
|
12
|
+
*
|
|
13
|
+
* 注意:
|
|
14
|
+
* - 这里处理的是 llmswitch-core 为 gemini-chat 构造的中间 payload(buildGeminiRequestFromChat 输出);
|
|
15
|
+
* - 对于 Gemini CLI 后续在传输层添加的 googleSearch 工具,本适配不负责构造或修改。
|
|
16
|
+
*/
|
|
17
|
+
export declare function applyGeminiWebSearchCompat(payload: JsonObject, adapterContext?: AdapterContext): JsonObject;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2
|
+
/**
|
|
3
|
+
* Gemini web_search 请求适配(作用于 Gemini 请求 payload,而不是 ChatEnvelope):
|
|
4
|
+
*
|
|
5
|
+
* - 仅在 routeId 以 `web_search` 开头时生效(来自 AdapterContext.routeId);
|
|
6
|
+
* - 针对 Gemini 请求中的 `tools` 字段进行清洗:
|
|
7
|
+
* - 保留 name === 'web_search' 的 functionDeclarations(如果存在);
|
|
8
|
+
* - 丢弃其它 Codex 自身工具(exec_command / MCP 等),避免 Cloud Code 报
|
|
9
|
+
* “Multiple tools are supported only when they are all search tools.”;
|
|
10
|
+
* - 如果清洗后没有任何工具,则删除 `tools` 字段。
|
|
11
|
+
*
|
|
12
|
+
* 注意:
|
|
13
|
+
* - 这里处理的是 llmswitch-core 为 gemini-chat 构造的中间 payload(buildGeminiRequestFromChat 输出);
|
|
14
|
+
* - 对于 Gemini CLI 后续在传输层添加的 googleSearch 工具,本适配不负责构造或修改。
|
|
15
|
+
*/
|
|
16
|
+
export function applyGeminiWebSearchCompat(payload, adapterContext) {
|
|
17
|
+
const routeId = typeof adapterContext?.routeId === 'string' ? adapterContext.routeId : '';
|
|
18
|
+
if (!routeId || !routeId.toLowerCase().startsWith('web_search')) {
|
|
19
|
+
return payload;
|
|
20
|
+
}
|
|
21
|
+
const cloned = { ...payload };
|
|
22
|
+
// 对于 Gemini Chat 后端,请求根节点不支持自定义 `web_search` 字段,
|
|
23
|
+
// 统一在 compat 层移除,避免触发
|
|
24
|
+
// "Unknown name \"web_search\" at 'request': Cannot find field." 错误。
|
|
25
|
+
if (cloned.web_search !== undefined) {
|
|
26
|
+
delete cloned.web_search;
|
|
27
|
+
}
|
|
28
|
+
const toolsRaw = cloned.tools;
|
|
29
|
+
// 当 web_search 路由下完全没有 tools 时,为 Gemini 搜索模型注入最小 googleSearch 工具,
|
|
30
|
+
// 保持与 gcli2api 类似的“搜索模型自动启用 Search 工具”行为。
|
|
31
|
+
if (!Array.isArray(toolsRaw)) {
|
|
32
|
+
cloned.tools = [{ googleSearch: {} }];
|
|
33
|
+
return cloned;
|
|
34
|
+
}
|
|
35
|
+
const nextTools = [];
|
|
36
|
+
for (const entry of toolsRaw) {
|
|
37
|
+
if (!isRecord(entry))
|
|
38
|
+
continue;
|
|
39
|
+
// 1) 保留 name === 'web_search' 的 functionDeclarations(单函数声明)。
|
|
40
|
+
const decls = Array.isArray(entry.functionDeclarations)
|
|
41
|
+
? entry.functionDeclarations
|
|
42
|
+
: [];
|
|
43
|
+
const webSearchDecls = decls.filter((fn) => {
|
|
44
|
+
if (!isRecord(fn))
|
|
45
|
+
return false;
|
|
46
|
+
const name = typeof fn.name === 'string' ? fn.name.trim().toLowerCase() : '';
|
|
47
|
+
return name === 'web_search';
|
|
48
|
+
});
|
|
49
|
+
if (webSearchDecls.length) {
|
|
50
|
+
nextTools.push({
|
|
51
|
+
functionDeclarations: webSearchDecls
|
|
52
|
+
});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// 2) 若上游已经构造了 googleSearch 工具,则保留。
|
|
56
|
+
if (isRecord(entry.googleSearch)) {
|
|
57
|
+
nextTools.push({ googleSearch: entry.googleSearch });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (nextTools.length > 0) {
|
|
61
|
+
cloned.tools = nextTools;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// 3) 清洗后没有任何工具时,再次注入一个最小 googleSearch 工具,确保搜索模型仍然启用 Search。
|
|
65
|
+
cloned.tools = [{ googleSearch: {} }];
|
|
66
|
+
}
|
|
67
|
+
return cloned;
|
|
68
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2
|
+
function normalizeImagePart(part) {
|
|
3
|
+
const rawType = typeof part.type === 'string' ? part.type.toLowerCase() : '';
|
|
4
|
+
if (rawType !== 'image' && rawType !== 'image_url') {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
// Try multiple locations for the URL, since different inbound
|
|
8
|
+
// protocols may populate different keys.
|
|
9
|
+
const imageUrlBlock = isRecord(part.image_url)
|
|
10
|
+
? part.image_url
|
|
11
|
+
: undefined;
|
|
12
|
+
let url;
|
|
13
|
+
if (imageUrlBlock && typeof imageUrlBlock.url === 'string') {
|
|
14
|
+
url = imageUrlBlock.url;
|
|
15
|
+
}
|
|
16
|
+
else if (typeof part.image_url === 'string') {
|
|
17
|
+
url = part.image_url;
|
|
18
|
+
}
|
|
19
|
+
else if (typeof part.url === 'string') {
|
|
20
|
+
url = part.url;
|
|
21
|
+
}
|
|
22
|
+
else if (typeof part.uri === 'string') {
|
|
23
|
+
url = part.uri;
|
|
24
|
+
}
|
|
25
|
+
else if (typeof part.data === 'string') {
|
|
26
|
+
// If caller passed a raw base64/data URI string, forward as-is.
|
|
27
|
+
url = part.data;
|
|
28
|
+
}
|
|
29
|
+
if (!url || !url.trim().length) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const normalized = {
|
|
33
|
+
type: 'image_url',
|
|
34
|
+
image_url: {
|
|
35
|
+
url: url.trim()
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
// Preserve a best-effort "detail" field when present.
|
|
39
|
+
const detailValue = (imageUrlBlock && imageUrlBlock.detail) ?? part.detail;
|
|
40
|
+
if (typeof detailValue === 'string' && detailValue.trim().length) {
|
|
41
|
+
normalized.image_url.detail = detailValue.trim();
|
|
42
|
+
}
|
|
43
|
+
return normalized;
|
|
44
|
+
}
|
|
45
|
+
export function applyGlmImageContentTransform(payload) {
|
|
46
|
+
const root = structuredClone(payload);
|
|
47
|
+
const messagesValue = root.messages;
|
|
48
|
+
if (!Array.isArray(messagesValue)) {
|
|
49
|
+
return root;
|
|
50
|
+
}
|
|
51
|
+
const messages = [];
|
|
52
|
+
for (const msg of messagesValue) {
|
|
53
|
+
if (!isRecord(msg)) {
|
|
54
|
+
messages.push(msg);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const contentValue = msg.content;
|
|
58
|
+
if (!Array.isArray(contentValue)) {
|
|
59
|
+
messages.push(msg);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const newContent = [];
|
|
63
|
+
for (const part of contentValue) {
|
|
64
|
+
if (!isRecord(part)) {
|
|
65
|
+
newContent.push(part);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const normalizedImage = normalizeImagePart(part);
|
|
69
|
+
if (normalizedImage) {
|
|
70
|
+
newContent.push(normalizedImage);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
newContent.push(part);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
messages.push({
|
|
77
|
+
...msg,
|
|
78
|
+
content: newContent
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
root.messages = messages;
|
|
82
|
+
return root;
|
|
83
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { JsonObject } from '../../hub/types/json.js';
|
|
2
|
+
/**
|
|
3
|
+
* GLM 4.6V 专用视觉提示裁剪:
|
|
4
|
+
* - 仅在 model 为 glm-4.6v 且存在携带图片的 user 消息时生效;
|
|
5
|
+
* - 丢弃原有 system 与历史对话,只保留一条新的 system + 一条 user;
|
|
6
|
+
* - system:要求模型以结构化 JSON 描述截图内容、标记(圈/箭头等)及其大致 bbox;
|
|
7
|
+
* - user:保留原始用户文字(如果有)+ 单一 image_url 块。
|
|
8
|
+
*
|
|
9
|
+
* 其他模型(包括 glm-4.7、Gemini 等)不受影响。
|
|
10
|
+
*/
|
|
11
|
+
export declare function applyGlmVisionPromptTransform(payload: JsonObject): JsonObject;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2
|
+
function extractImageUrlFromPart(part) {
|
|
3
|
+
const imageUrlBlock = isRecord(part.image_url)
|
|
4
|
+
? part.image_url
|
|
5
|
+
: undefined;
|
|
6
|
+
let url;
|
|
7
|
+
if (imageUrlBlock && typeof imageUrlBlock.url === 'string') {
|
|
8
|
+
url = imageUrlBlock.url;
|
|
9
|
+
}
|
|
10
|
+
else if (typeof part.image_url === 'string') {
|
|
11
|
+
url = part.image_url;
|
|
12
|
+
}
|
|
13
|
+
else if (typeof part.url === 'string') {
|
|
14
|
+
url = part.url;
|
|
15
|
+
}
|
|
16
|
+
else if (typeof part.uri === 'string') {
|
|
17
|
+
url = part.uri;
|
|
18
|
+
}
|
|
19
|
+
else if (typeof part.data === 'string') {
|
|
20
|
+
url = part.data;
|
|
21
|
+
}
|
|
22
|
+
const trimmed = (url ?? '').trim();
|
|
23
|
+
return trimmed.length ? trimmed : null;
|
|
24
|
+
}
|
|
25
|
+
function collectUserTextFromMessage(msg) {
|
|
26
|
+
const content = msg.content;
|
|
27
|
+
if (!Array.isArray(content)) {
|
|
28
|
+
if (typeof content === 'string') {
|
|
29
|
+
return content;
|
|
30
|
+
}
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
const parts = [];
|
|
34
|
+
for (const entry of content) {
|
|
35
|
+
if (!isRecord(entry))
|
|
36
|
+
continue;
|
|
37
|
+
const text = entry.text;
|
|
38
|
+
if (typeof text === 'string' && text.trim().length) {
|
|
39
|
+
parts.push(text.trim());
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return parts.join('\n');
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* GLM 4.6V 专用视觉提示裁剪:
|
|
46
|
+
* - 仅在 model 为 glm-4.6v 且存在携带图片的 user 消息时生效;
|
|
47
|
+
* - 丢弃原有 system 与历史对话,只保留一条新的 system + 一条 user;
|
|
48
|
+
* - system:要求模型以结构化 JSON 描述截图内容、标记(圈/箭头等)及其大致 bbox;
|
|
49
|
+
* - user:保留原始用户文字(如果有)+ 单一 image_url 块。
|
|
50
|
+
*
|
|
51
|
+
* 其他模型(包括 glm-4.7、Gemini 等)不受影响。
|
|
52
|
+
*/
|
|
53
|
+
export function applyGlmVisionPromptTransform(payload) {
|
|
54
|
+
const root = structuredClone(payload);
|
|
55
|
+
const modelRaw = root.model;
|
|
56
|
+
const model = typeof modelRaw === 'string' ? modelRaw.trim() : '';
|
|
57
|
+
if (!model.startsWith('glm-4.6v')) {
|
|
58
|
+
return root;
|
|
59
|
+
}
|
|
60
|
+
const messagesValue = root.messages;
|
|
61
|
+
if (!Array.isArray(messagesValue)) {
|
|
62
|
+
return root;
|
|
63
|
+
}
|
|
64
|
+
const messages = messagesValue.filter((msg) => isRecord(msg));
|
|
65
|
+
if (!messages.length) {
|
|
66
|
+
return root;
|
|
67
|
+
}
|
|
68
|
+
// 从末尾开始查找最近一条带图片的 user 消息。
|
|
69
|
+
let latestUserWithImage = null;
|
|
70
|
+
let imageUrl = null;
|
|
71
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
72
|
+
const msg = messages[i];
|
|
73
|
+
const role = typeof msg.role === 'string' ? msg.role.toLowerCase() : '';
|
|
74
|
+
if (role !== 'user') {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const content = msg.content;
|
|
78
|
+
if (!Array.isArray(content)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
for (const part of content) {
|
|
82
|
+
if (!isRecord(part))
|
|
83
|
+
continue;
|
|
84
|
+
const typeValue = typeof part.type === 'string' ? part.type.toLowerCase() : '';
|
|
85
|
+
if (typeValue === 'image' || typeValue === 'image_url' || typeValue === 'input_image') {
|
|
86
|
+
const candidateUrl = extractImageUrlFromPart(part);
|
|
87
|
+
if (candidateUrl) {
|
|
88
|
+
latestUserWithImage = msg;
|
|
89
|
+
imageUrl = candidateUrl;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (latestUserWithImage && imageUrl) {
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!latestUserWithImage || !imageUrl) {
|
|
99
|
+
return root;
|
|
100
|
+
}
|
|
101
|
+
const systemContent = '你是 Codex 的截图理解子系统,专门用于分析 UI 截图和网页图片。请仅根据用户提供的图片,输出一个结构化的 JSON,用于后续自动化处理,不要输出额外解释性文字或自然语言说明。\n' +
|
|
102
|
+
'\n' +
|
|
103
|
+
'输出必须是**单个合法 JSON 对象**,不要包含 Markdown、代码块标记或多余文本。请严格遵循下面的结构(字段可以为空数组,但必须存在):\n' +
|
|
104
|
+
'{\n' +
|
|
105
|
+
' "summary": "用 1-3 句整体描述这张图片(例如页面类型、主要区域、核心信息)",\n' +
|
|
106
|
+
' "marks": [\n' +
|
|
107
|
+
' {\n' +
|
|
108
|
+
' "type": "circle | arrow | underline | box | other",\n' +
|
|
109
|
+
' "color": "red | green | blue | yellow | other",\n' +
|
|
110
|
+
' "bbox": [x, y, width, height],\n' +
|
|
111
|
+
' "description": "该标记所圈出/指向/强调的内容,包含相关文字或 UI 元素描述"\n' +
|
|
112
|
+
' }\n' +
|
|
113
|
+
' ],\n' +
|
|
114
|
+
' "regions": [\n' +
|
|
115
|
+
' {\n' +
|
|
116
|
+
' "bbox": [x, y, width, height],\n' +
|
|
117
|
+
' "description": "该区域的可见内容(控件/图标/布局,以及其中出现的所有清晰可辨的文字)",\n' +
|
|
118
|
+
' "is_marked": true | false\n' +
|
|
119
|
+
' }\n' +
|
|
120
|
+
' ],\n' +
|
|
121
|
+
' "metadata": {\n' +
|
|
122
|
+
' "image_size_hint": "如果能推断出大致分辨率,请给出类似 1920x1080 的字符串;无法判断时用 null",\n' +
|
|
123
|
+
' "screenshot": true\n' +
|
|
124
|
+
' }\n' +
|
|
125
|
+
'}\n' +
|
|
126
|
+
'\n' +
|
|
127
|
+
'细则:\n' +
|
|
128
|
+
'1. 文字提取要求:\n' +
|
|
129
|
+
' - 如果图片中存在清晰可辨的文字(包括标题、菜单、按钮、标签、提示信息、弹窗、错误信息等),必须在对应的 regions.description 中**完整抄写**这些文字,按自然阅读顺序组织,避免遗漏。\n' +
|
|
130
|
+
' - 如果有多行文字,可以用换行符分隔,但仍放在同一个 description 字段中。\n' +
|
|
131
|
+
' - 对确实无法看清的文字,用类似 "(模糊,无法辨认)" 标注即可;没有任何文字也视为正常情况,此时只需描述界面结构。\n' +
|
|
132
|
+
'2. 标记识别(marks):\n' +
|
|
133
|
+
' - 对所有明显的圈选、箭头、下划线、高亮框等标记,必须在 marks 中列出,每一项提供大致 bbox、颜色和简短说明,说明其强调或指向的内容。\n' +
|
|
134
|
+
'3. 区域划分(regions):\n' +
|
|
135
|
+
' - 将截图拆分为若干有意义的区域:如导航栏、侧边栏、主内容区、弹窗、对话框、表格、代码块、表单等。\n' +
|
|
136
|
+
' - 每个区域的 description 中,既要描述布局/控件类型,也要包含该区域内的全部清晰文字内容。\n' +
|
|
137
|
+
' - is_marked 为 true 表示该区域与某个标记(marks)相关或被标记强调。\n' +
|
|
138
|
+
'4. 坐标规范:所有 bbox 使用相对于当前图片的像素坐标,左上角为 (0,0),width/height 为正数近似值。\n' +
|
|
139
|
+
'5. 无论图片内容如何,最终回答必须是合法 JSON,不能在 JSON 前后添加任何额外文本。';
|
|
140
|
+
const systemMessage = {
|
|
141
|
+
role: 'system',
|
|
142
|
+
content: systemContent
|
|
143
|
+
};
|
|
144
|
+
const originalUserText = collectUserTextFromMessage(latestUserWithImage);
|
|
145
|
+
const userBlocks = [];
|
|
146
|
+
if (originalUserText && originalUserText.trim().length) {
|
|
147
|
+
userBlocks.push({
|
|
148
|
+
type: 'text',
|
|
149
|
+
text: originalUserText.trim()
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
userBlocks.push({
|
|
154
|
+
type: 'text',
|
|
155
|
+
text: '请按照上面的 JSON 结构,详细描述这张图片的内容和标记。'
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
userBlocks.push({
|
|
159
|
+
type: 'image_url',
|
|
160
|
+
image_url: {
|
|
161
|
+
url: imageUrl
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
const userMessage = {
|
|
165
|
+
role: 'user',
|
|
166
|
+
content: userBlocks
|
|
167
|
+
};
|
|
168
|
+
// 丢弃原有 messages,仅保留新的 system + user。
|
|
169
|
+
root.messages = [systemMessage, userMessage];
|
|
170
|
+
// 对于专用视觉模型,限制 max_tokens,避免过大的 completion 预算进一步触发上下文相关错误。
|
|
171
|
+
const maxTokensValue = root.max_tokens;
|
|
172
|
+
if (typeof maxTokensValue === 'number' && Number.isFinite(maxTokensValue)) {
|
|
173
|
+
// 将超大值收敛到一个相对安全的上限;真实上限由上游再校验。
|
|
174
|
+
root.max_tokens = Math.min(maxTokensValue, 4096);
|
|
175
|
+
}
|
|
176
|
+
return root;
|
|
177
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2
|
+
const DEBUG_GLM_WEB_SEARCH = (process.env.ROUTECODEX_DEBUG_GLM_WEB_SEARCH || '').trim() === '1';
|
|
3
|
+
export function applyGlmWebSearchRequestTransform(payload) {
|
|
4
|
+
const root = structuredClone(payload);
|
|
5
|
+
const webSearchRaw = root.web_search;
|
|
6
|
+
if (!isRecord(webSearchRaw)) {
|
|
7
|
+
return root;
|
|
8
|
+
}
|
|
9
|
+
const webSearch = webSearchRaw;
|
|
10
|
+
const queryValue = webSearch.query;
|
|
11
|
+
const recencyValue = webSearch.recency;
|
|
12
|
+
const countValue = webSearch.count;
|
|
13
|
+
const query = typeof queryValue === 'string' ? queryValue.trim() : '';
|
|
14
|
+
const recency = typeof recencyValue === 'string' ? recencyValue.trim() : undefined;
|
|
15
|
+
let count;
|
|
16
|
+
if (typeof countValue === 'number' && Number.isFinite(countValue)) {
|
|
17
|
+
const normalized = Math.floor(countValue);
|
|
18
|
+
if (normalized >= 1 && normalized <= 50) {
|
|
19
|
+
count = normalized;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (!query) {
|
|
23
|
+
// No meaningful search query, drop the helper object and passthrough.
|
|
24
|
+
delete root.web_search;
|
|
25
|
+
return root;
|
|
26
|
+
}
|
|
27
|
+
const webSearchConfig = {
|
|
28
|
+
// 按 GLM 文档要求:search_engine 为必填,默认使用 search_std。
|
|
29
|
+
search_engine: 'search_std',
|
|
30
|
+
enable: true,
|
|
31
|
+
search_query: query,
|
|
32
|
+
// 返回搜索结果详情,便于我们在响应中提取摘要或调试。
|
|
33
|
+
search_result: true
|
|
34
|
+
};
|
|
35
|
+
if (recency) {
|
|
36
|
+
webSearchConfig.search_recency_filter = recency;
|
|
37
|
+
}
|
|
38
|
+
if (typeof count === 'number') {
|
|
39
|
+
webSearchConfig.count = count;
|
|
40
|
+
}
|
|
41
|
+
// 根据 OpenAPI:tools.anyOf[*] = WebSearchToolSchema[],即一次只能选择一种工具类型。
|
|
42
|
+
// 在 web_search 路由下,我们只保留 WebSearchToolSchema,丢弃其它 function/retrieval/MCP 工具,
|
|
43
|
+
// 避免混用导致后端忽略 web_search 配置。
|
|
44
|
+
const baseTool = {
|
|
45
|
+
type: 'web_search',
|
|
46
|
+
web_search: webSearchConfig
|
|
47
|
+
};
|
|
48
|
+
root.tools = [baseTool];
|
|
49
|
+
delete root.web_search;
|
|
50
|
+
if (DEBUG_GLM_WEB_SEARCH) {
|
|
51
|
+
try {
|
|
52
|
+
// eslint-disable-next-line no-console
|
|
53
|
+
console.log('\x1b[38;5;27m[compat][glm_web_search_request] applied web_search transform ' +
|
|
54
|
+
`search_engine=${String(baseTool?.web_search?.search_engine ?? 'search_std')} ` +
|
|
55
|
+
`query=${JSON.stringify(query).slice(0, 200)} ` +
|
|
56
|
+
`count=${String(count ?? '')}\x1b[0m`);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// logging best-effort
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return root;
|
|
63
|
+
}
|
package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/universal-shape-filter.js
CHANGED
|
@@ -127,6 +127,17 @@ export class UniversalShapeFilter {
|
|
|
127
127
|
normalized.tool_call_id = msg.tool_call_id;
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
|
+
else if (Array.isArray(msg.content)) {
|
|
131
|
+
normalized.content = msg.content.map((part) => {
|
|
132
|
+
if (typeof part === 'string') {
|
|
133
|
+
return part;
|
|
134
|
+
}
|
|
135
|
+
if (isRecord(part)) {
|
|
136
|
+
return { ...part };
|
|
137
|
+
}
|
|
138
|
+
return part;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
130
141
|
else {
|
|
131
142
|
normalized.content = (msg.content !== null && msg.content !== undefined) ? String(msg.content) : '';
|
|
132
143
|
}
|