@jsonstudio/llms 0.6.467 → 0.6.567
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/claude-thinking-tools.d.ts +15 -0
- package/dist/conversion/compat/actions/claude-thinking-tools.js +72 -0
- package/dist/conversion/compat/profiles/chat-gemini.json +1 -1
- package/dist/conversion/compat/profiles/responses-output2choices-test.json +12 -0
- 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 +15 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +15 -0
- package/dist/conversion/hub/process/chat-process.js +44 -17
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +8 -0
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +13 -8
- package/dist/conversion/hub/tool-session-compat.d.ts +26 -0
- package/dist/conversion/hub/tool-session-compat.js +299 -0
- package/dist/conversion/responses/responses-openai-bridge.d.ts +0 -1
- package/dist/conversion/responses/responses-openai-bridge.js +0 -71
- package/dist/conversion/shared/gemini-tool-utils.js +8 -0
- package/dist/conversion/shared/responses-output-builder.js +6 -68
- package/dist/conversion/shared/tool-governor.js +75 -4
- package/dist/conversion/shared/tool-mapping.js +14 -8
- package/dist/filters/special/request-toolcalls-stringify.js +5 -55
- package/dist/filters/special/request-tools-normalize.js +0 -19
- package/dist/guidance/index.js +25 -9
- package/dist/router/virtual-router/engine-health.d.ts +11 -0
- package/dist/router/virtual-router/engine-health.js +210 -0
- package/dist/router/virtual-router/engine-logging.d.ts +19 -0
- package/dist/router/virtual-router/engine-logging.js +165 -0
- package/dist/router/virtual-router/engine-selection.d.ts +32 -0
- package/dist/router/virtual-router/engine-selection.js +649 -0
- package/dist/router/virtual-router/engine.d.ts +4 -13
- package/dist/router/virtual-router/engine.js +64 -517
- package/dist/router/virtual-router/health-manager.d.ts +23 -0
- package/dist/router/virtual-router/health-manager.js +14 -0
- package/dist/router/virtual-router/message-utils.js +22 -0
- package/dist/router/virtual-router/routing-instructions.d.ts +6 -1
- package/dist/router/virtual-router/routing-instructions.js +129 -3
- package/dist/router/virtual-router/types.d.ts +6 -0
- package/dist/servertool/handlers/gemini-empty-reply-continue.d.ts +1 -0
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +120 -0
- package/dist/servertool/handlers/stop-message-auto.d.ts +1 -0
- package/dist/servertool/handlers/stop-message-auto.js +147 -0
- package/dist/servertool/handlers/vision.js +105 -7
- package/dist/servertool/server-side-tools.d.ts +2 -0
- package/dist/servertool/server-side-tools.js +2 -0
- package/dist/tools/tool-registry.js +195 -4
- package/package.json +1 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
const SERIES_COOLDOWN_DETAIL_KEY = 'virtualRouterSeriesCooldown';
|
|
2
|
+
export function handleProviderFailureImpl(event, healthManager, healthConfig, markProviderCooldown) {
|
|
3
|
+
if (!event || !event.providerKey) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
if (event.affectsHealth === false) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (event.fatal) {
|
|
10
|
+
healthManager.tripProvider(event.providerKey, event.reason, event.cooldownOverrideMs);
|
|
11
|
+
}
|
|
12
|
+
else if (event.reason === 'rate_limit' && event.statusCode === 429) {
|
|
13
|
+
healthManager.cooldownProvider(event.providerKey, event.reason, event.cooldownOverrideMs);
|
|
14
|
+
const ttl = event.cooldownOverrideMs ?? healthConfig.cooldownMs;
|
|
15
|
+
markProviderCooldown(event.providerKey, ttl);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
healthManager.recordFailure(event.providerKey, event.reason);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function mapProviderErrorImpl(event, healthConfig) {
|
|
22
|
+
if (!event || !event.runtime) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const runtime = event.runtime;
|
|
26
|
+
const providerKey = runtime.providerKey ||
|
|
27
|
+
(runtime.target && typeof runtime.target === 'object'
|
|
28
|
+
? runtime.target.providerKey
|
|
29
|
+
: undefined);
|
|
30
|
+
if (!providerKey) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const routeName = runtime.routeName;
|
|
34
|
+
const statusCode = event.status;
|
|
35
|
+
const code = event.code?.toUpperCase() ?? 'ERR_UNKNOWN';
|
|
36
|
+
const stage = event.stage?.toLowerCase() ?? 'unknown';
|
|
37
|
+
const recoverable = event.recoverable === true;
|
|
38
|
+
let fatal = !recoverable;
|
|
39
|
+
let reason = deriveReason(code, stage, statusCode);
|
|
40
|
+
let cooldownOverrideMs;
|
|
41
|
+
if (statusCode === 401 || statusCode === 402 || statusCode === 403 || code.includes('AUTH')) {
|
|
42
|
+
fatal = true;
|
|
43
|
+
cooldownOverrideMs = Math.max(10 * 60_000, healthConfig.fatalCooldownMs ?? 10 * 60_000);
|
|
44
|
+
reason = 'auth';
|
|
45
|
+
}
|
|
46
|
+
else if (statusCode === 429 && !recoverable) {
|
|
47
|
+
fatal = true;
|
|
48
|
+
cooldownOverrideMs = Math.max(10 * 60_000, healthConfig.fatalCooldownMs ?? 10 * 60_000);
|
|
49
|
+
reason = 'rate_limit';
|
|
50
|
+
}
|
|
51
|
+
else if (statusCode && statusCode >= 500) {
|
|
52
|
+
fatal = true;
|
|
53
|
+
cooldownOverrideMs = Math.max(5 * 60_000, healthConfig.fatalCooldownMs ?? 5 * 60_000);
|
|
54
|
+
reason = 'upstream_error';
|
|
55
|
+
}
|
|
56
|
+
else if (stage.includes('compat')) {
|
|
57
|
+
fatal = true;
|
|
58
|
+
cooldownOverrideMs = Math.max(10 * 60_000, healthConfig.fatalCooldownMs ?? 10 * 60_000);
|
|
59
|
+
reason = 'compatibility';
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
providerKey,
|
|
63
|
+
routeName,
|
|
64
|
+
reason,
|
|
65
|
+
fatal,
|
|
66
|
+
statusCode,
|
|
67
|
+
errorCode: code,
|
|
68
|
+
retryable: recoverable,
|
|
69
|
+
affectsHealth: event.affectsHealth !== false,
|
|
70
|
+
cooldownOverrideMs,
|
|
71
|
+
metadata: {
|
|
72
|
+
...event.runtime,
|
|
73
|
+
stage,
|
|
74
|
+
eventCode: code,
|
|
75
|
+
originalMessage: event.message,
|
|
76
|
+
statusCode
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function applySeriesCooldownImpl(event, providerRegistry, healthManager, markProviderCooldown, debug) {
|
|
81
|
+
const seriesDetail = extractSeriesCooldownDetail(event);
|
|
82
|
+
if (!seriesDetail) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const targetKeys = resolveSeriesCooldownTargets(seriesDetail, event, providerRegistry);
|
|
86
|
+
if (targetKeys.length === 0) {
|
|
87
|
+
debug?.log?.('[virtual-router] series cooldown skipped: no targets', {
|
|
88
|
+
providerId: seriesDetail.providerId,
|
|
89
|
+
providerKey: seriesDetail.providerKey,
|
|
90
|
+
series: seriesDetail.series
|
|
91
|
+
});
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const affected = [];
|
|
95
|
+
for (const providerKey of targetKeys) {
|
|
96
|
+
try {
|
|
97
|
+
const profile = providerRegistry.get(providerKey);
|
|
98
|
+
const modelSeries = resolveModelSeries(profile.modelId);
|
|
99
|
+
if (modelSeries !== seriesDetail.series) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
healthManager.tripProvider(providerKey, 'rate_limit', seriesDetail.cooldownMs);
|
|
103
|
+
markProviderCooldown(providerKey, seriesDetail.cooldownMs);
|
|
104
|
+
affected.push(providerKey);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// ignore lookup failures; invalid keys may show up if config drifted
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (affected.length) {
|
|
111
|
+
debug?.log?.('[virtual-router] series cooldown', {
|
|
112
|
+
providerId: seriesDetail.providerId,
|
|
113
|
+
providerKey: seriesDetail.providerKey,
|
|
114
|
+
series: seriesDetail.series,
|
|
115
|
+
cooldownMs: seriesDetail.cooldownMs,
|
|
116
|
+
affected
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function resolveSeriesCooldownTargets(detail, event, providerRegistry) {
|
|
121
|
+
const candidates = new Set();
|
|
122
|
+
const push = (key) => {
|
|
123
|
+
if (typeof key !== 'string') {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const trimmed = key.trim();
|
|
127
|
+
if (!trimmed) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (providerRegistry.has(trimmed)) {
|
|
131
|
+
candidates.add(trimmed);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
push(detail.providerKey);
|
|
135
|
+
const runtimeKey = (event.runtime?.target && typeof event.runtime.target === 'object'
|
|
136
|
+
? event.runtime.target.providerKey
|
|
137
|
+
: undefined) || event.runtime?.providerKey;
|
|
138
|
+
push(runtimeKey);
|
|
139
|
+
return Array.from(candidates);
|
|
140
|
+
}
|
|
141
|
+
function extractSeriesCooldownDetail(event) {
|
|
142
|
+
if (!event || !event.details || typeof event.details !== 'object') {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const raw = event.details[SERIES_COOLDOWN_DETAIL_KEY];
|
|
146
|
+
if (!raw || typeof raw !== 'object') {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const record = raw;
|
|
150
|
+
const providerIdRaw = record.providerId;
|
|
151
|
+
const seriesRaw = record.series;
|
|
152
|
+
const providerKeyRaw = record.providerKey;
|
|
153
|
+
const cooldownRaw = record.cooldownMs;
|
|
154
|
+
if (typeof providerIdRaw !== 'string' || !providerIdRaw.trim()) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const normalizedSeries = typeof seriesRaw === 'string' ? seriesRaw.trim().toLowerCase() : '';
|
|
158
|
+
if (normalizedSeries !== 'gemini-pro' && normalizedSeries !== 'gemini-flash' && normalizedSeries !== 'claude') {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const cooldownMs = typeof cooldownRaw === 'number'
|
|
162
|
+
? cooldownRaw
|
|
163
|
+
: typeof cooldownRaw === 'string'
|
|
164
|
+
? Number.parseFloat(cooldownRaw)
|
|
165
|
+
: Number.NaN;
|
|
166
|
+
if (!Number.isFinite(cooldownMs) || cooldownMs <= 0) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
providerId: providerIdRaw.trim(),
|
|
171
|
+
...(typeof providerKeyRaw === 'string' && providerKeyRaw.trim().length
|
|
172
|
+
? { providerKey: providerKeyRaw.trim() }
|
|
173
|
+
: {}),
|
|
174
|
+
series: normalizedSeries,
|
|
175
|
+
cooldownMs: Math.round(cooldownMs)
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
export function deriveReason(code, stage, statusCode) {
|
|
179
|
+
if (code.includes('RATE') || code.includes('429'))
|
|
180
|
+
return 'rate_limit';
|
|
181
|
+
if (code.includes('AUTH') || statusCode === 401 || statusCode === 403)
|
|
182
|
+
return 'auth';
|
|
183
|
+
if (stage.includes('compat'))
|
|
184
|
+
return 'compatibility';
|
|
185
|
+
if (code.includes('SSE'))
|
|
186
|
+
return 'sse';
|
|
187
|
+
if (code.includes('TIMEOUT') || statusCode === 408 || statusCode === 504)
|
|
188
|
+
return 'timeout';
|
|
189
|
+
if (statusCode && statusCode >= 500)
|
|
190
|
+
return 'upstream_error';
|
|
191
|
+
if (statusCode && statusCode >= 400)
|
|
192
|
+
return 'client_error';
|
|
193
|
+
return 'unknown';
|
|
194
|
+
}
|
|
195
|
+
function resolveModelSeries(modelId) {
|
|
196
|
+
if (!modelId) {
|
|
197
|
+
return 'default';
|
|
198
|
+
}
|
|
199
|
+
const lower = modelId.toLowerCase();
|
|
200
|
+
if (lower.includes('claude') || lower.includes('opus')) {
|
|
201
|
+
return 'claude';
|
|
202
|
+
}
|
|
203
|
+
if (lower.includes('flash')) {
|
|
204
|
+
return 'gemini-flash';
|
|
205
|
+
}
|
|
206
|
+
if (lower.includes('gemini') || lower.includes('pro')) {
|
|
207
|
+
return 'gemini-pro';
|
|
208
|
+
}
|
|
209
|
+
return 'default';
|
|
210
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ClassificationResult, type RoutingFeatures, type RoutingInstructionMode, type VirtualRouterContextRoutingConfig } from './types.js';
|
|
2
|
+
import { ProviderRegistry } from './provider-registry.js';
|
|
3
|
+
type LoggingDeps = {
|
|
4
|
+
providerRegistry: ProviderRegistry;
|
|
5
|
+
contextRouting: VirtualRouterContextRoutingConfig | undefined;
|
|
6
|
+
};
|
|
7
|
+
export declare function formatStickyScope(scope?: string): string | undefined;
|
|
8
|
+
export declare function parseProviderKey(providerKey: string): {
|
|
9
|
+
providerId: string;
|
|
10
|
+
keyAlias?: string;
|
|
11
|
+
modelId?: string;
|
|
12
|
+
} | null;
|
|
13
|
+
export declare function describeTargetProvider(providerKey: string, fallbackModelId?: string): {
|
|
14
|
+
providerLabel: string;
|
|
15
|
+
resolvedModel?: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function buildHitReason(routeUsed: string, providerKey: string, classification: ClassificationResult, features: RoutingFeatures, mode: RoutingInstructionMode | undefined, deps: LoggingDeps): string;
|
|
18
|
+
export declare function formatVirtualRouterHit(routeName: string, poolId: string | undefined, providerKey: string, modelId?: string, hitReason?: string, stickyScope?: string): string;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { DEFAULT_MODEL_CONTEXT_TOKENS, DEFAULT_ROUTE } from './types.js';
|
|
2
|
+
export function formatStickyScope(scope) {
|
|
3
|
+
if (!scope || scope.trim().length === 0) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
const normalized = scope.trim();
|
|
7
|
+
const maxLength = 20;
|
|
8
|
+
if (normalized.length <= maxLength) {
|
|
9
|
+
return normalized;
|
|
10
|
+
}
|
|
11
|
+
const delimiterIndex = normalized.indexOf(':');
|
|
12
|
+
const prefix = delimiterIndex > 0 ? normalized.slice(0, delimiterIndex + 1) : '';
|
|
13
|
+
const body = delimiterIndex > 0 ? normalized.slice(delimiterIndex + 1) : normalized;
|
|
14
|
+
if (body.length <= 8) {
|
|
15
|
+
return `${prefix}${body}`;
|
|
16
|
+
}
|
|
17
|
+
return `${prefix}${body.slice(0, 4)}…${body.slice(-4)}`;
|
|
18
|
+
}
|
|
19
|
+
export function parseProviderKey(providerKey) {
|
|
20
|
+
const trimmed = typeof providerKey === 'string' ? providerKey.trim() : '';
|
|
21
|
+
if (!trimmed) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const parts = trimmed.split('.');
|
|
25
|
+
if (parts.length < 2) {
|
|
26
|
+
return { providerId: trimmed };
|
|
27
|
+
}
|
|
28
|
+
if (parts.length === 2) {
|
|
29
|
+
return { providerId: parts[0], modelId: parts[1] };
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
providerId: parts[0],
|
|
33
|
+
keyAlias: parts[1],
|
|
34
|
+
modelId: parts.slice(2).join('.')
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function describeTargetProvider(providerKey, fallbackModelId) {
|
|
38
|
+
const parsed = parseProviderKey(providerKey);
|
|
39
|
+
if (!parsed) {
|
|
40
|
+
return { providerLabel: providerKey, resolvedModel: fallbackModelId };
|
|
41
|
+
}
|
|
42
|
+
const aliasLabel = parsed.keyAlias ? `${parsed.providerId}[${parsed.keyAlias}]` : parsed.providerId;
|
|
43
|
+
const resolvedModel = parsed.modelId || fallbackModelId;
|
|
44
|
+
return { providerLabel: aliasLabel, resolvedModel };
|
|
45
|
+
}
|
|
46
|
+
function resolveRouteColor(routeName) {
|
|
47
|
+
const map = {
|
|
48
|
+
tools: '\x1b[38;5;214m',
|
|
49
|
+
thinking: '\x1b[34m',
|
|
50
|
+
coding: '\x1b[35m',
|
|
51
|
+
longcontext: '\x1b[38;5;141m',
|
|
52
|
+
web_search: '\x1b[32m',
|
|
53
|
+
search: '\x1b[38;5;34m',
|
|
54
|
+
vision: '\x1b[38;5;207m',
|
|
55
|
+
background: '\x1b[90m'
|
|
56
|
+
};
|
|
57
|
+
return map[routeName] ?? '\x1b[36m';
|
|
58
|
+
}
|
|
59
|
+
function describeContextUsage(providerKey, estimatedTokens, deps) {
|
|
60
|
+
if (typeof estimatedTokens !== 'number' || !Number.isFinite(estimatedTokens) || estimatedTokens <= 0) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
let limit = DEFAULT_MODEL_CONTEXT_TOKENS;
|
|
64
|
+
try {
|
|
65
|
+
const profile = deps.providerRegistry.get(providerKey);
|
|
66
|
+
if (profile?.maxContextTokens && Number.isFinite(profile.maxContextTokens)) {
|
|
67
|
+
limit = profile.maxContextTokens;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
limit = DEFAULT_MODEL_CONTEXT_TOKENS;
|
|
72
|
+
}
|
|
73
|
+
if (!limit || limit <= 0) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
const ratio = estimatedTokens / limit;
|
|
77
|
+
const threshold = deps.contextRouting?.warnRatio ?? 0.9;
|
|
78
|
+
if (ratio < threshold) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
return `${ratio.toFixed(2)}/${Math.round(limit)}`;
|
|
82
|
+
}
|
|
83
|
+
function decorateWithDetail(baseLabel, primaryReason, detail) {
|
|
84
|
+
const normalizedDetail = detail && detail.trim();
|
|
85
|
+
if (!normalizedDetail) {
|
|
86
|
+
return primaryReason || baseLabel;
|
|
87
|
+
}
|
|
88
|
+
if (primaryReason) {
|
|
89
|
+
return `${primaryReason}(${normalizedDetail})`;
|
|
90
|
+
}
|
|
91
|
+
return `${baseLabel}(${normalizedDetail})`;
|
|
92
|
+
}
|
|
93
|
+
export function buildHitReason(routeUsed, providerKey, classification, features, mode, deps) {
|
|
94
|
+
const reasoning = classification.reasoning || '';
|
|
95
|
+
let primary = reasoning.split('|')[0] || '';
|
|
96
|
+
const commandDetail = features.lastAssistantToolLabel;
|
|
97
|
+
const isStickyMode = mode === 'sticky';
|
|
98
|
+
if (isStickyMode &&
|
|
99
|
+
(routeUsed === 'tools' || routeUsed === 'thinking' || routeUsed === 'coding')) {
|
|
100
|
+
primary = '';
|
|
101
|
+
}
|
|
102
|
+
const base = (() => {
|
|
103
|
+
if (routeUsed === 'tools') {
|
|
104
|
+
const label = isStickyMode ? 'sticky' : 'tools';
|
|
105
|
+
return decorateWithDetail(primary || label, primary, commandDetail);
|
|
106
|
+
}
|
|
107
|
+
if (routeUsed === 'thinking') {
|
|
108
|
+
const label = isStickyMode ? 'sticky' : 'thinking';
|
|
109
|
+
return decorateWithDetail(primary || label, primary, commandDetail);
|
|
110
|
+
}
|
|
111
|
+
if (routeUsed === 'coding') {
|
|
112
|
+
const label = isStickyMode ? 'sticky' : 'coding';
|
|
113
|
+
return decorateWithDetail(primary || label, primary, commandDetail);
|
|
114
|
+
}
|
|
115
|
+
if (routeUsed === 'web_search' || routeUsed === 'search') {
|
|
116
|
+
return decorateWithDetail(primary || routeUsed, primary, commandDetail);
|
|
117
|
+
}
|
|
118
|
+
if (routeUsed === DEFAULT_ROUTE && classification.fallback) {
|
|
119
|
+
if (isStickyMode) {
|
|
120
|
+
return primary || 'sticky:default';
|
|
121
|
+
}
|
|
122
|
+
return primary || 'fallback:default';
|
|
123
|
+
}
|
|
124
|
+
if (primary) {
|
|
125
|
+
return primary;
|
|
126
|
+
}
|
|
127
|
+
return routeUsed ? `route:${routeUsed}` : 'route:unknown';
|
|
128
|
+
})();
|
|
129
|
+
const contextDetail = describeContextUsage(providerKey, features.estimatedTokens, deps);
|
|
130
|
+
if (contextDetail) {
|
|
131
|
+
return `${base}|context:${contextDetail}`;
|
|
132
|
+
}
|
|
133
|
+
return base;
|
|
134
|
+
}
|
|
135
|
+
export function formatVirtualRouterHit(routeName, poolId, providerKey, modelId, hitReason, stickyScope) {
|
|
136
|
+
try {
|
|
137
|
+
const now = new Date();
|
|
138
|
+
const hours = String(now.getHours()).padStart(2, '0');
|
|
139
|
+
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
140
|
+
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
141
|
+
const timestamp = `${hours}:${minutes}:${seconds}`;
|
|
142
|
+
const prefixColor = '\x1b[38;5;208m';
|
|
143
|
+
const reset = '\x1b[0m';
|
|
144
|
+
const timeColor = '\x1b[90m';
|
|
145
|
+
const stickyColor = '\x1b[33m';
|
|
146
|
+
const routeColor = resolveRouteColor(routeName);
|
|
147
|
+
const prefix = `${prefixColor}[virtual-router-hit]${reset}`;
|
|
148
|
+
const timeLabel = `${timeColor}${timestamp}${reset}`;
|
|
149
|
+
const { providerLabel, resolvedModel } = describeTargetProvider(providerKey, modelId);
|
|
150
|
+
const routeLabel = poolId ? `${routeName}/${poolId}` : routeName;
|
|
151
|
+
const targetLabel = `${routeLabel} -> ${providerLabel}${resolvedModel ? '.' + resolvedModel : ''}`;
|
|
152
|
+
const stickyText = formatStickyScope(stickyScope);
|
|
153
|
+
const stickyLabel = stickyText ? ` ${stickyColor}[sticky:${stickyText}]${reset}` : '';
|
|
154
|
+
const reasonLabel = hitReason ? ` reason=${hitReason}` : '';
|
|
155
|
+
return `${prefix} ${timeLabel} ${routeColor}${targetLabel}${stickyLabel}${reasonLabel}${reset}`;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
const now = new Date();
|
|
159
|
+
const timestamp = now.toLocaleTimeString('zh-CN', { hour12: false });
|
|
160
|
+
const routeLabel = poolId ? `${routeName}/${poolId}` : routeName;
|
|
161
|
+
const stickyText = formatStickyScope(stickyScope);
|
|
162
|
+
const stickyLabel = stickyText ? ` [sticky:${stickyText}]` : '';
|
|
163
|
+
return `[virtual-router-hit] ${timestamp} ${routeLabel} -> ${providerKey}${modelId ? '.' + modelId : ''}${stickyLabel}${hitReason ? ` reason=${hitReason}` : ''}`;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ClassificationResult, RoutePoolTier, RouterMetadataInput, RoutingFeatures } from './types.js';
|
|
2
|
+
import type { RoutingInstructionState } from './routing-instructions.js';
|
|
3
|
+
import type { ContextAdvisor } from './context-advisor.js';
|
|
4
|
+
import type { RouteLoadBalancer } from './load-balancer.js';
|
|
5
|
+
import type { ProviderHealthManager } from './health-manager.js';
|
|
6
|
+
import type { ProviderRegistry } from './provider-registry.js';
|
|
7
|
+
type SelectionDeps = {
|
|
8
|
+
routing: Record<string, RoutePoolTier[]>;
|
|
9
|
+
providerRegistry: ProviderRegistry;
|
|
10
|
+
healthManager: ProviderHealthManager;
|
|
11
|
+
contextAdvisor: ContextAdvisor;
|
|
12
|
+
loadBalancer: RouteLoadBalancer;
|
|
13
|
+
isProviderCoolingDown: (providerKey: string) => boolean;
|
|
14
|
+
resolveStickyKey: (metadata: RouterMetadataInput) => string | undefined;
|
|
15
|
+
};
|
|
16
|
+
export declare function selectProviderImpl(requestedRoute: string, metadata: RouterMetadataInput, classification: ClassificationResult, features: RoutingFeatures, activeState: RoutingInstructionState, deps: SelectionDeps, options?: {
|
|
17
|
+
routingState?: RoutingInstructionState;
|
|
18
|
+
}): {
|
|
19
|
+
providerKey: string;
|
|
20
|
+
routeUsed: string;
|
|
21
|
+
pool: string[];
|
|
22
|
+
poolId?: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function selectFromStickyPool(stickyKeySet: Set<string>, metadata: RouterMetadataInput, features: RoutingFeatures, state: RoutingInstructionState, deps: SelectionDeps, options: {
|
|
25
|
+
allowAliasRotation?: boolean;
|
|
26
|
+
}): {
|
|
27
|
+
providerKey: string;
|
|
28
|
+
routeUsed: string;
|
|
29
|
+
pool: string[];
|
|
30
|
+
poolId?: string;
|
|
31
|
+
} | null;
|
|
32
|
+
export {};
|