@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
|
@@ -2,7 +2,9 @@ import type { ConversionCodec, ConversionContext, ConversionProfile } from '../t
|
|
|
2
2
|
export declare function buildOpenAIChatFromGeminiRequest(payload: any): {
|
|
3
3
|
messages: any[];
|
|
4
4
|
};
|
|
5
|
-
export declare function buildOpenAIChatFromGeminiResponse(payload: any
|
|
5
|
+
export declare function buildOpenAIChatFromGeminiResponse(payload: any, options?: {
|
|
6
|
+
aliasMap?: Record<string, string>;
|
|
7
|
+
}): any;
|
|
6
8
|
export declare function buildGeminiFromOpenAIChat(chatResp: any): any;
|
|
7
9
|
export declare class GeminiOpenAIConversionCodec implements ConversionCodec {
|
|
8
10
|
private readonly _dependencies;
|
|
@@ -175,7 +175,7 @@ export function buildOpenAIChatFromGeminiRequest(payload) {
|
|
|
175
175
|
}
|
|
176
176
|
return { messages };
|
|
177
177
|
}
|
|
178
|
-
export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
178
|
+
export function buildOpenAIChatFromGeminiResponse(payload, options) {
|
|
179
179
|
const candidates = Array.isArray(payload?.candidates) ? payload.candidates : [];
|
|
180
180
|
const primary = candidates[0] && typeof candidates[0] === 'object' ? candidates[0] : {};
|
|
181
181
|
const content = primary?.content || {};
|
|
@@ -229,9 +229,12 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
229
229
|
// 5. Function call (tool call)
|
|
230
230
|
if (pObj.functionCall && typeof pObj.functionCall === 'object') {
|
|
231
231
|
const fc = pObj.functionCall;
|
|
232
|
-
const
|
|
233
|
-
if (!
|
|
232
|
+
const nameRaw = typeof fc.name === 'string' ? String(fc.name) : undefined;
|
|
233
|
+
if (!nameRaw)
|
|
234
234
|
continue;
|
|
235
|
+
const name = options?.aliasMap && typeof options.aliasMap[nameRaw] === 'string'
|
|
236
|
+
? String(options.aliasMap[nameRaw])
|
|
237
|
+
: nameRaw;
|
|
235
238
|
let id = typeof fc.id === 'string' && fc.id.trim().length ? String(fc.id).trim() : undefined;
|
|
236
239
|
const argsRaw = (fc.args ?? fc.arguments);
|
|
237
240
|
let argsStr;
|
|
@@ -268,7 +271,10 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
268
271
|
if (pObj.functionResponse && typeof pObj.functionResponse === 'object') {
|
|
269
272
|
const fr = pObj.functionResponse;
|
|
270
273
|
const callId = typeof fr.id === 'string' && fr.id.trim().length ? String(fr.id) : undefined;
|
|
271
|
-
const
|
|
274
|
+
const nameRaw = typeof fr.name === 'string' && fr.name.trim().length ? String(fr.name) : undefined;
|
|
275
|
+
const name = nameRaw && options?.aliasMap && typeof options.aliasMap[nameRaw] === 'string'
|
|
276
|
+
? String(options.aliasMap[nameRaw])
|
|
277
|
+
: nameRaw;
|
|
272
278
|
const resp = fr.response;
|
|
273
279
|
let contentStr = '';
|
|
274
280
|
if (typeof resp === 'string') {
|
|
@@ -3,7 +3,7 @@ import type { AdapterContext } from '../../hub/types/chat-envelope.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Gemini web_search 请求适配(作用于 Gemini 请求 payload,而不是 ChatEnvelope):
|
|
5
5
|
*
|
|
6
|
-
* - 仅在 routeId 以 `web_search` 开头时生效(来自 AdapterContext.routeId);
|
|
6
|
+
* - 仅在 routeId 以 `web_search` 或 `search` 开头时生效(来自 AdapterContext.routeId);
|
|
7
7
|
* - 针对 Gemini 请求中的 `tools` 字段进行清洗:
|
|
8
8
|
* - 保留 name === 'web_search' 的 functionDeclarations(如果存在);
|
|
9
9
|
* - 丢弃其它 Codex 自身工具(exec_command / MCP 等),避免 Cloud Code 报
|
|
@@ -2,7 +2,7 @@ const isRecord = (value) => typeof value === 'object' && value !== null && !Arra
|
|
|
2
2
|
/**
|
|
3
3
|
* Gemini web_search 请求适配(作用于 Gemini 请求 payload,而不是 ChatEnvelope):
|
|
4
4
|
*
|
|
5
|
-
* - 仅在 routeId 以 `web_search` 开头时生效(来自 AdapterContext.routeId);
|
|
5
|
+
* - 仅在 routeId 以 `web_search` 或 `search` 开头时生效(来自 AdapterContext.routeId);
|
|
6
6
|
* - 针对 Gemini 请求中的 `tools` 字段进行清洗:
|
|
7
7
|
* - 保留 name === 'web_search' 的 functionDeclarations(如果存在);
|
|
8
8
|
* - 丢弃其它 Codex 自身工具(exec_command / MCP 等),避免 Cloud Code 报
|
|
@@ -15,7 +15,10 @@ const isRecord = (value) => typeof value === 'object' && value !== null && !Arra
|
|
|
15
15
|
*/
|
|
16
16
|
export function applyGeminiWebSearchCompat(payload, adapterContext) {
|
|
17
17
|
const routeId = typeof adapterContext?.routeId === 'string' ? adapterContext.routeId : '';
|
|
18
|
-
|
|
18
|
+
const normalizedRoute = routeId.trim().toLowerCase();
|
|
19
|
+
// Some hosts/configs use "search" as the route name for web search engines.
|
|
20
|
+
// Treat both "web_search*" and "search*" as web-search routes.
|
|
21
|
+
if (!normalizedRoute || (!normalizedRoute.startsWith('web_search') && !normalizedRoute.startsWith('search'))) {
|
|
19
22
|
return payload;
|
|
20
23
|
}
|
|
21
24
|
const cloned = { ...payload };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { JsonObject } from '../../hub/types/json.js';
|
|
2
|
+
/**
|
|
3
|
+
* Some iFlow models reject OpenAI `tools`/`tool_choice` fields with a non-standard 200 error envelope.
|
|
4
|
+
*
|
|
5
|
+
* For such models, we fall back to "text tool calls":
|
|
6
|
+
* - remove top-level `tools` / `tool_choice` so upstream accepts the request
|
|
7
|
+
* - inject a system instruction telling the model to emit `<tool:...>` blocks
|
|
8
|
+
* - response pipeline will harvest those blocks into canonical `tool_calls`
|
|
9
|
+
*/
|
|
10
|
+
export declare function applyIflowToolTextFallback(payload: JsonObject, options?: {
|
|
11
|
+
models?: string[];
|
|
12
|
+
}): JsonObject;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2
|
+
function normalizeModel(value) {
|
|
3
|
+
return typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
4
|
+
}
|
|
5
|
+
function hasNonEmptyArray(value) {
|
|
6
|
+
return Array.isArray(value) && value.length > 0;
|
|
7
|
+
}
|
|
8
|
+
function ensureSystemMessage(messages) {
|
|
9
|
+
const first = messages[0];
|
|
10
|
+
if (isRecord(first) && typeof first.role === 'string' && first.role.toLowerCase() === 'system') {
|
|
11
|
+
return first;
|
|
12
|
+
}
|
|
13
|
+
const sys = { role: 'system', content: '' };
|
|
14
|
+
messages.unshift(sys);
|
|
15
|
+
return sys;
|
|
16
|
+
}
|
|
17
|
+
function appendSystemText(sys, extra) {
|
|
18
|
+
const prev = typeof sys.content === 'string' ? sys.content : '';
|
|
19
|
+
if (prev.includes('## Tool Calls (Text Markup Mode)')) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const combined = prev ? `${prev}\n\n${extra}` : extra;
|
|
23
|
+
sys.content = combined;
|
|
24
|
+
}
|
|
25
|
+
function buildToolMarkupInstruction() {
|
|
26
|
+
// Keep this concise. The response-side canonicalizer will harvest these blocks into tool_calls.
|
|
27
|
+
return [
|
|
28
|
+
'## Tool Calls (Text Markup Mode)',
|
|
29
|
+
'',
|
|
30
|
+
'You MAY call tools by emitting one or more XML blocks with this exact format, and nothing else inside the block:',
|
|
31
|
+
'',
|
|
32
|
+
'<tool:exec_command>',
|
|
33
|
+
'<command>...</command>',
|
|
34
|
+
'<timeout_ms>10000</timeout_ms>',
|
|
35
|
+
'</tool:exec_command>',
|
|
36
|
+
'',
|
|
37
|
+
'<tool:write_stdin>',
|
|
38
|
+
'<session_id>...</session_id>',
|
|
39
|
+
'<input>...</input>',
|
|
40
|
+
'</tool:write_stdin>',
|
|
41
|
+
'',
|
|
42
|
+
'Rules:',
|
|
43
|
+
'- Use `<tool:exec_command>` to run shell commands.',
|
|
44
|
+
'- Use `<tool:write_stdin>` to send input to an existing session.',
|
|
45
|
+
'- Do NOT wrap these blocks in code fences.',
|
|
46
|
+
'- Do NOT invent tools; only use exec_command and write_stdin.',
|
|
47
|
+
''
|
|
48
|
+
].join('\n');
|
|
49
|
+
}
|
|
50
|
+
function stringifyToolOutput(value) {
|
|
51
|
+
if (typeof value === 'string')
|
|
52
|
+
return value;
|
|
53
|
+
if (value === null || value === undefined)
|
|
54
|
+
return '';
|
|
55
|
+
try {
|
|
56
|
+
return JSON.stringify(value, null, 2);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return String(value);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function coerceToolCallToXmlBlock(toolCall) {
|
|
63
|
+
if (!isRecord(toolCall))
|
|
64
|
+
return null;
|
|
65
|
+
const type = typeof toolCall.type === 'string' ? toolCall.type.trim().toLowerCase() : '';
|
|
66
|
+
if (type && type !== 'function')
|
|
67
|
+
return null;
|
|
68
|
+
const fn = isRecord(toolCall.function) ? toolCall.function : null;
|
|
69
|
+
const name = typeof fn?.name === 'string' ? fn.name.trim() : '';
|
|
70
|
+
if (!name)
|
|
71
|
+
return null;
|
|
72
|
+
const argsRaw = fn?.arguments;
|
|
73
|
+
let args = null;
|
|
74
|
+
if (typeof argsRaw === 'string' && argsRaw.trim().length) {
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(argsRaw);
|
|
77
|
+
args = isRecord(parsed) ? parsed : null;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
args = { raw: argsRaw };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (isRecord(argsRaw)) {
|
|
84
|
+
args = argsRaw;
|
|
85
|
+
}
|
|
86
|
+
if (name === 'exec_command') {
|
|
87
|
+
const command = typeof args?.cmd === 'string'
|
|
88
|
+
? args.cmd
|
|
89
|
+
: typeof args?.command === 'string'
|
|
90
|
+
? args.command
|
|
91
|
+
: '';
|
|
92
|
+
if (!command)
|
|
93
|
+
return null;
|
|
94
|
+
const timeoutMs = typeof args?.timeout_ms === 'number' && Number.isFinite(args.timeout_ms)
|
|
95
|
+
? Math.floor(args.timeout_ms)
|
|
96
|
+
: undefined;
|
|
97
|
+
return [
|
|
98
|
+
'<tool:exec_command>',
|
|
99
|
+
`<command>${command}</command>`,
|
|
100
|
+
...(timeoutMs !== undefined ? [`<timeout_ms>${timeoutMs}</timeout_ms>`] : []),
|
|
101
|
+
'</tool:exec_command>'
|
|
102
|
+
].join('\n');
|
|
103
|
+
}
|
|
104
|
+
if (name === 'write_stdin') {
|
|
105
|
+
const sessionId = typeof args?.session_id === 'string'
|
|
106
|
+
? args.session_id
|
|
107
|
+
: typeof args?.sessionId === 'string'
|
|
108
|
+
? args.sessionId
|
|
109
|
+
: '';
|
|
110
|
+
const input = typeof args?.chars === 'string'
|
|
111
|
+
? args.chars
|
|
112
|
+
: typeof args?.input === 'string'
|
|
113
|
+
? args.input
|
|
114
|
+
: '';
|
|
115
|
+
if (!sessionId || !input)
|
|
116
|
+
return null;
|
|
117
|
+
return [
|
|
118
|
+
'<tool:write_stdin>',
|
|
119
|
+
`<session_id>${sessionId}</session_id>`,
|
|
120
|
+
`<input>${input}</input>`,
|
|
121
|
+
'</tool:write_stdin>'
|
|
122
|
+
].join('\n');
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
function rewriteMessageToolSurfaceInPlace(message) {
|
|
127
|
+
const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
|
|
128
|
+
// Tool result messages are not supported by some iFlow models; convert them into plain text.
|
|
129
|
+
if (role === 'tool') {
|
|
130
|
+
const toolName = typeof message.name === 'string' ? message.name.trim() : '';
|
|
131
|
+
const toolCallId = typeof message.tool_call_id === 'string' ? String(message.tool_call_id).trim() : '';
|
|
132
|
+
const content = stringifyToolOutput(message.content);
|
|
133
|
+
message.role = 'user';
|
|
134
|
+
message.content = [
|
|
135
|
+
'Tool result:',
|
|
136
|
+
...(toolName ? [`name: ${toolName}`] : []),
|
|
137
|
+
...(toolCallId ? [`tool_call_id: ${toolCallId}`] : []),
|
|
138
|
+
'output:',
|
|
139
|
+
content
|
|
140
|
+
].join('\n');
|
|
141
|
+
delete message.name;
|
|
142
|
+
delete message.tool_call_id;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// Assistant tool_calls are not supported by some iFlow models; convert them to XML tool markup.
|
|
146
|
+
const toolCallsRaw = message.tool_calls;
|
|
147
|
+
if (hasNonEmptyArray(toolCallsRaw)) {
|
|
148
|
+
const blocks = toolCallsRaw
|
|
149
|
+
.map(coerceToolCallToXmlBlock)
|
|
150
|
+
.filter((b) => typeof b === 'string' && b.length > 0);
|
|
151
|
+
if (blocks.length) {
|
|
152
|
+
const prev = typeof message.content === 'string' ? message.content : '';
|
|
153
|
+
const joined = blocks.join('\n\n');
|
|
154
|
+
message.content = prev && prev.trim().length ? `${prev}\n\n${joined}` : joined;
|
|
155
|
+
}
|
|
156
|
+
delete message.tool_calls;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Some iFlow models reject OpenAI `tools`/`tool_choice` fields with a non-standard 200 error envelope.
|
|
161
|
+
*
|
|
162
|
+
* For such models, we fall back to "text tool calls":
|
|
163
|
+
* - remove top-level `tools` / `tool_choice` so upstream accepts the request
|
|
164
|
+
* - inject a system instruction telling the model to emit `<tool:...>` blocks
|
|
165
|
+
* - response pipeline will harvest those blocks into canonical `tool_calls`
|
|
166
|
+
*/
|
|
167
|
+
export function applyIflowToolTextFallback(payload, options) {
|
|
168
|
+
if (!payload || typeof payload !== 'object') {
|
|
169
|
+
return payload;
|
|
170
|
+
}
|
|
171
|
+
const root = structuredClone(payload);
|
|
172
|
+
const model = normalizeModel(root.model);
|
|
173
|
+
const models = Array.isArray(options?.models) ? options.models.map((m) => normalizeModel(m)).filter(Boolean) : [];
|
|
174
|
+
if (!model || !models.includes(model)) {
|
|
175
|
+
return root;
|
|
176
|
+
}
|
|
177
|
+
// Only apply when we have a message array to carry the injected instruction.
|
|
178
|
+
const messages = root.messages;
|
|
179
|
+
if (!hasNonEmptyArray(messages)) {
|
|
180
|
+
return root;
|
|
181
|
+
}
|
|
182
|
+
// Always strip tool schemas for fallback models (including followups),
|
|
183
|
+
// otherwise the first turn might succeed but the tool loop breaks.
|
|
184
|
+
if ('tools' in root) {
|
|
185
|
+
delete root.tools;
|
|
186
|
+
}
|
|
187
|
+
if ('tool_choice' in root) {
|
|
188
|
+
delete root.tool_choice;
|
|
189
|
+
}
|
|
190
|
+
// Rewrite message-level tool protocol to plain text so upstream accepts it.
|
|
191
|
+
for (const msg of messages) {
|
|
192
|
+
if (!isRecord(msg))
|
|
193
|
+
continue;
|
|
194
|
+
rewriteMessageToolSurfaceInPlace(msg);
|
|
195
|
+
}
|
|
196
|
+
const sys = ensureSystemMessage(messages);
|
|
197
|
+
appendSystemText(sys, buildToolMarkupInstruction());
|
|
198
|
+
return root;
|
|
199
|
+
}
|
|
@@ -3,7 +3,7 @@ import type { AdapterContext } from '../../hub/types/chat-envelope.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* IFlow web_search 请求适配(作用于 openai-chat 兼容 payload):
|
|
5
5
|
*
|
|
6
|
-
* - 仅在 routeId 以 `web_search` 开头时生效(来自 AdapterContext.routeId);
|
|
6
|
+
* - 仅在 routeId 以 `web_search` 或 `search` 开头时生效(来自 AdapterContext.routeId);
|
|
7
7
|
* - 读取顶层的 `web_search` helper 对象 `{ query, recency, count, engine }`;
|
|
8
8
|
* - 当 query 为空或无效时:删除 helper,原样透传;
|
|
9
9
|
* - 当 query 有效时:构造一个标准的 OpenAI function tool:
|
|
@@ -3,7 +3,7 @@ const DEBUG_IFLOW_WEB_SEARCH = (process.env.ROUTECODEX_DEBUG_IFLOW_WEB_SEARCH ||
|
|
|
3
3
|
/**
|
|
4
4
|
* IFlow web_search 请求适配(作用于 openai-chat 兼容 payload):
|
|
5
5
|
*
|
|
6
|
-
* - 仅在 routeId 以 `web_search` 开头时生效(来自 AdapterContext.routeId);
|
|
6
|
+
* - 仅在 routeId 以 `web_search` 或 `search` 开头时生效(来自 AdapterContext.routeId);
|
|
7
7
|
* - 读取顶层的 `web_search` helper 对象 `{ query, recency, count, engine }`;
|
|
8
8
|
* - 当 query 为空或无效时:删除 helper,原样透传;
|
|
9
9
|
* - 当 query 有效时:构造一个标准的 OpenAI function tool:
|
|
@@ -17,7 +17,10 @@ const DEBUG_IFLOW_WEB_SEARCH = (process.env.ROUTECODEX_DEBUG_IFLOW_WEB_SEARCH ||
|
|
|
17
17
|
*/
|
|
18
18
|
export function applyIflowWebSearchRequestTransform(payload, adapterContext) {
|
|
19
19
|
const routeId = typeof adapterContext?.routeId === 'string' ? adapterContext.routeId : '';
|
|
20
|
-
|
|
20
|
+
const normalizedRoute = routeId.trim().toLowerCase();
|
|
21
|
+
// Some hosts/configs use "search" as the route name for web search engines.
|
|
22
|
+
// Treat both "web_search*" and "search*" as web-search routes.
|
|
23
|
+
if (!normalizedRoute || (!normalizedRoute.startsWith('web_search') && !normalizedRoute.startsWith('search'))) {
|
|
21
24
|
return payload;
|
|
22
25
|
}
|
|
23
26
|
const root = structuredClone(payload);
|
|
@@ -3,7 +3,6 @@ import { buildOpenAIChatFromAnthropic, buildAnthropicRequestFromOpenAIChat } fro
|
|
|
3
3
|
import { encodeMetadataPassthrough, extractMetadataPassthrough } from '../../../shared/metadata-passthrough.js';
|
|
4
4
|
import { buildAnthropicToolAliasMap } from '../../../shared/anthropic-message-utils.js';
|
|
5
5
|
import { ChatSemanticMapper } from './chat-mapper.js';
|
|
6
|
-
import { ensureProtocolState, getProtocolState } from '../../../shared/protocol-state.js';
|
|
7
6
|
const ANTHROPIC_PARAMETER_KEYS = [
|
|
8
7
|
'model',
|
|
9
8
|
'temperature',
|
|
@@ -38,12 +37,12 @@ function ensureSemantics(chat) {
|
|
|
38
37
|
}
|
|
39
38
|
return chat.semantics;
|
|
40
39
|
}
|
|
41
|
-
function
|
|
40
|
+
function ensureToolsSemanticsNode(chat) {
|
|
42
41
|
const semantics = ensureSemantics(chat);
|
|
43
|
-
if (!semantics.
|
|
44
|
-
semantics.
|
|
42
|
+
if (!semantics.tools || !isJsonObject(semantics.tools)) {
|
|
43
|
+
semantics.tools = {};
|
|
45
44
|
}
|
|
46
|
-
return semantics.
|
|
45
|
+
return semantics.tools;
|
|
47
46
|
}
|
|
48
47
|
function markExplicitEmptyTools(chat) {
|
|
49
48
|
const semantics = ensureSemantics(chat);
|
|
@@ -52,11 +51,11 @@ function markExplicitEmptyTools(chat) {
|
|
|
52
51
|
}
|
|
53
52
|
semantics.tools.explicitEmpty = true;
|
|
54
53
|
}
|
|
55
|
-
function
|
|
54
|
+
function readToolsSemantics(chat) {
|
|
56
55
|
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
57
56
|
return undefined;
|
|
58
57
|
}
|
|
59
|
-
const node = chat.semantics.
|
|
58
|
+
const node = chat.semantics.tools;
|
|
60
59
|
return node && isJsonObject(node) ? node : undefined;
|
|
61
60
|
}
|
|
62
61
|
function hasExplicitEmptyToolsSemantics(chat) {
|
|
@@ -126,34 +125,24 @@ export class AnthropicSemanticMapper {
|
|
|
126
125
|
const metadata = chatEnvelope.metadata ?? { context: canonicalContext };
|
|
127
126
|
chatEnvelope.metadata = metadata;
|
|
128
127
|
metadata.context = canonicalContext;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
const protocolState = ensureProtocolState(metadata, 'anthropic');
|
|
128
|
+
const semantics = ensureSemantics(chatEnvelope);
|
|
129
|
+
if (!semantics.system || !isJsonObject(semantics.system)) {
|
|
130
|
+
semantics.system = {};
|
|
131
|
+
}
|
|
132
|
+
if (!semantics.providerExtras || !isJsonObject(semantics.providerExtras)) {
|
|
133
|
+
semantics.providerExtras = {};
|
|
134
|
+
}
|
|
137
135
|
const systemBlocks = cloneAnthropicSystemBlocks(payload.system);
|
|
138
136
|
if (systemBlocks) {
|
|
139
|
-
|
|
140
|
-
semanticsNode = semanticsNode ?? ensureAnthropicSemanticsNode(chatEnvelope);
|
|
141
|
-
semanticsNode.systemBlocks = jsonClone(systemBlocks);
|
|
137
|
+
semantics.system.blocks = jsonClone(systemBlocks);
|
|
142
138
|
}
|
|
143
139
|
if (payload.tools && Array.isArray(payload.tools) && payload.tools.length === 0) {
|
|
144
|
-
metadata.toolsFieldPresent = true;
|
|
145
|
-
resolveExtraFields().toolsFieldPresent = true;
|
|
146
140
|
markExplicitEmptyTools(chatEnvelope);
|
|
147
141
|
}
|
|
148
142
|
const aliasMap = buildAnthropicToolAliasMap(payload.tools);
|
|
149
143
|
if (aliasMap) {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
canonicalContext.anthropicToolNameMap = aliasMap;
|
|
153
|
-
metadata.anthropicToolNameMap = aliasMap;
|
|
154
|
-
extraFields.anthropicToolNameMap = aliasMap;
|
|
155
|
-
semanticsNode = semanticsNode ?? ensureAnthropicSemanticsNode(chatEnvelope);
|
|
156
|
-
semanticsNode.toolAliasMap = jsonClone(aliasMap);
|
|
144
|
+
const toolsNode = ensureToolsSemanticsNode(chatEnvelope);
|
|
145
|
+
toolsNode.toolNameAliasMap = jsonClone(aliasMap);
|
|
157
146
|
}
|
|
158
147
|
if (Array.isArray(payload.messages) && payload.messages.length) {
|
|
159
148
|
const shapes = payload.messages.map((entry) => {
|
|
@@ -172,14 +161,8 @@ export class AnthropicSemanticMapper {
|
|
|
172
161
|
}
|
|
173
162
|
return typeof rawContent;
|
|
174
163
|
});
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
? extraFields.anthropicMirror
|
|
178
|
-
: {};
|
|
179
|
-
mirrorNode.messageContentShape = shapes;
|
|
180
|
-
extraFields.anthropicMirror = mirrorNode;
|
|
181
|
-
semanticsNode = semanticsNode ?? ensureAnthropicSemanticsNode(chatEnvelope);
|
|
182
|
-
semanticsNode.mirror = jsonClone(mirrorNode);
|
|
164
|
+
const mirrorNode = { messageContentShape: shapes };
|
|
165
|
+
semantics.providerExtras.anthropicMirror = jsonClone(mirrorNode);
|
|
183
166
|
}
|
|
184
167
|
if (missing.length) {
|
|
185
168
|
metadata.missingFields = Array.isArray(metadata.missingFields)
|
|
@@ -189,9 +172,7 @@ export class AnthropicSemanticMapper {
|
|
|
189
172
|
const providerMetadata = passthrough.metadata ??
|
|
190
173
|
(payload.metadata && isJsonObject(payload.metadata) ? jsonClone(payload.metadata) : undefined);
|
|
191
174
|
if (providerMetadata) {
|
|
192
|
-
|
|
193
|
-
semanticsNode = semanticsNode ?? ensureAnthropicSemanticsNode(chatEnvelope);
|
|
194
|
-
semanticsNode.providerMetadata = jsonClone(providerMetadata);
|
|
175
|
+
semantics.providerExtras.providerMetadata = jsonClone(providerMetadata);
|
|
195
176
|
}
|
|
196
177
|
const mergedParameters = { ...(chatEnvelope.parameters ?? {}) };
|
|
197
178
|
const mergeParameters = (source) => {
|
|
@@ -232,8 +213,7 @@ export class AnthropicSemanticMapper {
|
|
|
232
213
|
messages: chat.messages,
|
|
233
214
|
tools: chat.tools
|
|
234
215
|
};
|
|
235
|
-
const
|
|
236
|
-
const explicitEmptyTools = (chat.metadata?.toolsFieldPresent === true) || hasExplicitEmptyToolsSemantics(chat);
|
|
216
|
+
const explicitEmptyTools = hasExplicitEmptyToolsSemantics(chat);
|
|
237
217
|
const trimmedParameters = chat.parameters && typeof chat.parameters === 'object' ? chat.parameters : undefined;
|
|
238
218
|
if (trimmedParameters) {
|
|
239
219
|
for (const [key, value] of Object.entries(trimmedParameters)) {
|
|
@@ -268,28 +248,39 @@ export class AnthropicSemanticMapper {
|
|
|
268
248
|
if (explicitEmptyTools && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
269
249
|
baseRequest.tools = [];
|
|
270
250
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
251
|
+
try {
|
|
252
|
+
const sysNode = chat.semantics && typeof chat.semantics === 'object' ? chat.semantics.system : undefined;
|
|
253
|
+
if (sysNode && typeof sysNode === 'object' && !Array.isArray(sysNode) && sysNode.blocks !== undefined) {
|
|
254
|
+
baseRequest.system = jsonClone(sysNode.blocks);
|
|
255
|
+
}
|
|
274
256
|
}
|
|
275
|
-
|
|
276
|
-
|
|
257
|
+
catch {
|
|
258
|
+
// ignore
|
|
277
259
|
}
|
|
278
|
-
|
|
279
|
-
typeof chat.
|
|
280
|
-
|
|
281
|
-
typeof
|
|
282
|
-
|
|
283
|
-
|
|
260
|
+
try {
|
|
261
|
+
const extras = chat.semantics && typeof chat.semantics === 'object' ? chat.semantics.providerExtras : undefined;
|
|
262
|
+
const mirror = extras && typeof extras === 'object' && !Array.isArray(extras) ? extras.anthropicMirror : undefined;
|
|
263
|
+
if (mirror && typeof mirror === 'object' && !Array.isArray(mirror)) {
|
|
264
|
+
baseRequest.__anthropicMirror = jsonClone(mirror);
|
|
265
|
+
}
|
|
266
|
+
const providerMetadata = extras && typeof extras === 'object' && !Array.isArray(extras) ? extras.providerMetadata : undefined;
|
|
267
|
+
if (providerMetadata && typeof providerMetadata === 'object' && !Array.isArray(providerMetadata)) {
|
|
268
|
+
// Only for anthropic-native endpoint: allow restoring anthropic metadata back into outbound.
|
|
269
|
+
const existing = baseRequest.metadata && isJsonObject(baseRequest.metadata)
|
|
270
|
+
? jsonClone(baseRequest.metadata)
|
|
271
|
+
: {};
|
|
272
|
+
const merged = {
|
|
273
|
+
...existing,
|
|
274
|
+
...jsonClone(providerMetadata)
|
|
275
|
+
};
|
|
276
|
+
baseRequest.metadata = merged;
|
|
277
|
+
}
|
|
284
278
|
}
|
|
285
|
-
|
|
286
|
-
|
|
279
|
+
catch {
|
|
280
|
+
// ignore
|
|
287
281
|
}
|
|
288
282
|
const payloadSource = buildAnthropicRequestFromOpenAIChat(baseRequest);
|
|
289
283
|
const payload = sanitizeAnthropicPayload(JSON.parse(JSON.stringify(payloadSource)));
|
|
290
|
-
if (chat.metadata?.toolsFieldPresent && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
291
|
-
payload.tools = [];
|
|
292
|
-
}
|
|
293
284
|
sanitizeAnthropicPayload(payload);
|
|
294
285
|
return {
|
|
295
286
|
protocol: 'anthropic-messages',
|
|
@@ -323,9 +323,6 @@ function applyExtraFields(body, metadata, semantics) {
|
|
|
323
323
|
if (semanticsExtras) {
|
|
324
324
|
sources.push(semanticsExtras);
|
|
325
325
|
}
|
|
326
|
-
if (metadata?.extraFields && isJsonObject(metadata.extraFields)) {
|
|
327
|
-
sources.push(metadata.extraFields);
|
|
328
|
-
}
|
|
329
326
|
if (!sources.length) {
|
|
330
327
|
return;
|
|
331
328
|
}
|
|
@@ -350,9 +347,6 @@ export class ChatSemanticMapper {
|
|
|
350
347
|
}
|
|
351
348
|
}
|
|
352
349
|
const metadata = { context: ctx };
|
|
353
|
-
if (normalized.systemSegments.length) {
|
|
354
|
-
metadata.systemInstructions = normalized.systemSegments;
|
|
355
|
-
}
|
|
356
350
|
const rawSystemBlocks = collectSystemRawBlocks(payload.messages);
|
|
357
351
|
if (rawSystemBlocks) {
|
|
358
352
|
const protocolState = ensureProtocolState(metadata, 'openai');
|
|
@@ -362,13 +356,7 @@ export class ChatSemanticMapper {
|
|
|
362
356
|
metadata.missingFields = normalized.missingFields;
|
|
363
357
|
}
|
|
364
358
|
const extraFields = collectExtraFields(payload);
|
|
365
|
-
if (extraFields) {
|
|
366
|
-
metadata.extraFields = extraFields;
|
|
367
|
-
}
|
|
368
359
|
const explicitEmptyTools = Array.isArray(payload.tools) && payload.tools.length === 0;
|
|
369
|
-
if (explicitEmptyTools) {
|
|
370
|
-
metadata.toolsFieldPresent = true;
|
|
371
|
-
}
|
|
372
360
|
const semantics = buildOpenAISemantics({
|
|
373
361
|
systemSegments: normalized.systemSegments,
|
|
374
362
|
extraFields,
|
|
@@ -384,7 +372,7 @@ export class ChatSemanticMapper {
|
|
|
384
372
|
};
|
|
385
373
|
}
|
|
386
374
|
async fromChat(chat, ctx) {
|
|
387
|
-
const shouldEmitEmptyTools = hasExplicitEmptyToolsSemantics(chat.semantics)
|
|
375
|
+
const shouldEmitEmptyTools = hasExplicitEmptyToolsSemantics(chat.semantics);
|
|
388
376
|
const payload = {
|
|
389
377
|
messages: chat.messages,
|
|
390
378
|
tools: chat.tools ?? (shouldEmitEmptyTools ? [] : undefined),
|