@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
|
@@ -1,10 +1,51 @@
|
|
|
1
1
|
import { type ClassificationResult, type RoutingFeatures, type RoutingInstructionMode, type VirtualRouterContextRoutingConfig } from './types.js';
|
|
2
2
|
import { ProviderRegistry } from './provider-registry.js';
|
|
3
3
|
import type { RoutingInstructionState } from './routing-instructions.js';
|
|
4
|
+
import type { VirtualRouterHitEvent } from '../../telemetry/stats-center.js';
|
|
4
5
|
type LoggingDeps = {
|
|
5
6
|
providerRegistry: ProviderRegistry;
|
|
6
7
|
contextRouting: VirtualRouterContextRoutingConfig | undefined;
|
|
7
8
|
};
|
|
9
|
+
export type StopMessageRuntimeSummary = {
|
|
10
|
+
hasAny: boolean;
|
|
11
|
+
safeText?: string;
|
|
12
|
+
mode: 'on' | 'off' | 'auto' | 'unset';
|
|
13
|
+
maxRepeats: number;
|
|
14
|
+
used: number;
|
|
15
|
+
remaining: number;
|
|
16
|
+
active: boolean;
|
|
17
|
+
updatedAt?: number;
|
|
18
|
+
lastUsedAt?: number;
|
|
19
|
+
};
|
|
20
|
+
export type VirtualRouterHitRecord = {
|
|
21
|
+
timestampMs: number;
|
|
22
|
+
routeName: string;
|
|
23
|
+
poolId?: string;
|
|
24
|
+
providerKey: string;
|
|
25
|
+
modelId?: string;
|
|
26
|
+
hitReason?: string;
|
|
27
|
+
stickyScope?: string;
|
|
28
|
+
requestTokens?: number;
|
|
29
|
+
selectionPenalty?: number;
|
|
30
|
+
stopMessage: StopMessageRuntimeSummary;
|
|
31
|
+
};
|
|
32
|
+
export type VirtualRouterHitEventMeta = {
|
|
33
|
+
requestId: string;
|
|
34
|
+
entryEndpoint?: string;
|
|
35
|
+
};
|
|
36
|
+
export declare function createVirtualRouterHitRecord(input: {
|
|
37
|
+
routeName: string;
|
|
38
|
+
poolId?: string;
|
|
39
|
+
providerKey: string;
|
|
40
|
+
modelId?: string;
|
|
41
|
+
hitReason?: string;
|
|
42
|
+
stickyScope?: string;
|
|
43
|
+
routingState?: RoutingInstructionState;
|
|
44
|
+
requestTokens?: number;
|
|
45
|
+
selectionPenalty?: number;
|
|
46
|
+
timestampMs?: number;
|
|
47
|
+
}): VirtualRouterHitRecord;
|
|
48
|
+
export declare function toVirtualRouterHitEvent(record: VirtualRouterHitRecord, meta: VirtualRouterHitEventMeta): VirtualRouterHitEvent;
|
|
8
49
|
export declare function formatStickyScope(scope?: string): string | undefined;
|
|
9
50
|
export declare function parseProviderKey(providerKey: string): {
|
|
10
51
|
providerId: string;
|
|
@@ -16,5 +57,5 @@ export declare function describeTargetProvider(providerKey: string, fallbackMode
|
|
|
16
57
|
resolvedModel?: string;
|
|
17
58
|
};
|
|
18
59
|
export declare function buildHitReason(routeUsed: string, providerKey: string, classification: ClassificationResult, features: RoutingFeatures, mode: RoutingInstructionMode | undefined, deps: LoggingDeps): string;
|
|
19
|
-
export declare function formatVirtualRouterHit(
|
|
60
|
+
export declare function formatVirtualRouterHit(record: VirtualRouterHitRecord): string;
|
|
20
61
|
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_MODEL_CONTEXT_TOKENS, DEFAULT_ROUTE } from './types.js';
|
|
2
|
+
const DEFAULT_STOP_MESSAGE_MAX_REPEATS = 10;
|
|
2
3
|
function summarizeStopMessageRuntime(state) {
|
|
3
4
|
if (!state) {
|
|
4
5
|
return {
|
|
@@ -14,9 +15,14 @@ function summarizeStopMessageRuntime(state) {
|
|
|
14
15
|
const safeText = text ? (text.length > 24 ? `${text.slice(0, 21)}…` : text) : undefined;
|
|
15
16
|
const modeRaw = typeof state.stopMessageStageMode === 'string' ? state.stopMessageStageMode.trim().toLowerCase() : '';
|
|
16
17
|
const mode = modeRaw === 'on' || modeRaw === 'off' || modeRaw === 'auto' ? modeRaw : 'unset';
|
|
17
|
-
const
|
|
18
|
+
const parsedMaxRepeats = typeof state.stopMessageMaxRepeats === 'number' && Number.isFinite(state.stopMessageMaxRepeats)
|
|
18
19
|
? Math.max(0, Math.floor(state.stopMessageMaxRepeats))
|
|
19
20
|
: 0;
|
|
21
|
+
const maxRepeats = parsedMaxRepeats > 0
|
|
22
|
+
? parsedMaxRepeats
|
|
23
|
+
: mode === 'on' || mode === 'auto'
|
|
24
|
+
? DEFAULT_STOP_MESSAGE_MAX_REPEATS
|
|
25
|
+
: 0;
|
|
20
26
|
const used = typeof state.stopMessageUsed === 'number' && Number.isFinite(state.stopMessageUsed)
|
|
21
27
|
? Math.max(0, Math.floor(state.stopMessageUsed))
|
|
22
28
|
: 0;
|
|
@@ -41,6 +47,55 @@ function summarizeStopMessageRuntime(state) {
|
|
|
41
47
|
...(lastUsedAt ? { lastUsedAt } : {})
|
|
42
48
|
};
|
|
43
49
|
}
|
|
50
|
+
function normalizePositiveInteger(value) {
|
|
51
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
return Math.floor(value);
|
|
55
|
+
}
|
|
56
|
+
function normalizeRoundedInteger(value) {
|
|
57
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
return Math.max(0, Math.round(value));
|
|
61
|
+
}
|
|
62
|
+
export function createVirtualRouterHitRecord(input) {
|
|
63
|
+
return {
|
|
64
|
+
timestampMs: typeof input.timestampMs === 'number' && Number.isFinite(input.timestampMs)
|
|
65
|
+
? input.timestampMs
|
|
66
|
+
: Date.now(),
|
|
67
|
+
routeName: input.routeName,
|
|
68
|
+
...(input.poolId ? { poolId: input.poolId } : {}),
|
|
69
|
+
providerKey: input.providerKey,
|
|
70
|
+
...(input.modelId ? { modelId: input.modelId } : {}),
|
|
71
|
+
...(input.hitReason ? { hitReason: input.hitReason } : {}),
|
|
72
|
+
...(input.stickyScope ? { stickyScope: input.stickyScope } : {}),
|
|
73
|
+
...(typeof normalizeRoundedInteger(input.requestTokens) === 'number'
|
|
74
|
+
? { requestTokens: normalizeRoundedInteger(input.requestTokens) }
|
|
75
|
+
: {}),
|
|
76
|
+
...(typeof normalizePositiveInteger(input.selectionPenalty) === 'number'
|
|
77
|
+
? { selectionPenalty: normalizePositiveInteger(input.selectionPenalty) }
|
|
78
|
+
: {}),
|
|
79
|
+
stopMessage: summarizeStopMessageRuntime(input.routingState)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export function toVirtualRouterHitEvent(record, meta) {
|
|
83
|
+
return {
|
|
84
|
+
requestId: meta.requestId,
|
|
85
|
+
timestamp: record.timestampMs,
|
|
86
|
+
entryEndpoint: meta.entryEndpoint || '/v1/chat/completions',
|
|
87
|
+
routeName: record.routeName,
|
|
88
|
+
pool: record.poolId || record.routeName,
|
|
89
|
+
providerKey: record.providerKey,
|
|
90
|
+
...(record.modelId ? { modelId: record.modelId } : {}),
|
|
91
|
+
...(record.hitReason ? { reason: record.hitReason } : {}),
|
|
92
|
+
...(typeof record.requestTokens === 'number' ? { requestTokens: record.requestTokens } : {}),
|
|
93
|
+
...(typeof record.selectionPenalty === 'number' ? { selectionPenalty: record.selectionPenalty } : {}),
|
|
94
|
+
stopMessageActive: record.stopMessage.active,
|
|
95
|
+
...(record.stopMessage.mode !== 'unset' ? { stopMessageMode: record.stopMessage.mode } : {}),
|
|
96
|
+
...(record.stopMessage.remaining >= 0 ? { stopMessageRemaining: record.stopMessage.remaining } : {})
|
|
97
|
+
};
|
|
98
|
+
}
|
|
44
99
|
export function formatStickyScope(scope) {
|
|
45
100
|
if (!scope || scope.trim().length === 0) {
|
|
46
101
|
return undefined;
|
|
@@ -175,9 +230,9 @@ export function buildHitReason(routeUsed, providerKey, classification, features,
|
|
|
175
230
|
}
|
|
176
231
|
return base;
|
|
177
232
|
}
|
|
178
|
-
export function formatVirtualRouterHit(
|
|
233
|
+
export function formatVirtualRouterHit(record) {
|
|
179
234
|
try {
|
|
180
|
-
const now = new Date();
|
|
235
|
+
const now = new Date(record.timestampMs);
|
|
181
236
|
const hours = String(now.getHours()).padStart(2, '0');
|
|
182
237
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
183
238
|
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
@@ -186,18 +241,24 @@ export function formatVirtualRouterHit(routeName, poolId, providerKey, modelId,
|
|
|
186
241
|
const reset = '\x1b[0m';
|
|
187
242
|
const timeColor = '\x1b[90m';
|
|
188
243
|
const stickyColor = '\x1b[33m';
|
|
189
|
-
const routeColor = resolveRouteColor(routeName);
|
|
244
|
+
const routeColor = resolveRouteColor(record.routeName);
|
|
190
245
|
const stopColor = '\x1b[38;5;214m';
|
|
191
246
|
const prefix = `${prefixColor}[virtual-router-hit]${reset}`;
|
|
192
247
|
const timeLabel = `${timeColor}${timestamp}${reset}`;
|
|
193
|
-
const { providerLabel, resolvedModel } = describeTargetProvider(providerKey, modelId);
|
|
194
|
-
const routeLabel = poolId ? `${routeName}/${poolId}` : routeName;
|
|
248
|
+
const { providerLabel, resolvedModel } = describeTargetProvider(record.providerKey, record.modelId);
|
|
249
|
+
const routeLabel = record.poolId ? `${record.routeName}/${record.poolId}` : record.routeName;
|
|
195
250
|
const targetLabel = `${routeLabel} -> ${providerLabel}${resolvedModel ? '.' + resolvedModel : ''}`;
|
|
196
|
-
const stickyText = formatStickyScope(stickyScope);
|
|
251
|
+
const stickyText = formatStickyScope(record.stickyScope);
|
|
197
252
|
const stickyLabel = stickyText ? ` ${stickyColor}[sticky:${stickyText}]${reset}` : '';
|
|
198
|
-
const reasonLabel = hitReason ? ` reason=${hitReason}` : '';
|
|
253
|
+
const reasonLabel = record.hitReason ? ` reason=${record.hitReason}` : '';
|
|
254
|
+
const requestTokenLabel = typeof record.requestTokens === 'number' && Number.isFinite(record.requestTokens)
|
|
255
|
+
? ` reqTokens=${Math.max(0, Math.round(record.requestTokens))}`
|
|
256
|
+
: '';
|
|
257
|
+
const penaltyLabel = typeof record.selectionPenalty === 'number' && Number.isFinite(record.selectionPenalty) && record.selectionPenalty > 0
|
|
258
|
+
? ` penalty=${Math.floor(record.selectionPenalty)}`
|
|
259
|
+
: '';
|
|
199
260
|
let stopLabel = '';
|
|
200
|
-
const stop =
|
|
261
|
+
const stop = record.stopMessage;
|
|
201
262
|
if (stop.hasAny) {
|
|
202
263
|
const parts = [
|
|
203
264
|
stop.safeText ? `"${stop.safeText}"` : '"(mode-only)"',
|
|
@@ -214,22 +275,28 @@ export function formatVirtualRouterHit(routeName, poolId, providerKey, modelId,
|
|
|
214
275
|
}
|
|
215
276
|
stopLabel = ` ${stopColor}[stopMessage:${parts.join(' ')}]${reset}`;
|
|
216
277
|
}
|
|
217
|
-
return `${prefix} ${timeLabel} ${routeColor}${targetLabel}${stickyLabel}${reasonLabel}${stopLabel}${reset}`;
|
|
278
|
+
return `${prefix} ${timeLabel} ${routeColor}${targetLabel}${stickyLabel}${reasonLabel}${requestTokenLabel}${penaltyLabel}${stopLabel}${reset}`;
|
|
218
279
|
}
|
|
219
280
|
catch {
|
|
220
|
-
const now = new Date();
|
|
281
|
+
const now = new Date(record.timestampMs);
|
|
221
282
|
const timestamp = now.toLocaleTimeString('zh-CN', { hour12: false });
|
|
222
|
-
const routeLabel = poolId ? `${routeName}/${poolId}` : routeName;
|
|
223
|
-
const stickyText = formatStickyScope(stickyScope);
|
|
283
|
+
const routeLabel = record.poolId ? `${record.routeName}/${record.poolId}` : record.routeName;
|
|
284
|
+
const stickyText = formatStickyScope(record.stickyScope);
|
|
224
285
|
const stickyLabel = stickyText ? ` [sticky:${stickyText}]` : '';
|
|
286
|
+
const requestTokenLabel = typeof record.requestTokens === 'number' && Number.isFinite(record.requestTokens)
|
|
287
|
+
? ` reqTokens=${Math.max(0, Math.round(record.requestTokens))}`
|
|
288
|
+
: '';
|
|
289
|
+
const penaltyLabel = typeof record.selectionPenalty === 'number' && Number.isFinite(record.selectionPenalty) && record.selectionPenalty > 0
|
|
290
|
+
? ` penalty=${Math.floor(record.selectionPenalty)}`
|
|
291
|
+
: '';
|
|
225
292
|
let stopLabel = '';
|
|
226
|
-
const stop =
|
|
293
|
+
const stop = record.stopMessage;
|
|
227
294
|
if (stop.hasAny) {
|
|
228
295
|
const safeText = stop.safeText ? `"${stop.safeText}"` : '"(mode-only)"';
|
|
229
296
|
const rounds = stop.maxRepeats > 0 ? `${stop.used}/${stop.maxRepeats}` : `${stop.used}/-`;
|
|
230
297
|
const left = stop.remaining >= 0 ? String(stop.remaining) : 'n/a';
|
|
231
298
|
stopLabel = ` [stopMessage:${safeText} mode=${stop.mode} round=${rounds} active=${stop.active ? 'yes' : 'no'} left=${left}]`;
|
|
232
299
|
}
|
|
233
|
-
return `[virtual-router-hit] ${timestamp} ${routeLabel} -> ${providerKey}${modelId ? '.' + modelId : ''}${stickyLabel}${hitReason ? ` reason=${hitReason}` : ''}${stopLabel}`;
|
|
300
|
+
return `[virtual-router-hit] ${timestamp} ${routeLabel} -> ${record.providerKey}${record.modelId ? '.' + record.modelId : ''}${stickyLabel}${record.hitReason ? ` reason=${record.hitReason}` : ''}${requestTokenLabel}${penaltyLabel}${stopLabel}`;
|
|
234
301
|
}
|
|
235
302
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { extractProviderId, getProviderModelId } from './key-parsing.js';
|
|
2
|
+
function isQwen35PlusProvider(providerKey, providerRegistry) {
|
|
3
|
+
const providerId = (extractProviderId(providerKey) ?? '').trim().toLowerCase();
|
|
4
|
+
if (providerId !== 'qwen') {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
const modelId = (getProviderModelId(providerKey, providerRegistry) ?? '').trim().toLowerCase();
|
|
8
|
+
if (!modelId) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
return modelId === 'qwen3.5-plus' || modelId === 'qwen3-5-plus' || modelId === 'qwen3_5-plus';
|
|
12
|
+
}
|
|
13
|
+
export function providerSupportsMultimodalRequest(providerKey, features, providerRegistry) {
|
|
14
|
+
if (!features.hasImageAttachment) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
if (!isQwen35PlusProvider(providerKey, providerRegistry)) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
if (features.hasVideoAttachment !== true) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
const hasRemoteVideo = features.hasRemoteVideoAttachment === true;
|
|
24
|
+
const hasLocalVideo = features.hasLocalVideoAttachment === true;
|
|
25
|
+
return hasRemoteVideo && !hasLocalVideo;
|
|
26
|
+
}
|
|
@@ -43,6 +43,10 @@ export function buildRouteCandidates(requestedRoute, classificationCandidates, f
|
|
|
43
43
|
const forceVision = routeHasForceFlag('vision', routing);
|
|
44
44
|
const hasMultimodalTargets = routeHasTargets(routing.multimodal);
|
|
45
45
|
const hasVisionTargets = routeHasTargets(routing.vision);
|
|
46
|
+
const hasLocalVideoAttachment = features.hasVideoAttachment === true && features.hasLocalVideoAttachment === true;
|
|
47
|
+
if (features.hasImageAttachment && hasLocalVideoAttachment && hasVisionTargets) {
|
|
48
|
+
return ['vision'];
|
|
49
|
+
}
|
|
46
50
|
const normalized = normalizeRouteAlias(requestedRoute || DEFAULT_ROUTE);
|
|
47
51
|
const baseList = [];
|
|
48
52
|
if (classificationCandidates && classificationCandidates.length) {
|
|
@@ -59,9 +63,9 @@ export function buildRouteCandidates(requestedRoute, classificationCandidates, f
|
|
|
59
63
|
baseList.unshift('multimodal');
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
|
-
|
|
66
|
+
if (hasVisionTargets) {
|
|
63
67
|
if (!baseList.includes('vision')) {
|
|
64
|
-
baseList.
|
|
68
|
+
baseList.push('vision');
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
if (!forceVision && hasMultimodalTargets) {
|
|
@@ -10,6 +10,7 @@ export type SelectionDeps = {
|
|
|
10
10
|
contextAdvisor: ContextAdvisor;
|
|
11
11
|
loadBalancer: RouteLoadBalancer;
|
|
12
12
|
isProviderCoolingDown: (providerKey: string) => boolean;
|
|
13
|
+
getProviderCooldownRemainingMs?: (providerKey: string) => number;
|
|
13
14
|
resolveStickyKey: (metadata: RouterMetadataInput) => string | undefined;
|
|
14
15
|
quotaView?: ProviderQuotaView;
|
|
15
16
|
aliasQueueStore?: Map<string, string[]>;
|
|
@@ -3,6 +3,7 @@ import { resolveContextWeightedConfig } from '../context-weighted.js';
|
|
|
3
3
|
import { resolveHealthWeightedConfig } from '../health-weighted.js';
|
|
4
4
|
import { pinCandidatesByAliasQueue, resolveAliasSelectionStrategy } from './alias-selection.js';
|
|
5
5
|
import { extractKeyAlias, extractKeyIndex, extractProviderId, getProviderModelId } from './key-parsing.js';
|
|
6
|
+
import { providerSupportsMultimodalRequest } from './multimodal-capability.js';
|
|
6
7
|
import { selectProviderKeyFromCandidatePool } from './tier-selection-select.js';
|
|
7
8
|
import { lookupAntigravityPinnedAliasForSessionId, unpinAntigravitySessionAliasForSessionId } from '../../../conversion/compat/antigravity-session-signature.js';
|
|
8
9
|
const DEFAULT_ANTIGRAVITY_ALIAS_SESSION_COOLDOWN_MS = 5 * 60_000;
|
|
@@ -190,6 +191,9 @@ function applyAntigravityAliasSessionLeases(targets, deps, metadata) {
|
|
|
190
191
|
const strictBinding = strictRequested && Boolean(pinnedLeaseKey);
|
|
191
192
|
const geminiSessionKey = buildScopedSessionKey(sessionKey);
|
|
192
193
|
let preferredGeminiRuntimeKey = pinnedLeaseKey || sessionAliasStore.get(geminiSessionKey);
|
|
194
|
+
if (preferredGeminiRuntimeKey && !preferredGeminiRuntimeKey.includes('::')) {
|
|
195
|
+
preferredGeminiRuntimeKey = buildAntigravityLeaseRuntimeKey(preferredGeminiRuntimeKey);
|
|
196
|
+
}
|
|
193
197
|
if (preferredGeminiRuntimeKey && !pinnedLeaseKey) {
|
|
194
198
|
const lease = leaseStore.get(preferredGeminiRuntimeKey);
|
|
195
199
|
if (lease && lease.sessionKey !== geminiSessionKey && now - lease.lastSeenAt < cooldownMs) {
|
|
@@ -264,7 +268,7 @@ function applyAntigravityAliasSessionLeases(targets, deps, metadata) {
|
|
|
264
268
|
blocked += 1;
|
|
265
269
|
return false;
|
|
266
270
|
});
|
|
267
|
-
return { targets: filtered, blocked, preferredPinned, pinnedStrict };
|
|
271
|
+
return { targets: filtered, blocked, preferredPinned, pinnedStrict, preferredRuntimeKey: preferredGeminiRuntimeKey };
|
|
268
272
|
}
|
|
269
273
|
export function trySelectFromTier(routeName, tier, stickyKey, estimatedTokens, features, deps, options) {
|
|
270
274
|
const { disabledProviders, disabledKeysMap, allowedProviders, disabledModels, requiredProviderKeys } = options;
|
|
@@ -385,6 +389,7 @@ export function trySelectFromTier(routeName, tier, stickyKey, estimatedTokens, f
|
|
|
385
389
|
targets = filtered;
|
|
386
390
|
}
|
|
387
391
|
if (features.hasImageAttachment && routeName === 'multimodal') {
|
|
392
|
+
targets = targets.filter((key) => providerSupportsMultimodalRequest(key, features, deps.providerRegistry));
|
|
388
393
|
const kimiTargets = targets.filter((key) => {
|
|
389
394
|
const modelId = getProviderModelId(key, deps.providerRegistry) ?? '';
|
|
390
395
|
return modelId.trim().toLowerCase() === 'kimi-k2.5';
|
|
@@ -448,6 +453,31 @@ export function trySelectFromTier(routeName, tier, stickyKey, estimatedTokens, f
|
|
|
448
453
|
const candidatesForSelect = avoidAntigravityOnRetry
|
|
449
454
|
? preferNonAntigravityWhenPossible(candidatePool)
|
|
450
455
|
: candidatePool;
|
|
456
|
+
if (leaseResult.preferredRuntimeKey && !leaseResult.pinnedStrict) {
|
|
457
|
+
const preferredCandidates = candidatesForSelect.filter((key) => isAntigravityGeminiModelKey(key, deps) && extractLeaseRuntimeKey(key, deps) === leaseResult.preferredRuntimeKey);
|
|
458
|
+
if (preferredCandidates.length > 0) {
|
|
459
|
+
const preferredProviderKey = selectProviderKeyFromCandidatePool({
|
|
460
|
+
routeName,
|
|
461
|
+
tier,
|
|
462
|
+
stickyKey,
|
|
463
|
+
candidates: preferredCandidates,
|
|
464
|
+
isSafePool,
|
|
465
|
+
deps,
|
|
466
|
+
options,
|
|
467
|
+
contextResult,
|
|
468
|
+
warnRatio,
|
|
469
|
+
excludedKeys,
|
|
470
|
+
isRecoveryAttempt,
|
|
471
|
+
now,
|
|
472
|
+
nowForWeights,
|
|
473
|
+
healthWeightedCfg,
|
|
474
|
+
contextWeightedCfg
|
|
475
|
+
});
|
|
476
|
+
if (preferredProviderKey) {
|
|
477
|
+
return { providerKey: preferredProviderKey, poolTargets: tier.targets, tierId: tier.id };
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
451
481
|
const providerKey = selectProviderKeyFromCandidatePool({
|
|
452
482
|
routeName,
|
|
453
483
|
tier,
|
|
@@ -11,16 +11,14 @@ export declare class VirtualRouterEngine {
|
|
|
11
11
|
private routing;
|
|
12
12
|
private readonly providerRegistry;
|
|
13
13
|
private readonly healthManager;
|
|
14
|
-
private
|
|
14
|
+
private get providerCooldowns();
|
|
15
15
|
private loadBalancer;
|
|
16
16
|
private classifier;
|
|
17
17
|
private readonly contextAdvisor;
|
|
18
18
|
private contextRouting;
|
|
19
|
-
private
|
|
20
|
-
private
|
|
21
|
-
private
|
|
22
|
-
private readonly antigravitySessionAliasStore;
|
|
23
|
-
private antigravityAliasReuseCooldownMs;
|
|
19
|
+
private readonly routeAnalytics;
|
|
20
|
+
private stickySessionManager;
|
|
21
|
+
private cooldownManager;
|
|
24
22
|
private antigravityLeasePersistence;
|
|
25
23
|
private readonly debug;
|
|
26
24
|
private healthConfig;
|
|
@@ -30,6 +28,11 @@ export declare class VirtualRouterEngine {
|
|
|
30
28
|
private routingStateStore;
|
|
31
29
|
private routingInstructionState;
|
|
32
30
|
private quotaView?;
|
|
31
|
+
/**
|
|
32
|
+
* Backward-compatible test/debug surface used by existing regression scripts.
|
|
33
|
+
* Keep this as a read-only view over StickySessionManager storage.
|
|
34
|
+
*/
|
|
35
|
+
get antigravitySessionAliasStore(): Map<string, string>;
|
|
33
36
|
constructor(deps?: {
|
|
34
37
|
healthStore?: VirtualRouterHealthStore;
|
|
35
38
|
routingStateStore?: RoutingInstructionStateStore;
|
|
@@ -41,6 +44,7 @@ export declare class VirtualRouterEngine {
|
|
|
41
44
|
quotaView?: ProviderQuotaView | null;
|
|
42
45
|
}): void;
|
|
43
46
|
private parseDirectProviderModel;
|
|
47
|
+
private shouldFallbackDirectModelForMedia;
|
|
44
48
|
initialize(config: VirtualRouterConfig): void;
|
|
45
49
|
route(request: StandardizedRequest | ProcessedRequest, metadata: RouterMetadataInput): {
|
|
46
50
|
target: TargetMetadata;
|
|
@@ -57,6 +61,15 @@ export declare class VirtualRouterEngine {
|
|
|
57
61
|
providers: string[];
|
|
58
62
|
hits: number;
|
|
59
63
|
lastUsedProvider?: string;
|
|
64
|
+
lastHit?: {
|
|
65
|
+
timestampMs: number;
|
|
66
|
+
reason?: string;
|
|
67
|
+
requestTokens?: number;
|
|
68
|
+
selectionPenalty?: number;
|
|
69
|
+
stopMessageActive: boolean;
|
|
70
|
+
stopMessageMode?: "on" | "off" | "auto";
|
|
71
|
+
stopMessageRemaining?: number;
|
|
72
|
+
};
|
|
60
73
|
}>;
|
|
61
74
|
health: import("./types.js").ProviderHealthState[];
|
|
62
75
|
};
|
|
@@ -67,7 +80,7 @@ export declare class VirtualRouterEngine {
|
|
|
67
80
|
private normalizeRouteAlias;
|
|
68
81
|
private validateConfig;
|
|
69
82
|
private selectProvider;
|
|
70
|
-
private
|
|
83
|
+
private resolveSelectionPenalty;
|
|
71
84
|
private providerHealthConfig;
|
|
72
85
|
private resolveStickyKey;
|
|
73
86
|
private resolveSessionScope;
|
|
@@ -105,6 +118,7 @@ export declare class VirtualRouterEngine {
|
|
|
105
118
|
private markProviderCooldown;
|
|
106
119
|
private clearProviderCooldown;
|
|
107
120
|
private isProviderCoolingDown;
|
|
121
|
+
private getProviderCooldownRemainingMs;
|
|
108
122
|
private restoreHealthFromStore;
|
|
109
123
|
private buildHealthSnapshot;
|
|
110
124
|
private persistHealthSnapshot;
|