@jsonstudio/llms 0.6.203 → 0.6.230
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.js +128 -4
- package/dist/conversion/compat/actions/glm-web-search.d.ts +2 -0
- package/dist/conversion/compat/actions/glm-web-search.js +66 -0
- package/dist/conversion/compat/profiles/chat-glm.json +4 -1
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +6 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +2 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +11 -3
- package/dist/conversion/hub/process/chat-process.js +131 -1
- package/dist/conversion/hub/response/provider-response.d.ts +22 -0
- package/dist/conversion/hub/response/provider-response.js +12 -1
- package/dist/conversion/hub/response/server-side-tools.d.ts +26 -0
- package/dist/conversion/hub/response/server-side-tools.js +326 -0
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +118 -11
- package/dist/conversion/hub/types/standardized.d.ts +1 -0
- package/dist/conversion/responses/responses-openai-bridge.js +49 -3
- package/dist/conversion/shared/snapshot-utils.js +17 -47
- package/dist/conversion/shared/tool-mapping.js +25 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/router/virtual-router/bootstrap.js +273 -40
- package/dist/router/virtual-router/context-advisor.d.ts +0 -2
- package/dist/router/virtual-router/context-advisor.js +0 -12
- package/dist/router/virtual-router/engine.d.ts +8 -2
- package/dist/router/virtual-router/engine.js +176 -81
- package/dist/router/virtual-router/types.d.ts +21 -2
- package/dist/sse/json-to-sse/event-generators/responses.js +15 -3
- package/dist/sse/sse-to-json/gemini-sse-to-json-converter.js +27 -1
- package/dist/sse/types/gemini-types.d.ts +20 -1
- package/dist/sse/types/responses-types.js +1 -1
- package/dist/telemetry/stats-center.d.ts +73 -0
- package/dist/telemetry/stats-center.js +280 -0
- package/package.json +1 -1
|
@@ -56,6 +56,19 @@ function enforceBuiltinToolSchema(name, candidate) {
|
|
|
56
56
|
const base = asSchema(candidate);
|
|
57
57
|
return ensureApplyPatchSchema(base);
|
|
58
58
|
}
|
|
59
|
+
if (normalizedName === 'web_search') {
|
|
60
|
+
// For web_search we currently accept any incoming schema and fall back to a
|
|
61
|
+
// minimal object definition. Server-side web_search execution only relies
|
|
62
|
+
// on the function name + JSON arguments, so tool schema is best-effort.
|
|
63
|
+
const base = asSchema(candidate) ?? {};
|
|
64
|
+
if (!base.type) {
|
|
65
|
+
base.type = 'object';
|
|
66
|
+
}
|
|
67
|
+
if (!Object.prototype.hasOwnProperty.call(base, 'properties')) {
|
|
68
|
+
base.properties = {};
|
|
69
|
+
}
|
|
70
|
+
return base;
|
|
71
|
+
}
|
|
59
72
|
return asSchema(candidate);
|
|
60
73
|
}
|
|
61
74
|
const DEFAULT_SANITIZER = (value) => {
|
|
@@ -111,10 +124,20 @@ export function bridgeToolToChatDefinition(rawTool, options) {
|
|
|
111
124
|
}
|
|
112
125
|
const tool = rawTool;
|
|
113
126
|
const fnNode = tool.function && typeof tool.function === 'object' ? tool.function : undefined;
|
|
114
|
-
|
|
127
|
+
let name = pickToolName([fnNode?.name, tool.name], options);
|
|
128
|
+
// Special case for Responses builtin web_search tools:
|
|
129
|
+
// Codex / Claude‑code may send tools shaped as `{ type: "web_search", ... }`
|
|
130
|
+
// without a nested `function` node. Treat these as a canonical `web_search`
|
|
131
|
+
// function tool so downstream Chat / Standardized layers can reason over a
|
|
132
|
+
// single function-style web_search surface.
|
|
115
133
|
if (!name) {
|
|
116
|
-
|
|
134
|
+
const rawType = typeof tool.type === 'string' ? tool.type.trim().toLowerCase() : '';
|
|
135
|
+
if (rawType === 'web_search' || rawType.startsWith('web_search')) {
|
|
136
|
+
name = 'web_search';
|
|
137
|
+
}
|
|
117
138
|
}
|
|
139
|
+
if (!name)
|
|
140
|
+
return null;
|
|
118
141
|
const description = resolveToolDescription(fnNode?.description ?? tool.description);
|
|
119
142
|
const parameters = enforceBuiltinToolSchema(name, resolveToolParameters(fnNode, tool));
|
|
120
143
|
const strict = resolveToolStrict(fnNode, tool);
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -11,8 +11,7 @@ const DEFAULT_LOAD_BALANCING = { strategy: 'round-robin' };
|
|
|
11
11
|
const DEFAULT_HEALTH = { failureThreshold: 3, cooldownMs: 30_000, fatalCooldownMs: 300_000 };
|
|
12
12
|
const DEFAULT_CONTEXT_ROUTING = {
|
|
13
13
|
warnRatio: 0.9,
|
|
14
|
-
hardLimit: false
|
|
15
|
-
fallbackRoute: 'longcontext'
|
|
14
|
+
hardLimit: false
|
|
16
15
|
};
|
|
17
16
|
/**
|
|
18
17
|
* 将用户提供的 Virtual Router 配置(或包含 virtualrouter 字段的整体配置)
|
|
@@ -28,6 +27,8 @@ export function bootstrapVirtualRouterConfig(input) {
|
|
|
28
27
|
if (!Object.keys(routingSource).length) {
|
|
29
28
|
throw new VirtualRouterError('Virtual Router routing table cannot be empty', VirtualRouterErrorCode.CONFIG_ERROR);
|
|
30
29
|
}
|
|
30
|
+
const webSearch = normalizeWebSearch(section.webSearch);
|
|
31
|
+
validateWebSearchRouting(webSearch, routingSource);
|
|
31
32
|
const { runtimeEntries, aliasIndex } = buildProviderRuntimeEntries(providersSource);
|
|
32
33
|
const { routing, targetKeys } = expandRoutingTable(routingSource, aliasIndex);
|
|
33
34
|
if (!routing.default || routing.default.length === 0) {
|
|
@@ -44,7 +45,8 @@ export function bootstrapVirtualRouterConfig(input) {
|
|
|
44
45
|
classifier,
|
|
45
46
|
loadBalancing,
|
|
46
47
|
health,
|
|
47
|
-
contextRouting
|
|
48
|
+
contextRouting,
|
|
49
|
+
...(webSearch ? { webSearch } : {})
|
|
48
50
|
};
|
|
49
51
|
return {
|
|
50
52
|
config,
|
|
@@ -65,7 +67,8 @@ function extractVirtualRouterSection(input) {
|
|
|
65
67
|
const loadBalancing = normalizeLoadBalancing(section.loadBalancing ?? root.loadBalancing);
|
|
66
68
|
const health = normalizeHealth(section.health ?? root.health);
|
|
67
69
|
const contextRouting = normalizeContextRouting(section.contextRouting ?? root.contextRouting);
|
|
68
|
-
|
|
70
|
+
const webSearch = section.webSearch ?? root.webSearch;
|
|
71
|
+
return { providers, routing, classifier, loadBalancing, health, contextRouting, webSearch };
|
|
69
72
|
}
|
|
70
73
|
function buildProviderRuntimeEntries(providers) {
|
|
71
74
|
const runtimeEntries = {};
|
|
@@ -95,6 +98,11 @@ function buildProviderRuntimeEntries(providers) {
|
|
|
95
98
|
userInfoUrl: entry.auth.userInfoUrl,
|
|
96
99
|
refreshUrl: entry.auth.refreshUrl
|
|
97
100
|
};
|
|
101
|
+
// 为 OAuth 类型的 auth 设置 tokenFile 为 alias(如果没有显式配置 tokenFile)
|
|
102
|
+
// 这允许 oauth-lifecycle.ts 的 resolveTokenFilePath 函数正确解析并匹配现有文件
|
|
103
|
+
if (!runtimeAuth.tokenFile && (runtimeAuth.rawType?.includes('oauth') || runtimeAuth.type === 'oauth')) {
|
|
104
|
+
runtimeAuth.tokenFile = entry.keyAlias;
|
|
105
|
+
}
|
|
98
106
|
if (runtimeAuth.type === 'apiKey' && !runtimeAuth.secretRef) {
|
|
99
107
|
runtimeAuth.secretRef = `${providerId}.${entry.keyAlias}`;
|
|
100
108
|
}
|
|
@@ -122,28 +130,39 @@ function buildProviderRuntimeEntries(providers) {
|
|
|
122
130
|
function expandRoutingTable(routingSource, aliasIndex) {
|
|
123
131
|
const routing = {};
|
|
124
132
|
const targetKeys = new Set();
|
|
125
|
-
for (const [routeName,
|
|
126
|
-
const
|
|
127
|
-
for (const
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
for (const [routeName, pools] of Object.entries(routingSource)) {
|
|
134
|
+
const expandedPools = [];
|
|
135
|
+
for (const pool of pools) {
|
|
136
|
+
const expandedTargets = [];
|
|
137
|
+
for (const entry of pool.targets) {
|
|
138
|
+
const parsed = parseRouteEntry(entry, aliasIndex);
|
|
139
|
+
if (!parsed) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (!aliasIndex.has(parsed.providerId)) {
|
|
143
|
+
throw new VirtualRouterError(`Route "${routeName}" references unknown provider "${parsed.providerId}"`, VirtualRouterErrorCode.CONFIG_ERROR);
|
|
144
|
+
}
|
|
145
|
+
const aliases = parsed.keyAlias ? [parsed.keyAlias] : aliasIndex.get(parsed.providerId);
|
|
146
|
+
if (!aliases.length) {
|
|
147
|
+
throw new VirtualRouterError(`Provider ${parsed.providerId} has no auth aliases but is referenced in routing`, VirtualRouterErrorCode.CONFIG_ERROR);
|
|
148
|
+
}
|
|
149
|
+
for (const alias of aliases) {
|
|
150
|
+
const runtimeKey = buildRuntimeKey(parsed.providerId, alias);
|
|
151
|
+
const targetKey = `${runtimeKey}.${parsed.modelId}`;
|
|
152
|
+
pushUnique(expandedTargets, targetKey);
|
|
153
|
+
targetKeys.add(targetKey);
|
|
154
|
+
}
|
|
134
155
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
pushUnique(expanded, targetKey);
|
|
143
|
-
targetKeys.add(targetKey);
|
|
156
|
+
if (expandedTargets.length) {
|
|
157
|
+
expandedPools.push({
|
|
158
|
+
id: pool.id,
|
|
159
|
+
priority: pool.priority,
|
|
160
|
+
backup: pool.backup,
|
|
161
|
+
targets: expandedTargets
|
|
162
|
+
});
|
|
144
163
|
}
|
|
145
164
|
}
|
|
146
|
-
routing[routeName] =
|
|
165
|
+
routing[routeName] = expandedPools;
|
|
147
166
|
}
|
|
148
167
|
return { routing, targetKeys };
|
|
149
168
|
}
|
|
@@ -200,15 +219,135 @@ function resolveContextTokens(runtime, modelId) {
|
|
|
200
219
|
function normalizeRouting(source) {
|
|
201
220
|
const routing = {};
|
|
202
221
|
for (const [routeName, entries] of Object.entries(source)) {
|
|
203
|
-
if (!Array.isArray(entries))
|
|
222
|
+
if (!Array.isArray(entries) || !entries.length) {
|
|
223
|
+
routing[routeName] = [];
|
|
204
224
|
continue;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
225
|
+
}
|
|
226
|
+
const allStrings = entries.every((entry) => typeof entry === 'string' || entry === null || entry === undefined);
|
|
227
|
+
if (allStrings) {
|
|
228
|
+
const targets = normalizeTargetList(entries);
|
|
229
|
+
routing[routeName] = targets.length ? [buildLegacyRoutePool(routeName, targets)] : [];
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const normalized = [];
|
|
233
|
+
const total = entries.length || 1;
|
|
234
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
235
|
+
const entry = entries[index];
|
|
236
|
+
const pool = normalizeRoutePoolEntry(routeName, entry, index, total);
|
|
237
|
+
if (pool && pool.targets.length) {
|
|
238
|
+
normalized.push(pool);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
routing[routeName] = normalized;
|
|
209
242
|
}
|
|
210
243
|
return routing;
|
|
211
244
|
}
|
|
245
|
+
function buildLegacyRoutePool(routeName, targets) {
|
|
246
|
+
return {
|
|
247
|
+
id: `${routeName}:pool0`,
|
|
248
|
+
priority: targets.length,
|
|
249
|
+
backup: false,
|
|
250
|
+
targets
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function normalizeRoutePoolEntry(routeName, entry, index, total) {
|
|
254
|
+
if (typeof entry === 'string') {
|
|
255
|
+
const targets = normalizeTargetList(entry);
|
|
256
|
+
return targets.length
|
|
257
|
+
? {
|
|
258
|
+
id: `${routeName}:pool${index + 1}`,
|
|
259
|
+
priority: total - index,
|
|
260
|
+
backup: false,
|
|
261
|
+
targets
|
|
262
|
+
}
|
|
263
|
+
: null;
|
|
264
|
+
}
|
|
265
|
+
if (!entry || typeof entry !== 'object') {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
const record = entry;
|
|
269
|
+
const id = readOptionalString(record.id) ??
|
|
270
|
+
readOptionalString(record?.poolId) ??
|
|
271
|
+
`${routeName}:pool${index + 1}`;
|
|
272
|
+
const backup = record.backup === true ||
|
|
273
|
+
record.isBackup === true ||
|
|
274
|
+
(typeof record.type === 'string' && record.type.toLowerCase() === 'backup');
|
|
275
|
+
const priority = normalizePriorityValue(record.priority, total - index);
|
|
276
|
+
const targets = normalizeRouteTargets(record);
|
|
277
|
+
return targets.length
|
|
278
|
+
? {
|
|
279
|
+
id,
|
|
280
|
+
priority,
|
|
281
|
+
backup,
|
|
282
|
+
targets
|
|
283
|
+
}
|
|
284
|
+
: null;
|
|
285
|
+
}
|
|
286
|
+
function normalizeRouteTargets(record) {
|
|
287
|
+
const buckets = [
|
|
288
|
+
record.targets,
|
|
289
|
+
record.providers,
|
|
290
|
+
record.pool,
|
|
291
|
+
record.entries,
|
|
292
|
+
record.items,
|
|
293
|
+
record.routes
|
|
294
|
+
];
|
|
295
|
+
const normalized = [];
|
|
296
|
+
for (const bucket of buckets) {
|
|
297
|
+
for (const target of normalizeTargetList(bucket)) {
|
|
298
|
+
if (!normalized.includes(target)) {
|
|
299
|
+
normalized.push(target);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const singular = [record.target, record.provider];
|
|
304
|
+
for (const candidate of singular) {
|
|
305
|
+
for (const target of normalizeTargetList(candidate)) {
|
|
306
|
+
if (!normalized.includes(target)) {
|
|
307
|
+
normalized.push(target);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return normalized;
|
|
312
|
+
}
|
|
313
|
+
function normalizeTargetList(value) {
|
|
314
|
+
if (Array.isArray(value)) {
|
|
315
|
+
const normalized = [];
|
|
316
|
+
for (const entry of value) {
|
|
317
|
+
if (typeof entry === 'string') {
|
|
318
|
+
const trimmed = entry.trim();
|
|
319
|
+
if (trimmed && !normalized.includes(trimmed)) {
|
|
320
|
+
normalized.push(trimmed);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return normalized;
|
|
325
|
+
}
|
|
326
|
+
if (typeof value === 'string') {
|
|
327
|
+
const trimmed = value.trim();
|
|
328
|
+
return trimmed ? [trimmed] : [];
|
|
329
|
+
}
|
|
330
|
+
if (typeof value === 'number') {
|
|
331
|
+
const str = String(value).trim();
|
|
332
|
+
return str ? [str] : [];
|
|
333
|
+
}
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
function normalizePriorityValue(value, fallback) {
|
|
337
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
338
|
+
return value;
|
|
339
|
+
}
|
|
340
|
+
if (typeof value === 'string') {
|
|
341
|
+
const trimmed = value.trim();
|
|
342
|
+
if (trimmed) {
|
|
343
|
+
const parsed = Number(trimmed);
|
|
344
|
+
if (Number.isFinite(parsed)) {
|
|
345
|
+
return parsed;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return fallback;
|
|
350
|
+
}
|
|
212
351
|
function normalizeClassifier(input) {
|
|
213
352
|
const normalized = asRecord(input);
|
|
214
353
|
const result = {
|
|
@@ -389,15 +528,91 @@ function normalizeContextRouting(input) {
|
|
|
389
528
|
coerceRatio(record?.warn_ratio);
|
|
390
529
|
const hardLimitCandidate = coerceBoolean(record.hardLimit) ??
|
|
391
530
|
coerceBoolean(record?.hard_limit);
|
|
392
|
-
const fallbackCandidate = readOptionalString(record.fallbackRoute) ??
|
|
393
|
-
readOptionalString(record?.fallback_route);
|
|
394
531
|
const warnRatio = clampWarnRatio(warnCandidate ?? DEFAULT_CONTEXT_ROUTING.warnRatio);
|
|
395
532
|
const hardLimit = typeof hardLimitCandidate === 'boolean' ? hardLimitCandidate : DEFAULT_CONTEXT_ROUTING.hardLimit;
|
|
396
|
-
const fallbackRoute = fallbackCandidate ?? DEFAULT_CONTEXT_ROUTING.fallbackRoute;
|
|
397
533
|
return {
|
|
398
534
|
warnRatio,
|
|
399
|
-
hardLimit
|
|
400
|
-
|
|
535
|
+
hardLimit
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
function validateWebSearchRouting(webSearch, routingSource) {
|
|
539
|
+
if (!webSearch) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const routePools = routingSource['web_search'];
|
|
543
|
+
if (!Array.isArray(routePools) || !routePools.length) {
|
|
544
|
+
throw new VirtualRouterError('Virtual Router webSearch.engines configured but routing.web_search route is missing or empty', VirtualRouterErrorCode.CONFIG_ERROR);
|
|
545
|
+
}
|
|
546
|
+
const targets = new Set();
|
|
547
|
+
for (const pool of routePools) {
|
|
548
|
+
if (!pool || !Array.isArray(pool.targets)) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
for (const target of pool.targets) {
|
|
552
|
+
if (typeof target === 'string' && target.trim()) {
|
|
553
|
+
targets.add(target.trim());
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
for (const engine of webSearch.engines) {
|
|
558
|
+
if (!targets.has(engine.providerKey)) {
|
|
559
|
+
throw new VirtualRouterError(`Virtual Router webSearch engine "${engine.id}" references providerKey "${engine.providerKey}" which is not present in routing.web_search`, VirtualRouterErrorCode.CONFIG_ERROR);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
function normalizeWebSearch(input) {
|
|
564
|
+
if (!input || typeof input !== 'object') {
|
|
565
|
+
return undefined;
|
|
566
|
+
}
|
|
567
|
+
const record = input;
|
|
568
|
+
const enginesNode = Array.isArray(record.engines) ? record.engines : [];
|
|
569
|
+
const engines = [];
|
|
570
|
+
for (const raw of enginesNode) {
|
|
571
|
+
if (!raw || typeof raw !== 'object') {
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
const node = raw;
|
|
575
|
+
const idRaw = node.id;
|
|
576
|
+
const providerKeyRaw = node.providerKey ?? node.provider ?? node.target;
|
|
577
|
+
const id = typeof idRaw === 'string' && idRaw.trim()
|
|
578
|
+
? idRaw.trim()
|
|
579
|
+
: undefined;
|
|
580
|
+
const providerKey = typeof providerKeyRaw === 'string' && providerKeyRaw.trim()
|
|
581
|
+
? providerKeyRaw.trim()
|
|
582
|
+
: undefined;
|
|
583
|
+
if (!id || !providerKey) {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
const description = typeof node.description === 'string' && node.description.trim()
|
|
587
|
+
? node.description.trim()
|
|
588
|
+
: undefined;
|
|
589
|
+
const isDefault = node.default === true ||
|
|
590
|
+
(typeof node.default === 'string' && node.default.trim().toLowerCase() === 'true');
|
|
591
|
+
// Deduplicate by id; first wins, subsequent are ignored.
|
|
592
|
+
if (engines.some((engine) => engine.id === id)) {
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
engines.push({
|
|
596
|
+
id,
|
|
597
|
+
providerKey,
|
|
598
|
+
description,
|
|
599
|
+
default: isDefault
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
if (!engines.length) {
|
|
603
|
+
return undefined;
|
|
604
|
+
}
|
|
605
|
+
let injectPolicy;
|
|
606
|
+
const rawPolicy = record.injectPolicy ?? record?.inject_policy;
|
|
607
|
+
if (typeof rawPolicy === 'string') {
|
|
608
|
+
const normalized = rawPolicy.trim().toLowerCase();
|
|
609
|
+
if (normalized === 'always' || normalized === 'selective') {
|
|
610
|
+
injectPolicy = normalized;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
engines,
|
|
615
|
+
injectPolicy: injectPolicy ?? 'selective'
|
|
401
616
|
};
|
|
402
617
|
}
|
|
403
618
|
function extractProviderAuthEntries(providerId, raw) {
|
|
@@ -531,19 +746,37 @@ function extractProviderAuthEntries(providerId, raw) {
|
|
|
531
746
|
pushEntry(undefined, buildAuthCandidate(baseTypeSource, { value: apiKeyField.trim() }));
|
|
532
747
|
}
|
|
533
748
|
// 自动多 token 扫描:仅在未显式声明多 key、且为受支持的 OAuth 提供方时触发
|
|
534
|
-
if (
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
749
|
+
if (baseType === 'oauth') {
|
|
750
|
+
const scanCandidates = new Set();
|
|
751
|
+
const pushCandidate = (value) => {
|
|
752
|
+
if (typeof value === 'string' && value.trim()) {
|
|
753
|
+
scanCandidates.add(value.trim().toLowerCase());
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
pushCandidate(auth?.oauthProviderId);
|
|
757
|
+
pushCandidate(baseTypeInfo.oauthProviderId);
|
|
758
|
+
pushCandidate(providerId);
|
|
759
|
+
for (const candidate of scanCandidates) {
|
|
760
|
+
if (!MULTI_TOKEN_OAUTH_PROVIDERS.has(candidate)) {
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
const tokenFiles = scanOAuthTokenFiles(candidate);
|
|
764
|
+
if (!tokenFiles.length) {
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
const baseTypeAlias = baseTypeInfo.oauthProviderId?.toLowerCase();
|
|
538
768
|
for (const match of tokenFiles) {
|
|
539
769
|
const alias = match.alias && match.alias !== 'default'
|
|
540
770
|
? `${match.sequence}-${match.alias}`
|
|
541
771
|
: String(match.sequence);
|
|
772
|
+
const typeHint = baseTypeSource && baseTypeAlias === candidate
|
|
773
|
+
? baseTypeSource
|
|
774
|
+
: `${candidate}-oauth`;
|
|
542
775
|
const authConfig = {
|
|
543
776
|
...defaults,
|
|
544
|
-
type:
|
|
777
|
+
type: typeHint,
|
|
545
778
|
tokenFile: match.filePath,
|
|
546
|
-
oauthProviderId
|
|
779
|
+
oauthProviderId: candidate
|
|
547
780
|
};
|
|
548
781
|
pushEntry(alias, authConfig);
|
|
549
782
|
}
|
|
@@ -712,7 +945,7 @@ function mergeScopes(primary, fallback) {
|
|
|
712
945
|
}
|
|
713
946
|
return merged.size ? Array.from(merged) : undefined;
|
|
714
947
|
}
|
|
715
|
-
const MULTI_TOKEN_OAUTH_PROVIDERS = new Set(['iflow']);
|
|
948
|
+
const MULTI_TOKEN_OAUTH_PROVIDERS = new Set(['iflow', 'qwen', 'gemini-cli', 'antigravity']);
|
|
716
949
|
function interpretAuthType(value) {
|
|
717
950
|
if (typeof value !== 'string') {
|
|
718
951
|
return { type: 'apiKey' };
|
|
@@ -16,6 +16,4 @@ export declare class ContextAdvisor {
|
|
|
16
16
|
private hardLimit;
|
|
17
17
|
configure(config?: VirtualRouterContextRoutingConfig | null): void;
|
|
18
18
|
classify(pool: string[], estimatedTokens: number, resolveProfile: (key: string) => ProviderProfile): ContextAdvisorResult;
|
|
19
|
-
prefersFallback(result: ContextAdvisorResult): boolean;
|
|
20
|
-
allowsOverflow(): boolean;
|
|
21
19
|
}
|
|
@@ -55,18 +55,6 @@ export class ContextAdvisor {
|
|
|
55
55
|
allOverflow: safe.length === 0 && risky.length === 0 && overflow.length > 0
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
|
-
prefersFallback(result) {
|
|
59
|
-
if (result.safe.length > 0) {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
if (result.risky.length > 0) {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
return result.allOverflow;
|
|
66
|
-
}
|
|
67
|
-
allowsOverflow() {
|
|
68
|
-
return !this.hardLimit;
|
|
69
|
-
}
|
|
70
58
|
}
|
|
71
59
|
function clampWarnRatio(value) {
|
|
72
60
|
if (!Number.isFinite(value)) {
|
|
@@ -11,6 +11,7 @@ export declare class VirtualRouterEngine {
|
|
|
11
11
|
private routeStats;
|
|
12
12
|
private readonly debug;
|
|
13
13
|
private healthConfig;
|
|
14
|
+
private readonly statsCenter;
|
|
14
15
|
initialize(config: VirtualRouterConfig): void;
|
|
15
16
|
route(request: StandardizedRequest | ProcessedRequest, metadata: RouterMetadataInput): {
|
|
16
17
|
target: TargetMetadata;
|
|
@@ -29,11 +30,10 @@ export declare class VirtualRouterEngine {
|
|
|
29
30
|
};
|
|
30
31
|
private validateConfig;
|
|
31
32
|
private selectProvider;
|
|
33
|
+
private trySelectFromTier;
|
|
32
34
|
private incrementRouteStat;
|
|
33
35
|
private providerHealthConfig;
|
|
34
36
|
private initializeRouteQueue;
|
|
35
|
-
private resolveFallbackRoute;
|
|
36
|
-
private maybeDeferToFallback;
|
|
37
37
|
private buildContextCandidatePools;
|
|
38
38
|
private describeAttempt;
|
|
39
39
|
private resolveStickyKey;
|
|
@@ -42,9 +42,15 @@ export declare class VirtualRouterEngine {
|
|
|
42
42
|
private buildRouteCandidates;
|
|
43
43
|
private sortByPriority;
|
|
44
44
|
private routeWeight;
|
|
45
|
+
private routeHasTargets;
|
|
46
|
+
private hasPrimaryPool;
|
|
47
|
+
private sortRoutePools;
|
|
48
|
+
private flattenPoolTargets;
|
|
45
49
|
private buildHitReason;
|
|
46
50
|
private decorateWithDetail;
|
|
47
51
|
private formatVirtualRouterHit;
|
|
48
52
|
private resolveRouteColor;
|
|
49
53
|
private describeContextUsage;
|
|
54
|
+
private describeTargetProvider;
|
|
55
|
+
private parseProviderKey;
|
|
50
56
|
}
|