@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
|
@@ -37,30 +37,42 @@ export function detectExtendedThinkingKeyword(text) {
|
|
|
37
37
|
export function detectImageAttachment(message) {
|
|
38
38
|
if (!message)
|
|
39
39
|
return false;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
40
|
+
// 仅基于标准 Chat 语义判断是否携带图片:
|
|
41
|
+
// - content 为数组时查找 { type: 'image' | 'image_url' | 'input_image', ... } 块;
|
|
42
|
+
// - 不再依赖 metadata.attachments,也不再用纯文本关键字或剪贴板标记作为信号。
|
|
43
|
+
if (Array.isArray(message.content)) {
|
|
44
|
+
for (const part of message.content) {
|
|
45
|
+
if (!part || typeof part !== 'object') {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const record = part;
|
|
49
|
+
const typeValue = typeof record.type === 'string' ? record.type.toLowerCase() : '';
|
|
50
|
+
// For chat/standardized content, images may appear as:
|
|
51
|
+
// - { type: "image_url", image_url: { url } }
|
|
52
|
+
// - { type: "image", uri: "...", data: "...", url: "..." }
|
|
53
|
+
// - { type: "input_image", image_url: "data:..." }
|
|
54
|
+
// Treat any non-empty URL/URI/data on an image-* block as a signal.
|
|
55
|
+
let imageCandidate = '';
|
|
56
|
+
if (typeof record.image_url === 'string') {
|
|
57
|
+
imageCandidate = record.image_url ?? '';
|
|
58
|
+
}
|
|
59
|
+
else if (record.image_url &&
|
|
60
|
+
typeof record.image_url?.url === 'string') {
|
|
61
|
+
imageCandidate = record.image_url?.url ?? '';
|
|
62
|
+
}
|
|
63
|
+
else if (typeof record.url === 'string') {
|
|
64
|
+
imageCandidate = record.url ?? '';
|
|
65
|
+
}
|
|
66
|
+
else if (typeof record.uri === 'string') {
|
|
67
|
+
imageCandidate = record.uri ?? '';
|
|
68
|
+
}
|
|
69
|
+
else if (typeof record.data === 'string') {
|
|
70
|
+
imageCandidate = record.data ?? '';
|
|
71
|
+
}
|
|
72
|
+
if (typeValue.includes('image') && imageCandidate.trim().length > 0) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
64
76
|
}
|
|
65
77
|
return false;
|
|
66
78
|
}
|
|
@@ -62,7 +62,8 @@ export class ProviderRegistry {
|
|
|
62
62
|
processMode: profile.processMode || 'chat',
|
|
63
63
|
responsesConfig: profile.responsesConfig,
|
|
64
64
|
streaming: profile.streaming,
|
|
65
|
-
maxContextTokens: profile.maxContextTokens
|
|
65
|
+
maxContextTokens: profile.maxContextTokens,
|
|
66
|
+
...(profile.serverToolsDisabled ? { serverToolsDisabled: true } : {})
|
|
66
67
|
};
|
|
67
68
|
}
|
|
68
69
|
}
|
|
@@ -78,11 +78,22 @@ function encodeContent(content, encoder) {
|
|
|
78
78
|
total += encodeText(part, encoder);
|
|
79
79
|
}
|
|
80
80
|
else if (part && typeof part === 'object') {
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
const record = part;
|
|
82
|
+
const typeValue = typeof record.type === 'string' ? record.type.toLowerCase() : '';
|
|
83
|
+
// Large binary/image payloads (data URIs, base64, etc.) should not
|
|
84
|
+
// dominate context estimation. For image-like blocks, only count a
|
|
85
|
+
// small textual placeholder instead of the full JSON/body.
|
|
86
|
+
if (typeValue.startsWith('image')) {
|
|
87
|
+
const caption = typeof record.caption === 'string' && record.caption.trim().length
|
|
88
|
+
? record.caption
|
|
89
|
+
: '[image]';
|
|
90
|
+
total += encodeText(caption, encoder);
|
|
91
|
+
}
|
|
92
|
+
else if (typeof record.text === 'string') {
|
|
93
|
+
total += encodeText(record.text, encoder);
|
|
83
94
|
}
|
|
84
95
|
else {
|
|
85
|
-
total += encodeText(JSON.stringify(
|
|
96
|
+
total += encodeText(JSON.stringify(record), encoder);
|
|
86
97
|
}
|
|
87
98
|
}
|
|
88
99
|
}
|
|
@@ -5,7 +5,20 @@ import type { StandardizedRequest } from '../../conversion/hub/types/standardize
|
|
|
5
5
|
export declare const DEFAULT_MODEL_CONTEXT_TOKENS = 200000;
|
|
6
6
|
export declare const DEFAULT_ROUTE = "default";
|
|
7
7
|
export declare const ROUTE_PRIORITY: string[];
|
|
8
|
-
export
|
|
8
|
+
export interface RoutePoolTier {
|
|
9
|
+
id: string;
|
|
10
|
+
targets: string[];
|
|
11
|
+
priority: number;
|
|
12
|
+
backup?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Optional force flag for this route pool.
|
|
15
|
+
* Currently interpreted for:
|
|
16
|
+
* - routing.vision: force dedicated vision backend handling.
|
|
17
|
+
* - routing.web_search / routing.search: force server-side web_search flow.
|
|
18
|
+
*/
|
|
19
|
+
force?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export type RoutingPools = Record<string, RoutePoolTier[]>;
|
|
9
22
|
export type StreamingPreference = 'auto' | 'always' | 'never';
|
|
10
23
|
export interface ProviderAuthConfig {
|
|
11
24
|
type: 'apiKey' | 'oauth';
|
|
@@ -36,6 +49,13 @@ export interface ProviderProfile {
|
|
|
36
49
|
responsesConfig?: ResponsesProviderConfig;
|
|
37
50
|
streaming?: StreamingPreference;
|
|
38
51
|
maxContextTokens?: number;
|
|
52
|
+
/**
|
|
53
|
+
* When true, this provider must be skipped for any request that
|
|
54
|
+
* requires server-side tool orchestration (e.g. web_search).
|
|
55
|
+
* Normal chat routing (without servertool injection) may still
|
|
56
|
+
* use this provider as usual.
|
|
57
|
+
*/
|
|
58
|
+
serverToolsDisabled?: boolean;
|
|
39
59
|
}
|
|
40
60
|
export interface ProviderRuntimeProfile {
|
|
41
61
|
runtimeKey: string;
|
|
@@ -55,6 +75,12 @@ export interface ProviderRuntimeProfile {
|
|
|
55
75
|
modelContextTokens?: Record<string, number>;
|
|
56
76
|
defaultContextTokens?: number;
|
|
57
77
|
maxContextTokens?: number;
|
|
78
|
+
/**
|
|
79
|
+
* Provider-level flag propagated from virtualrouter.providers[*].
|
|
80
|
+
* When true, VirtualRouterEngine will skip this runtime for any
|
|
81
|
+
* request that declares serverToolRequired=true in routing metadata.
|
|
82
|
+
*/
|
|
83
|
+
serverToolsDisabled?: boolean;
|
|
58
84
|
}
|
|
59
85
|
export interface VirtualRouterClassifierConfig {
|
|
60
86
|
longContextThresholdTokens?: number;
|
|
@@ -72,6 +98,27 @@ export interface ProviderHealthConfig {
|
|
|
72
98
|
cooldownMs: number;
|
|
73
99
|
fatalCooldownMs?: number;
|
|
74
100
|
}
|
|
101
|
+
export interface VirtualRouterWebSearchEngineConfig {
|
|
102
|
+
id: string;
|
|
103
|
+
providerKey: string;
|
|
104
|
+
description?: string;
|
|
105
|
+
default?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* When true, this engine will never be used by server-side tools
|
|
108
|
+
* (e.g. web_search). It will also be omitted from injected tool
|
|
109
|
+
* schemas so main models cannot select it for servertool flows.
|
|
110
|
+
*/
|
|
111
|
+
serverToolsDisabled?: boolean;
|
|
112
|
+
}
|
|
113
|
+
export interface VirtualRouterWebSearchConfig {
|
|
114
|
+
engines: VirtualRouterWebSearchEngineConfig[];
|
|
115
|
+
injectPolicy?: 'always' | 'selective';
|
|
116
|
+
/**
|
|
117
|
+
* When true, always prefer server-side web_search orchestration
|
|
118
|
+
* over upstream builtin behaviours (e.g. OpenAI Responses builtin web_search).
|
|
119
|
+
*/
|
|
120
|
+
force?: boolean;
|
|
121
|
+
}
|
|
75
122
|
export interface VirtualRouterConfig {
|
|
76
123
|
routing: RoutingPools;
|
|
77
124
|
providers: Record<string, ProviderProfile>;
|
|
@@ -79,11 +126,11 @@ export interface VirtualRouterConfig {
|
|
|
79
126
|
loadBalancing?: LoadBalancingPolicy;
|
|
80
127
|
health?: ProviderHealthConfig;
|
|
81
128
|
contextRouting?: VirtualRouterContextRoutingConfig;
|
|
129
|
+
webSearch?: VirtualRouterWebSearchConfig;
|
|
82
130
|
}
|
|
83
131
|
export interface VirtualRouterContextRoutingConfig {
|
|
84
132
|
warnRatio: number;
|
|
85
133
|
hardLimit?: boolean;
|
|
86
|
-
fallbackRoute?: string;
|
|
87
134
|
}
|
|
88
135
|
export type VirtualRouterProviderDefinition = Record<string, unknown>;
|
|
89
136
|
export interface VirtualRouterBootstrapInput extends Record<string, unknown> {
|
|
@@ -94,6 +141,7 @@ export interface VirtualRouterBootstrapInput extends Record<string, unknown> {
|
|
|
94
141
|
loadBalancing?: LoadBalancingPolicy;
|
|
95
142
|
health?: ProviderHealthConfig;
|
|
96
143
|
contextRouting?: VirtualRouterContextRoutingConfig;
|
|
144
|
+
webSearch?: VirtualRouterWebSearchConfig | Record<string, unknown>;
|
|
97
145
|
}
|
|
98
146
|
export type ProviderRuntimeMap = Record<string, ProviderRuntimeProfile>;
|
|
99
147
|
export interface VirtualRouterBootstrapResult {
|
|
@@ -112,6 +160,13 @@ export interface RouterMetadataInput {
|
|
|
112
160
|
providerProtocol?: string;
|
|
113
161
|
stage?: 'inbound' | 'outbound' | 'response';
|
|
114
162
|
routeHint?: string;
|
|
163
|
+
/**
|
|
164
|
+
* Indicates that current routing decision is for a request which
|
|
165
|
+
* expects server-side tools orchestration (e.g. web_search).
|
|
166
|
+
* Virtual Router should skip providers that opt out via
|
|
167
|
+
* serverToolsDisabled when this flag is true.
|
|
168
|
+
*/
|
|
169
|
+
serverToolRequired?: boolean;
|
|
115
170
|
responsesResume?: {
|
|
116
171
|
previousRequestId?: string;
|
|
117
172
|
restoredFromResponseId?: string;
|
|
@@ -152,6 +207,7 @@ export interface RoutingDecision {
|
|
|
152
207
|
reasoning: string;
|
|
153
208
|
fallback: boolean;
|
|
154
209
|
pool: string[];
|
|
210
|
+
poolId?: string;
|
|
155
211
|
}
|
|
156
212
|
export interface TargetMetadata {
|
|
157
213
|
providerKey: string;
|
|
@@ -164,6 +220,13 @@ export interface TargetMetadata {
|
|
|
164
220
|
responsesConfig?: ResponsesProviderConfig;
|
|
165
221
|
streaming?: StreamingPreference;
|
|
166
222
|
maxContextTokens?: number;
|
|
223
|
+
/**
|
|
224
|
+
* Route-level flags propagated from the virtual router.
|
|
225
|
+
* These are derived from routing pools and webSearch config and
|
|
226
|
+
* are used by hub pipeline/process layers (web_search / vision).
|
|
227
|
+
*/
|
|
228
|
+
forceWebSearch?: boolean;
|
|
229
|
+
forceVision?: boolean;
|
|
167
230
|
}
|
|
168
231
|
export interface ResponsesProviderConfig {
|
|
169
232
|
toolCallIdStyle?: 'fc' | 'preserve';
|
|
@@ -185,6 +248,7 @@ export interface RoutingDiagnostics {
|
|
|
185
248
|
reasoning: string;
|
|
186
249
|
fallback: boolean;
|
|
187
250
|
pool: string[];
|
|
251
|
+
poolId?: string;
|
|
188
252
|
confidence: number;
|
|
189
253
|
}
|
|
190
254
|
export interface RoutingStatusSnapshot {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AdapterContext } from '../conversion/hub/types/chat-envelope.js';
|
|
2
|
+
import type { JsonObject } from '../conversion/hub/types/json.js';
|
|
3
|
+
import type { ProviderInvoker } from './types.js';
|
|
4
|
+
export interface ServerToolOrchestrationOptions {
|
|
5
|
+
chat: JsonObject;
|
|
6
|
+
adapterContext: AdapterContext;
|
|
7
|
+
requestId: string;
|
|
8
|
+
entryEndpoint: string;
|
|
9
|
+
providerProtocol: string;
|
|
10
|
+
reenterPipeline?: (options: {
|
|
11
|
+
entryEndpoint: string;
|
|
12
|
+
requestId: string;
|
|
13
|
+
body: JsonObject;
|
|
14
|
+
metadata?: JsonObject;
|
|
15
|
+
}) => Promise<{
|
|
16
|
+
body?: JsonObject;
|
|
17
|
+
__sse_responses?: unknown;
|
|
18
|
+
format?: string;
|
|
19
|
+
}>;
|
|
20
|
+
providerInvoker?: ProviderInvoker;
|
|
21
|
+
}
|
|
22
|
+
export interface ServerToolOrchestrationResult {
|
|
23
|
+
chat: JsonObject;
|
|
24
|
+
executed: boolean;
|
|
25
|
+
flowId?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function runServerToolOrchestration(options: ServerToolOrchestrationOptions): Promise<ServerToolOrchestrationResult>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { runServerSideToolEngine } from './server-side-tools.js';
|
|
2
|
+
export async function runServerToolOrchestration(options) {
|
|
3
|
+
const engineOptions = {
|
|
4
|
+
chatResponse: options.chat,
|
|
5
|
+
adapterContext: options.adapterContext,
|
|
6
|
+
entryEndpoint: options.entryEndpoint,
|
|
7
|
+
requestId: options.requestId,
|
|
8
|
+
providerProtocol: options.providerProtocol,
|
|
9
|
+
providerInvoker: options.providerInvoker,
|
|
10
|
+
reenterPipeline: options.reenterPipeline
|
|
11
|
+
};
|
|
12
|
+
const engineResult = await runServerSideToolEngine(engineOptions);
|
|
13
|
+
if (engineResult.mode === 'passthrough' || !engineResult.execution) {
|
|
14
|
+
return {
|
|
15
|
+
chat: engineResult.finalChatResponse,
|
|
16
|
+
executed: false
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (!engineResult.execution.followup || !options.reenterPipeline) {
|
|
20
|
+
return {
|
|
21
|
+
chat: engineResult.finalChatResponse,
|
|
22
|
+
executed: true,
|
|
23
|
+
flowId: engineResult.execution.flowId
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const routeHint = resolveRouteHint(options.adapterContext, engineResult.execution.flowId);
|
|
27
|
+
const metadata = {
|
|
28
|
+
serverToolFollowup: true,
|
|
29
|
+
stream: false,
|
|
30
|
+
...(engineResult.execution.followup.metadata ?? {})
|
|
31
|
+
};
|
|
32
|
+
if (routeHint && typeof metadata.routeHint !== 'string') {
|
|
33
|
+
metadata.routeHint = routeHint;
|
|
34
|
+
}
|
|
35
|
+
const followup = await options.reenterPipeline({
|
|
36
|
+
entryEndpoint: '/v1/chat/completions',
|
|
37
|
+
requestId: `${options.requestId}${engineResult.execution.followup.requestIdSuffix}`,
|
|
38
|
+
body: engineResult.execution.followup.payload,
|
|
39
|
+
metadata
|
|
40
|
+
});
|
|
41
|
+
const followupBody = followup.body && typeof followup.body === 'object'
|
|
42
|
+
? followup.body
|
|
43
|
+
: engineResult.finalChatResponse;
|
|
44
|
+
return {
|
|
45
|
+
chat: followupBody,
|
|
46
|
+
executed: true,
|
|
47
|
+
flowId: engineResult.execution.flowId
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function resolveRouteHint(adapterContext, flowId) {
|
|
51
|
+
const rawRoute = adapterContext.routeId;
|
|
52
|
+
const routeId = typeof rawRoute === 'string' && rawRoute.trim() ? rawRoute.trim() : '';
|
|
53
|
+
if (!routeId) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
if (flowId && routeId.toLowerCase() === flowId.toLowerCase()) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
return routeId;
|
|
60
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { JsonObject } from '../conversion/hub/types/json.js';
|
|
2
|
+
import type { ServerToolOrchestrationOptions, ServerToolOrchestrationResult } from './orchestration-types.js';
|
|
3
|
+
export interface ServerToolFlowContext {
|
|
4
|
+
options: ServerToolOrchestrationOptions;
|
|
5
|
+
baseChatResponse: JsonObject;
|
|
6
|
+
capturedChatRequest?: JsonObject;
|
|
7
|
+
routeId?: string;
|
|
8
|
+
cache: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export interface ServerToolHopResult {
|
|
11
|
+
body?: JsonObject;
|
|
12
|
+
__sse_responses?: unknown;
|
|
13
|
+
format?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ServerToolReenterCallOptions {
|
|
16
|
+
requestIdSuffix: string;
|
|
17
|
+
body: JsonObject;
|
|
18
|
+
entryEndpoint?: string;
|
|
19
|
+
metadata?: JsonObject;
|
|
20
|
+
}
|
|
21
|
+
export interface ServerToolProviderCallOptions {
|
|
22
|
+
requestIdSuffix: string;
|
|
23
|
+
providerKey: string;
|
|
24
|
+
providerProtocol: string;
|
|
25
|
+
entryEndpoint: string;
|
|
26
|
+
payload: JsonObject;
|
|
27
|
+
modelId?: string;
|
|
28
|
+
routeHint?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ServerToolFlowHelpers {
|
|
31
|
+
makeRequestId: (suffix: string) => string;
|
|
32
|
+
callReenterHop: (options: ServerToolReenterCallOptions) => Promise<ServerToolHopResult | null>;
|
|
33
|
+
callProviderHop: (options: ServerToolProviderCallOptions) => Promise<ServerToolHopResult | null>;
|
|
34
|
+
getRouteHintForFollowup: (exclude?: string) => string | undefined;
|
|
35
|
+
}
|
|
36
|
+
export interface ServerToolFlow {
|
|
37
|
+
id: string;
|
|
38
|
+
shouldRun: (context: ServerToolFlowContext) => Promise<boolean> | boolean;
|
|
39
|
+
run: (context: ServerToolFlowContext, helpers: ServerToolFlowHelpers) => Promise<ServerToolOrchestrationResult | null>;
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { registerServerToolHandler } from '../registry.js';
|
|
2
|
+
import { cloneJson, extractTextFromChatLike } from '../server-side-tools.js';
|
|
3
|
+
const FLOW_ID = 'vision_flow';
|
|
4
|
+
const handler = async (ctx) => {
|
|
5
|
+
if (!ctx.options.reenterPipeline) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
if (!shouldRunVisionFlow(ctx)) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const captured = getCapturedRequest(ctx.adapterContext);
|
|
12
|
+
if (!captured) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const analysisPayload = buildVisionAnalysisPayload(captured);
|
|
16
|
+
if (!analysisPayload) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const visionResponse = await ctx.options.reenterPipeline({
|
|
20
|
+
entryEndpoint: '/v1/chat/completions',
|
|
21
|
+
requestId: `${ctx.options.requestId}:vision`,
|
|
22
|
+
body: analysisPayload,
|
|
23
|
+
metadata: {
|
|
24
|
+
routeHint: 'vision',
|
|
25
|
+
serverToolFollowup: true,
|
|
26
|
+
stream: false
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const visionBody = visionResponse.body && typeof visionResponse.body === 'object'
|
|
30
|
+
? visionResponse.body
|
|
31
|
+
: null;
|
|
32
|
+
if (!visionBody) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const visionSummary = extractTextFromChatLike(visionBody);
|
|
36
|
+
if (!visionSummary) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const followupPayload = buildVisionFollowupPayload(captured, visionSummary);
|
|
40
|
+
if (!followupPayload) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const execution = {
|
|
44
|
+
flowId: FLOW_ID,
|
|
45
|
+
followup: {
|
|
46
|
+
requestIdSuffix: ':vision_followup',
|
|
47
|
+
payload: followupPayload,
|
|
48
|
+
metadata: buildFollowupMetadata(ctx.adapterContext, 'vision')
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
chatResponse: ctx.base,
|
|
53
|
+
execution
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
registerServerToolHandler('vision_auto', handler, { trigger: 'auto' });
|
|
57
|
+
function shouldRunVisionFlow(ctx) {
|
|
58
|
+
const record = ctx.adapterContext;
|
|
59
|
+
const followupFlag = record.serverToolFollowup === true || record.serverToolFollowup === 'true';
|
|
60
|
+
if (followupFlag) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return record.hasImageAttachment === true || record.hasImageAttachment === 'true';
|
|
64
|
+
}
|
|
65
|
+
function getCapturedRequest(adapterContext) {
|
|
66
|
+
if (!adapterContext || typeof adapterContext !== 'object') {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const captured = adapterContext.capturedChatRequest;
|
|
70
|
+
if (!captured || typeof captured !== 'object' || Array.isArray(captured)) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return captured;
|
|
74
|
+
}
|
|
75
|
+
function buildVisionAnalysisPayload(source) {
|
|
76
|
+
if (!source || typeof source !== 'object') {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const payload = {};
|
|
80
|
+
if (typeof source.model === 'string' && source.model.trim()) {
|
|
81
|
+
payload.model = source.model.trim();
|
|
82
|
+
}
|
|
83
|
+
if (Array.isArray(source.messages)) {
|
|
84
|
+
payload.messages = cloneJson(source.messages);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
if (Array.isArray(source.tools) && source.tools.length) {
|
|
90
|
+
payload.tools = cloneJson(source.tools);
|
|
91
|
+
}
|
|
92
|
+
const parameters = source.parameters;
|
|
93
|
+
if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
|
|
94
|
+
const params = cloneJson(parameters);
|
|
95
|
+
Object.assign(payload, params);
|
|
96
|
+
}
|
|
97
|
+
return payload;
|
|
98
|
+
}
|
|
99
|
+
function buildVisionFollowupPayload(source, summary) {
|
|
100
|
+
if (!source || typeof source !== 'object') {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const payload = {};
|
|
104
|
+
if (typeof source.model === 'string' && source.model.trim()) {
|
|
105
|
+
payload.model = source.model.trim();
|
|
106
|
+
}
|
|
107
|
+
payload.messages = injectVisionSummary(source.messages, summary);
|
|
108
|
+
if (Array.isArray(source.tools) && source.tools.length) {
|
|
109
|
+
payload.tools = cloneJson(source.tools);
|
|
110
|
+
}
|
|
111
|
+
const parameters = source.parameters;
|
|
112
|
+
if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
|
|
113
|
+
const params = cloneJson(parameters);
|
|
114
|
+
Object.assign(payload, params);
|
|
115
|
+
}
|
|
116
|
+
return payload;
|
|
117
|
+
}
|
|
118
|
+
function injectVisionSummary(source, summary) {
|
|
119
|
+
const messages = Array.isArray(source) ? cloneJson(source) : [];
|
|
120
|
+
let injected = false;
|
|
121
|
+
for (const message of messages) {
|
|
122
|
+
if (!message || typeof message !== 'object')
|
|
123
|
+
continue;
|
|
124
|
+
const content = message.content;
|
|
125
|
+
if (!Array.isArray(content))
|
|
126
|
+
continue;
|
|
127
|
+
const nextParts = [];
|
|
128
|
+
let removed = false;
|
|
129
|
+
for (const part of content) {
|
|
130
|
+
if (part && typeof part === 'object') {
|
|
131
|
+
const typeValue = typeof part.type === 'string'
|
|
132
|
+
? part.type.toLowerCase()
|
|
133
|
+
: '';
|
|
134
|
+
if (typeValue.includes('image')) {
|
|
135
|
+
removed = true;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
nextParts.push(part);
|
|
140
|
+
}
|
|
141
|
+
if (removed) {
|
|
142
|
+
nextParts.push({
|
|
143
|
+
type: 'text',
|
|
144
|
+
text: `[Vision] ${summary}`
|
|
145
|
+
});
|
|
146
|
+
message.content = nextParts;
|
|
147
|
+
injected = true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (!injected) {
|
|
151
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
152
|
+
const msg = messages[i];
|
|
153
|
+
if (!msg || typeof msg !== 'object')
|
|
154
|
+
continue;
|
|
155
|
+
const role = typeof msg.role === 'string'
|
|
156
|
+
? msg.role.toLowerCase()
|
|
157
|
+
: '';
|
|
158
|
+
if (role !== 'user')
|
|
159
|
+
continue;
|
|
160
|
+
const content = msg.content;
|
|
161
|
+
if (Array.isArray(content)) {
|
|
162
|
+
content.push({
|
|
163
|
+
type: 'text',
|
|
164
|
+
text: `[Vision] ${summary}`
|
|
165
|
+
});
|
|
166
|
+
injected = true;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
if (typeof content === 'string' && content.length) {
|
|
170
|
+
msg.content = `${content}\n[Vision] ${summary}`;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
msg.content = `[Vision] ${summary}`;
|
|
174
|
+
}
|
|
175
|
+
injected = true;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (!injected) {
|
|
180
|
+
messages.push({
|
|
181
|
+
role: 'system',
|
|
182
|
+
content: `[Vision] ${summary}`
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return messages;
|
|
186
|
+
}
|
|
187
|
+
function buildFollowupMetadata(adapterContext, toolName) {
|
|
188
|
+
const ctx = adapterContext && typeof adapterContext === 'object' ? adapterContext : null;
|
|
189
|
+
const routeId = ctx && typeof ctx.routeId === 'string' && ctx.routeId.trim() ? ctx.routeId.trim() : '';
|
|
190
|
+
if (!routeId || routeId.toLowerCase() === toolName.toLowerCase()) {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
return { routeHint: routeId };
|
|
194
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|