@jsonstudio/llms 0.6.1892 → 0.6.2172
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/compat/actions/deepseek-web-request.js +16 -2
- package/dist/conversion/compat/actions/deepseek-web-response.d.ts +7 -1
- package/dist/conversion/compat/actions/deepseek-web-response.js +302 -40
- package/dist/conversion/compat/actions/harvest-tool-calls-from-text.d.ts +5 -0
- package/dist/conversion/compat/actions/harvest-tool-calls-from-text.js +7 -4
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +1 -0
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +12 -0
- package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.js +1 -1
- package/dist/conversion/compat/actions/tool-text-request-guidance.d.ts +9 -0
- package/dist/conversion/compat/actions/tool-text-request-guidance.js +177 -0
- package/dist/conversion/compat/antigravity-session-signature.d.ts +6 -0
- package/dist/conversion/compat/antigravity-session-signature.js +15 -0
- package/dist/conversion/compat/profiles/chat-deepseek-web.json +52 -1
- package/dist/conversion/compat/profiles/chat-glm.json +22 -0
- package/dist/conversion/compat/profiles/chat-iflow.json +4 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +13 -27
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +10 -1
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +13 -4
- package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +1 -53
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +8 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +8 -4
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +191 -9
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +118 -15
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +65 -2
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage3_servertool_orchestration/index.d.ts +34 -0
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage3_servertool_orchestration/index.js +75 -0
- package/dist/conversion/hub/process/chat-process.js +85 -18
- package/dist/conversion/hub/response/provider-response.js +21 -50
- package/dist/conversion/hub/response/response-runtime.js +71 -10
- package/dist/conversion/responses/responses-openai-bridge/response-payload.d.ts +3 -0
- package/dist/conversion/responses/responses-openai-bridge/response-payload.js +576 -0
- package/dist/conversion/responses/responses-openai-bridge/types.d.ts +42 -0
- package/dist/conversion/responses/responses-openai-bridge/types.js +1 -0
- package/dist/conversion/responses/responses-openai-bridge.d.ts +3 -44
- package/dist/conversion/responses/responses-openai-bridge.js +193 -504
- package/dist/conversion/shared/anthropic-message-utils.js +82 -2
- package/dist/conversion/shared/bridge-message-utils.js +92 -39
- package/dist/conversion/shared/snapshot-hooks.js +8 -13
- package/dist/conversion/shared/text-markup-normalizer/extractors-apply-patch.d.ts +2 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-apply-patch.js +129 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-json.d.ts +4 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-json.js +637 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-shared.d.ts +21 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-shared.js +177 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-transcript.d.ts +5 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-transcript.js +385 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-xml.d.ts +10 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-xml.js +602 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors.d.ts +5 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors.js +4 -0
- package/dist/conversion/shared/text-markup-normalizer/normalize.d.ts +2 -0
- package/dist/conversion/shared/text-markup-normalizer/normalize.js +76 -0
- package/dist/conversion/shared/text-markup-normalizer.d.ts +3 -25
- package/dist/conversion/shared/text-markup-normalizer.js +2 -1386
- package/dist/conversion/shared/tool-governor.js +136 -10
- package/dist/filters/utils/snapshot-writer.js +3 -3
- package/dist/router/virtual-router/bootstrap/auth-utils.d.ts +6 -0
- package/dist/router/virtual-router/bootstrap/auth-utils.js +288 -0
- package/dist/router/virtual-router/bootstrap/claude-code-helpers.d.ts +11 -0
- package/dist/router/virtual-router/bootstrap/claude-code-helpers.js +18 -0
- package/dist/router/virtual-router/bootstrap/config-defaults.d.ts +5 -0
- package/dist/router/virtual-router/bootstrap/config-defaults.js +13 -0
- package/dist/router/virtual-router/bootstrap/config-normalizers.d.ts +4 -0
- package/dist/router/virtual-router/bootstrap/config-normalizers.js +106 -0
- package/dist/router/virtual-router/bootstrap/profile-builder.d.ts +7 -0
- package/dist/router/virtual-router/bootstrap/profile-builder.js +68 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.d.ts +40 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.js +212 -0
- package/dist/router/virtual-router/bootstrap/responses-helpers.d.ts +15 -0
- package/dist/router/virtual-router/bootstrap/responses-helpers.js +65 -0
- package/dist/router/virtual-router/bootstrap/routing-config.d.ts +23 -0
- package/dist/router/virtual-router/bootstrap/routing-config.js +293 -0
- package/dist/router/virtual-router/bootstrap/streaming-helpers.d.ts +12 -0
- package/dist/router/virtual-router/bootstrap/streaming-helpers.js +128 -0
- package/dist/router/virtual-router/bootstrap/utils.d.ts +5 -0
- package/dist/router/virtual-router/bootstrap/utils.js +41 -0
- package/dist/router/virtual-router/bootstrap/web-search-config.d.ts +4 -0
- package/dist/router/virtual-router/bootstrap/web-search-config.js +131 -0
- package/dist/router/virtual-router/bootstrap.d.ts +0 -4
- package/dist/router/virtual-router/bootstrap.js +31 -1275
- package/dist/router/virtual-router/classifier.js +32 -14
- package/dist/router/virtual-router/engine/antigravity/alias-lease.js +2 -2
- package/dist/router/virtual-router/engine/cooldown-manager.d.ts +34 -0
- package/dist/router/virtual-router/engine/cooldown-manager.js +118 -0
- package/dist/router/virtual-router/engine/route-analytics.d.ts +28 -0
- package/dist/router/virtual-router/engine/route-analytics.js +44 -0
- package/dist/router/virtual-router/engine/routing-pools/index.js +165 -4
- package/dist/router/virtual-router/engine/sticky-session-manager.d.ts +29 -0
- package/dist/router/virtual-router/engine/sticky-session-manager.js +55 -0
- package/dist/router/virtual-router/engine-logging.d.ts +42 -1
- package/dist/router/virtual-router/engine-logging.js +82 -15
- package/dist/router/virtual-router/engine-selection/multimodal-capability.d.ts +3 -0
- package/dist/router/virtual-router/engine-selection/multimodal-capability.js +26 -0
- package/dist/router/virtual-router/engine-selection/route-utils.js +6 -2
- package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/tier-selection.js +31 -1
- package/dist/router/virtual-router/engine.d.ts +21 -7
- package/dist/router/virtual-router/engine.js +198 -194
- package/dist/router/virtual-router/features.js +12 -4
- package/dist/router/virtual-router/message-utils.d.ts +8 -0
- package/dist/router/virtual-router/message-utils.js +170 -45
- package/dist/router/virtual-router/pre-command-file-resolver.js +40 -2
- package/dist/router/virtual-router/routing-instructions.d.ts +8 -0
- package/dist/router/virtual-router/routing-instructions.js +18 -2
- package/dist/router/virtual-router/routing-stop-message-actions.js +34 -10
- package/dist/router/virtual-router/routing-stop-message-state-codec.d.ts +2 -0
- package/dist/router/virtual-router/routing-stop-message-state-codec.js +50 -1
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +1 -1
- package/dist/router/virtual-router/stop-message-state-sync.js +3 -0
- package/dist/router/virtual-router/token-counter.js +51 -10
- package/dist/router/virtual-router/tool-signals.js +4 -0
- package/dist/router/virtual-router/types.d.ts +15 -0
- package/dist/servertool/clock/session-scope.d.ts +3 -0
- package/dist/servertool/clock/session-scope.js +52 -0
- package/dist/servertool/clock/state.js +9 -0
- package/dist/servertool/clock/tasks.js +12 -1
- package/dist/servertool/clock/types.d.ts +3 -0
- package/dist/servertool/engine.js +177 -31
- package/dist/servertool/handlers/clock-auto.js +2 -8
- package/dist/servertool/handlers/clock.js +6 -9
- package/dist/servertool/handlers/recursive-detection-guard.js +53 -14
- package/dist/servertool/handlers/stop-message-auto/blocked-report.d.ts +16 -0
- package/dist/servertool/handlers/stop-message-auto/blocked-report.js +349 -0
- package/dist/servertool/handlers/stop-message-auto/iflow-followup.d.ts +23 -0
- package/dist/servertool/handlers/stop-message-auto/iflow-followup.js +503 -0
- package/dist/servertool/handlers/stop-message-auto/routing-state.d.ts +38 -0
- package/dist/servertool/handlers/stop-message-auto/routing-state.js +149 -0
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +67 -0
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +387 -0
- package/dist/servertool/handlers/stop-message-auto.d.ts +1 -1
- package/dist/servertool/handlers/stop-message-auto.js +80 -556
- package/dist/servertool/handlers/stop-message-stage-policy/bd-runtime.d.ts +18 -0
- package/dist/servertool/handlers/stop-message-stage-policy/bd-runtime.js +398 -0
- package/dist/servertool/handlers/stop-message-stage-policy/decision.d.ts +9 -0
- package/dist/servertool/handlers/stop-message-stage-policy/decision.js +127 -0
- package/dist/servertool/handlers/stop-message-stage-policy/observation.d.ts +2 -0
- package/dist/servertool/handlers/stop-message-stage-policy/observation.js +179 -0
- package/dist/servertool/handlers/stop-message-stage-policy/templates.d.ts +4 -0
- package/dist/servertool/handlers/stop-message-stage-policy/templates.js +96 -0
- package/dist/servertool/handlers/stop-message-stage-policy/text-utils.d.ts +9 -0
- package/dist/servertool/handlers/stop-message-stage-policy/text-utils.js +89 -0
- package/dist/servertool/handlers/stop-message-stage-policy/types.d.ts +59 -0
- package/dist/servertool/handlers/stop-message-stage-policy/types.js +1 -0
- package/dist/servertool/handlers/stop-message-stage-policy.d.ts +3 -43
- package/dist/servertool/handlers/stop-message-stage-policy.js +2 -684
- package/dist/servertool/handlers/web-search.js +117 -0
- package/dist/servertool/server-side-tools.d.ts +0 -1
- package/dist/servertool/server-side-tools.js +4 -3
- package/dist/sse/sse-to-json/builders/response-builder.js +16 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +1 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +110 -37
- package/dist/telemetry/stats-center.d.ts +9 -0
- package/dist/telemetry/stats-center.js +29 -1
- package/dist/tools/apply-patch/structured/coercion.js +3 -11
- package/dist/tools/exec-command/validator.d.ts +1 -0
- package/dist/tools/exec-command/validator.js +132 -0
- package/dist/tools/tool-registry.d.ts +1 -0
- package/dist/tools/tool-registry.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { VirtualRouterError, VirtualRouterErrorCode } from '../types.js';
|
|
2
|
+
import { readOptionalString } from './utils.js';
|
|
3
|
+
export function normalizeRouting(source) {
|
|
4
|
+
const routing = {};
|
|
5
|
+
for (const [routeName, entries] of Object.entries(source)) {
|
|
6
|
+
if (!Array.isArray(entries) || !entries.length) {
|
|
7
|
+
routing[routeName] = [];
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
const allStrings = entries.every((entry) => typeof entry === 'string' || entry === null || entry === undefined);
|
|
11
|
+
if (allStrings) {
|
|
12
|
+
const targets = normalizeTargetList(entries);
|
|
13
|
+
routing[routeName] = targets.length ? [buildLegacyRoutePool(routeName, targets)] : [];
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const normalized = [];
|
|
17
|
+
const total = entries.length || 1;
|
|
18
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
19
|
+
const entry = entries[index];
|
|
20
|
+
const pool = normalizeRoutePoolEntry(routeName, entry, index, total);
|
|
21
|
+
if (pool && pool.targets.length) {
|
|
22
|
+
normalized.push(pool);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
routing[routeName] = normalized;
|
|
26
|
+
}
|
|
27
|
+
return routing;
|
|
28
|
+
}
|
|
29
|
+
export function expandRoutingTable(routingSource, aliasIndex, modelIndex) {
|
|
30
|
+
const routing = {};
|
|
31
|
+
const targetKeys = new Set();
|
|
32
|
+
for (const [routeName, pools] of Object.entries(routingSource)) {
|
|
33
|
+
const expandedPools = [];
|
|
34
|
+
for (const pool of pools) {
|
|
35
|
+
const expandedTargets = [];
|
|
36
|
+
let orderCounter = 0;
|
|
37
|
+
for (const entry of pool.targets) {
|
|
38
|
+
const parsed = parseRouteEntry(entry, aliasIndex);
|
|
39
|
+
if (!parsed) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (!aliasIndex.has(parsed.providerId)) {
|
|
43
|
+
throw new VirtualRouterError(`Route "${routeName}" references unknown provider "${parsed.providerId}"`, VirtualRouterErrorCode.CONFIG_ERROR);
|
|
44
|
+
}
|
|
45
|
+
const modelInfo = modelIndex.get(parsed.providerId);
|
|
46
|
+
if (modelInfo?.declared) {
|
|
47
|
+
if (!parsed.modelId) {
|
|
48
|
+
throw new VirtualRouterError(`Route "${routeName}" references empty model id for provider "${parsed.providerId}"`, VirtualRouterErrorCode.CONFIG_ERROR);
|
|
49
|
+
}
|
|
50
|
+
const knownModels = modelInfo.models ?? [];
|
|
51
|
+
if (!knownModels.length) {
|
|
52
|
+
throw new VirtualRouterError(`Route "${routeName}" references provider "${parsed.providerId}" but provider declares no models`, VirtualRouterErrorCode.CONFIG_ERROR);
|
|
53
|
+
}
|
|
54
|
+
if (!knownModels.includes(parsed.modelId)) {
|
|
55
|
+
throw new VirtualRouterError(`Route "${routeName}" references unknown model "${parsed.modelId}" for provider "${parsed.providerId}"`, VirtualRouterErrorCode.CONFIG_ERROR);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const aliases = parsed.keyAlias ? [parsed.keyAlias] : aliasIndex.get(parsed.providerId);
|
|
59
|
+
if (!aliases.length) {
|
|
60
|
+
throw new VirtualRouterError(`Provider ${parsed.providerId} has no auth aliases but is referenced in routing`, VirtualRouterErrorCode.CONFIG_ERROR);
|
|
61
|
+
}
|
|
62
|
+
for (const alias of aliases) {
|
|
63
|
+
const runtimeKey = buildRuntimeKey(parsed.providerId, alias);
|
|
64
|
+
const targetKey = `${runtimeKey}.${parsed.modelId}`;
|
|
65
|
+
const existing = expandedTargets.find((candidate) => candidate.key === targetKey);
|
|
66
|
+
if (existing) {
|
|
67
|
+
if (parsed.priority > existing.priority) {
|
|
68
|
+
existing.priority = parsed.priority;
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
expandedTargets.push({ key: targetKey, priority: parsed.priority, order: orderCounter });
|
|
73
|
+
orderCounter += 1;
|
|
74
|
+
targetKeys.add(targetKey);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (expandedTargets.length) {
|
|
78
|
+
const sortedTargets = pool.mode === 'priority'
|
|
79
|
+
? [...expandedTargets]
|
|
80
|
+
.sort((a, b) => {
|
|
81
|
+
if (a.priority !== b.priority) {
|
|
82
|
+
return b.priority - a.priority;
|
|
83
|
+
}
|
|
84
|
+
return a.order - b.order;
|
|
85
|
+
})
|
|
86
|
+
.map((candidate) => candidate.key)
|
|
87
|
+
: expandedTargets.map((candidate) => candidate.key);
|
|
88
|
+
expandedPools.push({
|
|
89
|
+
id: pool.id,
|
|
90
|
+
priority: pool.priority,
|
|
91
|
+
backup: pool.backup,
|
|
92
|
+
targets: sortedTargets,
|
|
93
|
+
...(pool.mode ? { mode: pool.mode } : {}),
|
|
94
|
+
...(pool.force ? { force: true } : {})
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
routing[routeName] = expandedPools;
|
|
99
|
+
}
|
|
100
|
+
return { routing, targetKeys };
|
|
101
|
+
}
|
|
102
|
+
function buildLegacyRoutePool(routeName, targets) {
|
|
103
|
+
return {
|
|
104
|
+
id: `${routeName}:pool0`,
|
|
105
|
+
priority: targets.length,
|
|
106
|
+
backup: false,
|
|
107
|
+
targets
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function normalizeRoutePoolEntry(routeName, entry, index, total) {
|
|
111
|
+
if (typeof entry === 'string') {
|
|
112
|
+
const targets = normalizeTargetList(entry);
|
|
113
|
+
return targets.length
|
|
114
|
+
? {
|
|
115
|
+
id: `${routeName}:pool${index + 1}`,
|
|
116
|
+
priority: total - index,
|
|
117
|
+
backup: false,
|
|
118
|
+
targets
|
|
119
|
+
}
|
|
120
|
+
: null;
|
|
121
|
+
}
|
|
122
|
+
if (!entry || typeof entry !== 'object') {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const record = entry;
|
|
126
|
+
const id = readOptionalString(record.id) ??
|
|
127
|
+
readOptionalString(record.poolId) ??
|
|
128
|
+
`${routeName}:pool${index + 1}`;
|
|
129
|
+
const backup = record.backup === true ||
|
|
130
|
+
record.isBackup === true ||
|
|
131
|
+
(typeof record.type === 'string' && record.type.toLowerCase() === 'backup');
|
|
132
|
+
const priority = normalizePriorityValue(record.priority, total - index);
|
|
133
|
+
const targets = normalizeRouteTargets(record);
|
|
134
|
+
const mode = normalizeRoutePoolMode(record.mode ?? record.strategy ?? record.routingMode);
|
|
135
|
+
const force = record.force === true ||
|
|
136
|
+
(typeof record.force === 'string' && record.force.trim().toLowerCase() === 'true');
|
|
137
|
+
return targets.length
|
|
138
|
+
? {
|
|
139
|
+
id,
|
|
140
|
+
priority,
|
|
141
|
+
backup,
|
|
142
|
+
targets,
|
|
143
|
+
...(mode ? { mode } : {}),
|
|
144
|
+
...(force ? { force: true } : {})
|
|
145
|
+
}
|
|
146
|
+
: null;
|
|
147
|
+
}
|
|
148
|
+
function normalizeRoutePoolMode(value) {
|
|
149
|
+
if (typeof value !== 'string') {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
const normalized = value.trim().toLowerCase();
|
|
153
|
+
if (!normalized) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
if (normalized === 'priority') {
|
|
157
|
+
return 'priority';
|
|
158
|
+
}
|
|
159
|
+
if (normalized === 'round-robin' ||
|
|
160
|
+
normalized === 'round_robin' ||
|
|
161
|
+
normalized === 'roundrobin' ||
|
|
162
|
+
normalized === 'rr') {
|
|
163
|
+
return 'round-robin';
|
|
164
|
+
}
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
function normalizeRouteTargets(record) {
|
|
168
|
+
const buckets = [record.targets, record.providers, record.pool, record.entries, record.items, record.routes];
|
|
169
|
+
const normalized = [];
|
|
170
|
+
for (const bucket of buckets) {
|
|
171
|
+
for (const target of normalizeTargetList(bucket)) {
|
|
172
|
+
if (!normalized.includes(target)) {
|
|
173
|
+
normalized.push(target);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const singular = [record.target, record.provider];
|
|
178
|
+
for (const candidate of singular) {
|
|
179
|
+
for (const target of normalizeTargetList(candidate)) {
|
|
180
|
+
if (!normalized.includes(target)) {
|
|
181
|
+
normalized.push(target);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return normalized;
|
|
186
|
+
}
|
|
187
|
+
function normalizeTargetList(value) {
|
|
188
|
+
if (Array.isArray(value)) {
|
|
189
|
+
const normalized = [];
|
|
190
|
+
for (const entry of value) {
|
|
191
|
+
if (typeof entry === 'string') {
|
|
192
|
+
const trimmed = entry.trim();
|
|
193
|
+
if (trimmed && !normalized.includes(trimmed)) {
|
|
194
|
+
normalized.push(trimmed);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return normalized;
|
|
199
|
+
}
|
|
200
|
+
if (typeof value === 'string') {
|
|
201
|
+
const trimmed = value.trim();
|
|
202
|
+
return trimmed ? [trimmed] : [];
|
|
203
|
+
}
|
|
204
|
+
if (typeof value === 'number') {
|
|
205
|
+
const str = String(value).trim();
|
|
206
|
+
return str ? [str] : [];
|
|
207
|
+
}
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
function normalizePriorityValue(value, fallback) {
|
|
211
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
212
|
+
return value;
|
|
213
|
+
}
|
|
214
|
+
if (typeof value === 'string') {
|
|
215
|
+
const trimmed = value.trim();
|
|
216
|
+
if (trimmed) {
|
|
217
|
+
const parsed = Number(trimmed);
|
|
218
|
+
if (Number.isFinite(parsed)) {
|
|
219
|
+
return parsed;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return fallback;
|
|
224
|
+
}
|
|
225
|
+
function parseRouteEntry(entry, aliasIndex) {
|
|
226
|
+
const value = typeof entry === 'string' ? entry.trim() : '';
|
|
227
|
+
if (!value)
|
|
228
|
+
return null;
|
|
229
|
+
const firstDot = value.indexOf('.');
|
|
230
|
+
if (firstDot <= 0 || firstDot === value.length - 1)
|
|
231
|
+
return null;
|
|
232
|
+
const providerId = value.slice(0, firstDot);
|
|
233
|
+
const remainder = value.slice(firstDot + 1);
|
|
234
|
+
const aliases = aliasIndex.get(providerId);
|
|
235
|
+
if (aliases && aliases.length) {
|
|
236
|
+
const secondDot = remainder.indexOf('.');
|
|
237
|
+
if (secondDot > 0 && secondDot < remainder.length - 1) {
|
|
238
|
+
const aliasCandidate = remainder.slice(0, secondDot);
|
|
239
|
+
if (aliases.includes(aliasCandidate)) {
|
|
240
|
+
const parsed = splitModelPriority(remainder.slice(secondDot + 1));
|
|
241
|
+
return {
|
|
242
|
+
providerId,
|
|
243
|
+
keyAlias: aliasCandidate,
|
|
244
|
+
modelId: parsed.modelId,
|
|
245
|
+
priority: parsed.priority
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const parsed = splitModelPriority(remainder);
|
|
251
|
+
return { providerId, modelId: parsed.modelId, priority: parsed.priority };
|
|
252
|
+
}
|
|
253
|
+
function splitModelPriority(raw) {
|
|
254
|
+
const value = typeof raw === 'string' ? raw.trim() : '';
|
|
255
|
+
if (!value) {
|
|
256
|
+
return { modelId: value, priority: 100 };
|
|
257
|
+
}
|
|
258
|
+
const match = value.match(/^(.*):(\d+)$/);
|
|
259
|
+
if (!match) {
|
|
260
|
+
return { modelId: value, priority: 100 };
|
|
261
|
+
}
|
|
262
|
+
const modelId = (match[1] ?? '').trim();
|
|
263
|
+
const priorityRaw = (match[2] ?? '').trim();
|
|
264
|
+
const parsed = Number(priorityRaw);
|
|
265
|
+
if (!modelId) {
|
|
266
|
+
return { modelId: value, priority: 100 };
|
|
267
|
+
}
|
|
268
|
+
if (!Number.isFinite(parsed)) {
|
|
269
|
+
return { modelId, priority: 100 };
|
|
270
|
+
}
|
|
271
|
+
return { modelId, priority: parsed };
|
|
272
|
+
}
|
|
273
|
+
export function parseTargetKey(targetKey) {
|
|
274
|
+
const value = typeof targetKey === 'string' ? targetKey.trim() : '';
|
|
275
|
+
if (!value)
|
|
276
|
+
return null;
|
|
277
|
+
const firstDot = value.indexOf('.');
|
|
278
|
+
if (firstDot <= 0 || firstDot === value.length - 1)
|
|
279
|
+
return null;
|
|
280
|
+
const providerId = value.slice(0, firstDot);
|
|
281
|
+
const remainder = value.slice(firstDot + 1);
|
|
282
|
+
const secondDot = remainder.indexOf('.');
|
|
283
|
+
if (secondDot <= 0 || secondDot === remainder.length - 1)
|
|
284
|
+
return null;
|
|
285
|
+
return {
|
|
286
|
+
providerId,
|
|
287
|
+
keyAlias: remainder.slice(0, secondDot),
|
|
288
|
+
modelId: remainder.slice(secondDot + 1)
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
export function buildRuntimeKey(providerId, keyAlias) {
|
|
292
|
+
return `${providerId}.${keyAlias}`;
|
|
293
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type StreamingPreference } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Normalize model-level streaming preferences from provider config.
|
|
4
|
+
*/
|
|
5
|
+
export declare function normalizeModelStreaming(provider: Record<string, unknown>): Record<string, StreamingPreference> | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Normalize model-level context token limits.
|
|
8
|
+
*/
|
|
9
|
+
export declare function normalizeModelContextTokens(provider: Record<string, unknown>): {
|
|
10
|
+
modelContextTokens?: Record<string, number>;
|
|
11
|
+
defaultContextTokens?: number;
|
|
12
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { asRecord } from './utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* Normalize model-level streaming preferences from provider config.
|
|
4
|
+
*/
|
|
5
|
+
export function normalizeModelStreaming(provider) {
|
|
6
|
+
const modelsNode = asRecord(provider.models);
|
|
7
|
+
if (!modelsNode) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
const normalized = {};
|
|
11
|
+
for (const [modelId, modelRaw] of Object.entries(modelsNode)) {
|
|
12
|
+
if (!modelRaw || typeof modelRaw !== 'object') {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const preference = resolveStreamingPreference(modelRaw);
|
|
16
|
+
if (preference) {
|
|
17
|
+
normalized[modelId] = preference;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return Object.keys(normalized).length ? normalized : undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve streaming preference from model config.
|
|
24
|
+
*/
|
|
25
|
+
function resolveStreamingPreference(model) {
|
|
26
|
+
return (coerceStreamingPreference(model.streaming) ??
|
|
27
|
+
coerceStreamingPreference(model.stream) ??
|
|
28
|
+
coerceStreamingPreference(model.supportsStreaming));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Coerce various value types to StreamingPreference.
|
|
32
|
+
*/
|
|
33
|
+
function coerceStreamingPreference(value) {
|
|
34
|
+
if (typeof value === 'string') {
|
|
35
|
+
const normalized = value.trim().toLowerCase();
|
|
36
|
+
if (normalized === 'always' || normalized === 'auto' || normalized === 'never') {
|
|
37
|
+
return normalized;
|
|
38
|
+
}
|
|
39
|
+
if (normalized === 'true') {
|
|
40
|
+
return 'always';
|
|
41
|
+
}
|
|
42
|
+
if (normalized === 'false') {
|
|
43
|
+
return 'never';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (typeof value === 'boolean') {
|
|
47
|
+
return value ? 'always' : 'never';
|
|
48
|
+
}
|
|
49
|
+
if (value && typeof value === 'object') {
|
|
50
|
+
const record = value;
|
|
51
|
+
if (record.mode !== undefined) {
|
|
52
|
+
return coerceStreamingPreference(record.mode);
|
|
53
|
+
}
|
|
54
|
+
if (record.value !== undefined) {
|
|
55
|
+
return coerceStreamingPreference(record.value);
|
|
56
|
+
}
|
|
57
|
+
if (record.enabled !== undefined) {
|
|
58
|
+
return coerceStreamingPreference(record.enabled);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Normalize model-level context token limits.
|
|
65
|
+
*/
|
|
66
|
+
export function normalizeModelContextTokens(provider) {
|
|
67
|
+
const modelsNode = asRecord(provider.models);
|
|
68
|
+
const normalized = {};
|
|
69
|
+
for (const [modelId, modelRaw] of Object.entries(modelsNode)) {
|
|
70
|
+
if (!modelRaw || typeof modelRaw !== 'object') {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const candidate = readContextTokens(modelRaw);
|
|
74
|
+
if (candidate) {
|
|
75
|
+
normalized[modelId] = candidate;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const configNode = asRecord(provider.config);
|
|
79
|
+
const defaultsNode = asRecord(configNode?.userConfigDefaults);
|
|
80
|
+
const defaultCandidate = readContextTokens(provider) ?? readContextTokens(configNode) ?? readContextTokens(defaultsNode);
|
|
81
|
+
return {
|
|
82
|
+
modelContextTokens: Object.keys(normalized).length ? normalized : undefined,
|
|
83
|
+
defaultContextTokens: defaultCandidate
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Read context tokens from various field names.
|
|
88
|
+
*/
|
|
89
|
+
function readContextTokens(record) {
|
|
90
|
+
if (!record) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const keys = [
|
|
94
|
+
'maxContextTokens',
|
|
95
|
+
'max_context_tokens',
|
|
96
|
+
'maxContext',
|
|
97
|
+
'max_context',
|
|
98
|
+
'contextTokens',
|
|
99
|
+
'context_tokens'
|
|
100
|
+
];
|
|
101
|
+
for (const key of keys) {
|
|
102
|
+
const value = record[key];
|
|
103
|
+
const parsed = normalizePositiveInteger(value);
|
|
104
|
+
if (parsed) {
|
|
105
|
+
return parsed;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Normalize positive integer value.
|
|
112
|
+
*/
|
|
113
|
+
function normalizePositiveInteger(value) {
|
|
114
|
+
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
|
115
|
+
return Math.floor(value);
|
|
116
|
+
}
|
|
117
|
+
if (typeof value === 'string') {
|
|
118
|
+
const trimmed = value.trim();
|
|
119
|
+
if (!trimmed) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
const parsed = Number(trimmed);
|
|
123
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
124
|
+
return Math.floor(parsed);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function asRecord<T extends Record<string, unknown>>(value: unknown): T;
|
|
2
|
+
export declare function readOptionalString(value: unknown): string | undefined;
|
|
3
|
+
export declare function normalizePositiveInteger(value: unknown): number | undefined;
|
|
4
|
+
export declare function normalizeAlias(candidate: string | undefined, existing: Set<string>): string;
|
|
5
|
+
export declare function pushUnique<T>(list: T[], value: T): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export function asRecord(value) {
|
|
2
|
+
return (value && typeof value === 'object' ? value : {});
|
|
3
|
+
}
|
|
4
|
+
export function readOptionalString(value) {
|
|
5
|
+
if (typeof value !== 'string') {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
const trimmed = value.trim();
|
|
9
|
+
return trimmed ? trimmed : undefined;
|
|
10
|
+
}
|
|
11
|
+
export function normalizePositiveInteger(value) {
|
|
12
|
+
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
|
13
|
+
return Math.floor(value);
|
|
14
|
+
}
|
|
15
|
+
if (typeof value === 'string') {
|
|
16
|
+
const trimmed = value.trim();
|
|
17
|
+
if (!trimmed) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const parsed = Number(trimmed);
|
|
21
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
22
|
+
return Math.floor(parsed);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
export function normalizeAlias(candidate, existing) {
|
|
28
|
+
const base = candidate && candidate.trim() ? candidate.trim() : `key${existing.size + 1}`;
|
|
29
|
+
let alias = base;
|
|
30
|
+
let i = 1;
|
|
31
|
+
while (existing.has(alias)) {
|
|
32
|
+
alias = `${base}_${i}`;
|
|
33
|
+
i += 1;
|
|
34
|
+
}
|
|
35
|
+
return alias;
|
|
36
|
+
}
|
|
37
|
+
export function pushUnique(list, value) {
|
|
38
|
+
if (!list.includes(value)) {
|
|
39
|
+
list.push(value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type VirtualRouterWebSearchConfig } from '../types.js';
|
|
2
|
+
import type { NormalizedRoutePoolConfig } from './routing-config.js';
|
|
3
|
+
export declare function validateWebSearchRouting(webSearch: VirtualRouterWebSearchConfig | undefined, routingSource: Record<string, NormalizedRoutePoolConfig[]>): void;
|
|
4
|
+
export declare function normalizeWebSearch(input: unknown, routingSource: Record<string, NormalizedRoutePoolConfig[]>): VirtualRouterWebSearchConfig | undefined;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { VirtualRouterError, VirtualRouterErrorCode } from '../types.js';
|
|
2
|
+
export function validateWebSearchRouting(webSearch, routingSource) {
|
|
3
|
+
if (!webSearch) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
const routePools = routingSource.web_search;
|
|
7
|
+
if (!Array.isArray(routePools) || !routePools.length) {
|
|
8
|
+
throw new VirtualRouterError('Virtual Router webSearch.engines configured but routing.web_search route is missing or empty', VirtualRouterErrorCode.CONFIG_ERROR);
|
|
9
|
+
}
|
|
10
|
+
const targets = new Set(collectWebSearchRouteTargets(routingSource));
|
|
11
|
+
for (const engine of webSearch.engines) {
|
|
12
|
+
if (!targets.has(engine.providerKey)) {
|
|
13
|
+
throw new VirtualRouterError(`Virtual Router webSearch engine "${engine.id}" references providerKey "${engine.providerKey}" which is not present in routing.web_search/search`, VirtualRouterErrorCode.CONFIG_ERROR);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function normalizeWebSearch(input, routingSource) {
|
|
18
|
+
if (!input || typeof input !== 'object') {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const record = input;
|
|
22
|
+
const enginesNode = Array.isArray(record.engines) ? record.engines : [];
|
|
23
|
+
const engines = [];
|
|
24
|
+
const webSearchRouteTargets = collectWebSearchRouteTargets(routingSource);
|
|
25
|
+
for (const raw of enginesNode) {
|
|
26
|
+
if (!raw || typeof raw !== 'object') {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const node = raw;
|
|
30
|
+
const idRaw = node.id;
|
|
31
|
+
const providerKeyRaw = node.providerKey ?? node.provider ?? node.target;
|
|
32
|
+
const id = typeof idRaw === 'string' && idRaw.trim() ? idRaw.trim() : undefined;
|
|
33
|
+
const providerKey = typeof providerKeyRaw === 'string' && providerKeyRaw.trim() ? providerKeyRaw.trim() : undefined;
|
|
34
|
+
if (!id || !providerKey) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const resolvedProviderKey = resolveWebSearchEngineProviderKey(providerKey, webSearchRouteTargets) ?? providerKey;
|
|
38
|
+
const description = typeof node.description === 'string' && node.description.trim() ? node.description.trim() : undefined;
|
|
39
|
+
const isDefault = node.default === true || (typeof node.default === 'string' && node.default.trim().toLowerCase() === 'true');
|
|
40
|
+
const serverToolsDisabled = node.serverToolsDisabled === true ||
|
|
41
|
+
(typeof node.serverToolsDisabled === 'string' &&
|
|
42
|
+
node.serverToolsDisabled.trim().toLowerCase() === 'true') ||
|
|
43
|
+
(node.serverTools &&
|
|
44
|
+
typeof node.serverTools === 'object' &&
|
|
45
|
+
node.serverTools.enabled === false);
|
|
46
|
+
if (engines.some((engine) => engine.id === id)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
engines.push({
|
|
50
|
+
id,
|
|
51
|
+
providerKey: resolvedProviderKey,
|
|
52
|
+
description,
|
|
53
|
+
default: isDefault,
|
|
54
|
+
...(serverToolsDisabled ? { serverToolsDisabled: true } : {})
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (!engines.length) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
let injectPolicy;
|
|
61
|
+
let force;
|
|
62
|
+
const rawPolicy = record.injectPolicy ?? record.inject_policy;
|
|
63
|
+
if (typeof rawPolicy === 'string') {
|
|
64
|
+
const normalized = rawPolicy.trim().toLowerCase();
|
|
65
|
+
if (normalized === 'always' || normalized === 'selective') {
|
|
66
|
+
injectPolicy = normalized;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (record.force === true || (typeof record.force === 'string' && record.force.trim().toLowerCase() === 'true')) {
|
|
70
|
+
force = true;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const webSearchPools = routingSource.web_search ?? [];
|
|
74
|
+
if (Array.isArray(webSearchPools) && webSearchPools.some((pool) => pool.force)) {
|
|
75
|
+
force = true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
engines,
|
|
80
|
+
injectPolicy: injectPolicy ?? 'selective',
|
|
81
|
+
...(force ? { force } : {})
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function collectWebSearchRouteTargets(routingSource) {
|
|
85
|
+
const routePools = routingSource.web_search;
|
|
86
|
+
if (!Array.isArray(routePools) || !routePools.length) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
const targets = [];
|
|
91
|
+
for (const pool of routePools) {
|
|
92
|
+
if (!pool || !Array.isArray(pool.targets)) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
for (const target of pool.targets) {
|
|
96
|
+
if (typeof target !== 'string') {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const normalized = target.trim();
|
|
100
|
+
if (!normalized || seen.has(normalized)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
seen.add(normalized);
|
|
104
|
+
targets.push(normalized);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return targets;
|
|
108
|
+
}
|
|
109
|
+
function resolveWebSearchEngineProviderKey(providerKey, routeTargets) {
|
|
110
|
+
const input = providerKey.trim();
|
|
111
|
+
if (!input) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
if (routeTargets.includes(input)) {
|
|
115
|
+
return input;
|
|
116
|
+
}
|
|
117
|
+
const prefixMatches = routeTargets.filter((target) => target.startsWith(`${input}.`));
|
|
118
|
+
if (prefixMatches.length > 0) {
|
|
119
|
+
return prefixMatches[0];
|
|
120
|
+
}
|
|
121
|
+
const firstDot = input.indexOf('.');
|
|
122
|
+
if (firstDot > 0 && firstDot < input.length - 1) {
|
|
123
|
+
const providerId = input.slice(0, firstDot);
|
|
124
|
+
const modelSuffix = input.slice(firstDot + 1);
|
|
125
|
+
const suffixMatches = routeTargets.filter((target) => target.startsWith(`${providerId}.`) && target.endsWith(`.${modelSuffix}`));
|
|
126
|
+
if (suffixMatches.length > 0) {
|
|
127
|
+
return suffixMatches[0];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
@@ -1,6 +1,2 @@
|
|
|
1
1
|
import { type VirtualRouterBootstrapInput, type VirtualRouterBootstrapResult } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* 将用户提供的 Virtual Router 配置(或包含 virtualrouter 字段的整体配置)
|
|
4
|
-
* 规范化为 VirtualRouterConfig,供 HubPipeline / VirtualRouterEngine 直接使用。
|
|
5
|
-
*/
|
|
6
2
|
export declare function bootstrapVirtualRouterConfig(input: VirtualRouterBootstrapInput): VirtualRouterBootstrapResult;
|