@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
|
@@ -11,7 +11,6 @@ export class RoutingClassifier {
|
|
|
11
11
|
}
|
|
12
12
|
classify(features) {
|
|
13
13
|
const lastToolCategory = features.lastAssistantToolCategory;
|
|
14
|
-
const webSearchDeclared = features.hasWebSearchToolDeclared === true;
|
|
15
14
|
const webSearchIntent = detectWebSearchIntent(features.userTextSample);
|
|
16
15
|
const localToolContinuation = lastToolCategory === 'read' ||
|
|
17
16
|
lastToolCategory === 'write' ||
|
|
@@ -49,15 +48,10 @@ export class RoutingClassifier {
|
|
|
49
48
|
},
|
|
50
49
|
web_search: {
|
|
51
50
|
// web_search 仅由“当前请求”触发:
|
|
52
|
-
// - 显式声明 web_search 工具;或
|
|
53
51
|
// - 用户输入命中联网搜索意图关键词。
|
|
54
|
-
//
|
|
55
|
-
triggered: !localToolContinuation &&
|
|
56
|
-
reason:
|
|
57
|
-
? 'web_search:tool+intent'
|
|
58
|
-
: webSearchDeclared
|
|
59
|
-
? 'web_search:tool-declared'
|
|
60
|
-
: 'web_search:intent-keyword'
|
|
52
|
+
// 不再使用工具声明或上一轮 websearch 续写来决定路由。
|
|
53
|
+
triggered: !localToolContinuation && webSearchIntent,
|
|
54
|
+
reason: 'web_search:intent-keyword'
|
|
61
55
|
},
|
|
62
56
|
search: {
|
|
63
57
|
// search 路由:仅在上一轮 assistant 使用 search 类工具时继续命中,
|
|
@@ -120,13 +114,15 @@ function detectWebSearchIntent(text) {
|
|
|
120
114
|
return false;
|
|
121
115
|
}
|
|
122
116
|
const normalized = text.toLowerCase();
|
|
117
|
+
if (isNegativeWebSearchContext(normalized, text)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
123
120
|
const directKeywords = [
|
|
124
121
|
'web search',
|
|
125
122
|
'web_search',
|
|
126
123
|
'websearch',
|
|
127
124
|
'search the web',
|
|
128
125
|
'internet search',
|
|
129
|
-
'search online',
|
|
130
126
|
'搜索网页',
|
|
131
127
|
'联网搜索',
|
|
132
128
|
'上网搜索',
|
|
@@ -139,21 +135,43 @@ function detectWebSearchIntent(text) {
|
|
|
139
135
|
return true;
|
|
140
136
|
}
|
|
141
137
|
const enVerb = ['search', 'find', 'lookup', 'look up', 'google'];
|
|
142
|
-
const enNoun = ['web', 'internet', 'online', '
|
|
138
|
+
const enNoun = ['web', 'internet', 'online', 'google', 'bing'];
|
|
143
139
|
const hasEnVerb = enVerb.some((keyword) => normalized.includes(keyword));
|
|
144
140
|
const hasEnNoun = enNoun.some((keyword) => normalized.includes(keyword));
|
|
145
141
|
if (hasEnVerb && hasEnNoun) {
|
|
146
142
|
return true;
|
|
147
143
|
}
|
|
148
|
-
const zhVerb = ['搜索', '查找', '
|
|
149
|
-
const zhNoun = ['网络', '联网', '网页', '
|
|
144
|
+
const zhVerb = ['搜索', '查找', '搜', '上网查', '上网搜', '联网查', '联网搜'];
|
|
145
|
+
const zhNoun = ['网络', '联网', '网页', '网上', '互联网', '谷歌', '百度'];
|
|
150
146
|
const hasZhVerb = zhVerb.some((keyword) => text.includes(keyword));
|
|
151
147
|
const hasZhNoun = zhNoun.some((keyword) => text.includes(keyword));
|
|
152
|
-
if (text.includes('上网') || (
|
|
148
|
+
if ((text.includes('上网') || text.includes('联网')) && (text.includes('搜') || text.includes('查'))) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
if (hasZhVerb && hasZhNoun) {
|
|
153
152
|
return true;
|
|
154
153
|
}
|
|
155
154
|
return false;
|
|
156
155
|
}
|
|
156
|
+
function isNegativeWebSearchContext(normalized, originalText) {
|
|
157
|
+
const englishPatterns = [
|
|
158
|
+
/prefer\s+resources?\s+over\s+web[\s_-]?search/u,
|
|
159
|
+
/prefer[\s\S]{0,40}web[\s_-]?search/u,
|
|
160
|
+
/do\s+not[\s\S]{0,20}web[\s_-]?search/u,
|
|
161
|
+
/don't[\s\S]{0,20}web[\s_-]?search/u,
|
|
162
|
+
/without[\s\S]{0,20}web[\s_-]?search/u,
|
|
163
|
+
/cannot[\s\S]{0,20}web[\s_-]?search/u
|
|
164
|
+
];
|
|
165
|
+
if (englishPatterns.some((pattern) => pattern.test(normalized))) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
const chinesePatterns = [
|
|
169
|
+
/不能.{0,20}(上网|联网|web[_ -]?search|搜索网页)/u,
|
|
170
|
+
/不要.{0,20}(上网|联网|web[_ -]?search|搜索网页)/u,
|
|
171
|
+
/避免.{0,20}(上网|联网|web[_ -]?search|搜索网页)/u
|
|
172
|
+
];
|
|
173
|
+
return chinesePatterns.some((pattern) => pattern.test(originalText));
|
|
174
|
+
}
|
|
157
175
|
function normalizeList(source, fallback) {
|
|
158
176
|
if (!source || source.length === 0) {
|
|
159
177
|
return fallback;
|
|
@@ -233,11 +233,11 @@ export function recordAntigravitySessionLease(options) {
|
|
|
233
233
|
return;
|
|
234
234
|
}
|
|
235
235
|
// Bind sessionKey → alias runtimeKey so subsequent routing will prefer this alias.
|
|
236
|
-
options.sessionAliasStore.set(scopedSessionKey,
|
|
236
|
+
options.sessionAliasStore.set(scopedSessionKey, runtimeKey);
|
|
237
237
|
try {
|
|
238
238
|
options.debug?.log?.('[virtual-router][antigravity-session-binding] commit', {
|
|
239
239
|
sessionKey: scopedSessionKey,
|
|
240
|
-
runtimeKey
|
|
240
|
+
runtimeKey,
|
|
241
241
|
prev: existing?.sessionKey ?? null
|
|
242
242
|
});
|
|
243
243
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cooldown Manager Module
|
|
3
|
+
*
|
|
4
|
+
* Manages provider cooldown TTLs and health snapshot persistence.
|
|
5
|
+
*/
|
|
6
|
+
import type { VirtualRouterHealthSnapshot, VirtualRouterHealthStore, ProviderHealthConfig } from '../types.js';
|
|
7
|
+
export declare class CooldownManager {
|
|
8
|
+
private providerCooldowns;
|
|
9
|
+
private healthStore?;
|
|
10
|
+
private healthConfig;
|
|
11
|
+
private quotaView?;
|
|
12
|
+
constructor(deps?: {
|
|
13
|
+
healthStore?: VirtualRouterHealthStore;
|
|
14
|
+
healthConfig?: ProviderHealthConfig | null;
|
|
15
|
+
quotaView?: (providerKey: string) => {
|
|
16
|
+
selectionPenalty?: number;
|
|
17
|
+
} | undefined;
|
|
18
|
+
});
|
|
19
|
+
updateDeps(deps: {
|
|
20
|
+
healthStore?: VirtualRouterHealthStore | null;
|
|
21
|
+
healthConfig?: ProviderHealthConfig | null;
|
|
22
|
+
quotaView?: (providerKey: string) => {
|
|
23
|
+
selectionPenalty?: number;
|
|
24
|
+
} | undefined | null;
|
|
25
|
+
}): void;
|
|
26
|
+
markProviderCooldown(providerKey: string, cooldownMs: number | undefined): void;
|
|
27
|
+
clearProviderCooldown(providerKey: string): void;
|
|
28
|
+
isProviderCoolingDown(providerKey: string): boolean;
|
|
29
|
+
restoreHealthFromStore(): void;
|
|
30
|
+
buildHealthSnapshot(): VirtualRouterHealthSnapshot;
|
|
31
|
+
persistHealthSnapshot(): void;
|
|
32
|
+
clearAllCooldowns(): void;
|
|
33
|
+
getCooldownMap(): Map<string, number>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cooldown Manager Module
|
|
3
|
+
*
|
|
4
|
+
* Manages provider cooldown TTLs and health snapshot persistence.
|
|
5
|
+
*/
|
|
6
|
+
export class CooldownManager {
|
|
7
|
+
providerCooldowns = new Map();
|
|
8
|
+
healthStore;
|
|
9
|
+
healthConfig = null;
|
|
10
|
+
quotaView;
|
|
11
|
+
constructor(deps) {
|
|
12
|
+
if (deps?.healthStore)
|
|
13
|
+
this.healthStore = deps.healthStore;
|
|
14
|
+
if (deps?.healthConfig !== undefined)
|
|
15
|
+
this.healthConfig = deps.healthConfig;
|
|
16
|
+
if (deps?.quotaView)
|
|
17
|
+
this.quotaView = deps.quotaView;
|
|
18
|
+
}
|
|
19
|
+
updateDeps(deps) {
|
|
20
|
+
if ('healthStore' in deps)
|
|
21
|
+
this.healthStore = deps.healthStore ?? undefined;
|
|
22
|
+
if ('healthConfig' in deps)
|
|
23
|
+
this.healthConfig = deps.healthConfig ?? null;
|
|
24
|
+
if ('quotaView' in deps) {
|
|
25
|
+
const prevQuotaEnabled = Boolean(this.quotaView);
|
|
26
|
+
this.quotaView = deps.quotaView ?? undefined;
|
|
27
|
+
const nextQuotaEnabled = Boolean(this.quotaView);
|
|
28
|
+
// When quotaView is enabled, cooldown must be driven by quotaView only.
|
|
29
|
+
if (!prevQuotaEnabled && nextQuotaEnabled) {
|
|
30
|
+
this.providerCooldowns.clear();
|
|
31
|
+
}
|
|
32
|
+
else if (prevQuotaEnabled && !nextQuotaEnabled) {
|
|
33
|
+
this.providerCooldowns.clear();
|
|
34
|
+
this.restoreHealthFromStore();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
markProviderCooldown(providerKey, cooldownMs) {
|
|
39
|
+
if (!providerKey)
|
|
40
|
+
return;
|
|
41
|
+
const ttl = typeof cooldownMs === 'number' ? Math.round(cooldownMs) : Number.NaN;
|
|
42
|
+
if (!Number.isFinite(ttl) || ttl <= 0)
|
|
43
|
+
return;
|
|
44
|
+
this.providerCooldowns.set(providerKey, Date.now() + ttl);
|
|
45
|
+
this.persistHealthSnapshot();
|
|
46
|
+
}
|
|
47
|
+
clearProviderCooldown(providerKey) {
|
|
48
|
+
if (!providerKey)
|
|
49
|
+
return;
|
|
50
|
+
if (this.providerCooldowns.delete(providerKey)) {
|
|
51
|
+
this.persistHealthSnapshot();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
isProviderCoolingDown(providerKey) {
|
|
55
|
+
if (!providerKey)
|
|
56
|
+
return false;
|
|
57
|
+
const expiry = this.providerCooldowns.get(providerKey);
|
|
58
|
+
if (!expiry)
|
|
59
|
+
return false;
|
|
60
|
+
if (Date.now() >= expiry) {
|
|
61
|
+
this.providerCooldowns.delete(providerKey);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
restoreHealthFromStore() {
|
|
67
|
+
if (!this.healthStore || typeof this.healthStore.loadInitialSnapshot !== 'function')
|
|
68
|
+
return;
|
|
69
|
+
// When quotaView is enabled, health/cooldown must be driven by quotaView only.
|
|
70
|
+
if (this.quotaView)
|
|
71
|
+
return;
|
|
72
|
+
let snapshot = null;
|
|
73
|
+
try {
|
|
74
|
+
snapshot = this.healthStore.loadInitialSnapshot();
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
snapshot = null;
|
|
78
|
+
}
|
|
79
|
+
if (!snapshot)
|
|
80
|
+
return;
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
const byKey = new Map();
|
|
83
|
+
for (const entry of snapshot.cooldowns || []) {
|
|
84
|
+
if (!entry?.providerKey || !Number.isFinite(entry.cooldownExpiresAt) || entry.cooldownExpiresAt <= now)
|
|
85
|
+
continue;
|
|
86
|
+
byKey.set(entry.providerKey, entry);
|
|
87
|
+
this.providerCooldowns.set(entry.providerKey, entry.cooldownExpiresAt);
|
|
88
|
+
}
|
|
89
|
+
// Note: Provider health manager's state is separate; we only restore local cooldowns.
|
|
90
|
+
}
|
|
91
|
+
buildHealthSnapshot() {
|
|
92
|
+
const cooldowns = [];
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
for (const [providerKey, expiry] of this.providerCooldowns.entries()) {
|
|
95
|
+
if (!expiry || expiry <= now)
|
|
96
|
+
continue;
|
|
97
|
+
cooldowns.push({ providerKey, cooldownExpiresAt: expiry });
|
|
98
|
+
}
|
|
99
|
+
return { providers: [], cooldowns }; // providers part handled by health manager
|
|
100
|
+
}
|
|
101
|
+
persistHealthSnapshot() {
|
|
102
|
+
if (!this.healthStore || typeof this.healthStore.persistSnapshot !== 'function')
|
|
103
|
+
return;
|
|
104
|
+
try {
|
|
105
|
+
const snapshot = this.buildHealthSnapshot();
|
|
106
|
+
this.healthStore.persistSnapshot(snapshot);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// persistence failure does not affect routing
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
clearAllCooldowns() {
|
|
113
|
+
this.providerCooldowns.clear();
|
|
114
|
+
}
|
|
115
|
+
getCooldownMap() {
|
|
116
|
+
return this.providerCooldowns;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route Analytics Module
|
|
3
|
+
*
|
|
4
|
+
* Route statistics and hit tracking extracted from VirtualRouterEngine.
|
|
5
|
+
*/
|
|
6
|
+
import type { RouterMetadataInput } from '../types.js';
|
|
7
|
+
import type { VirtualRouterHitRecord } from '../engine-logging.js';
|
|
8
|
+
export interface RouteLastHit {
|
|
9
|
+
timestampMs: number;
|
|
10
|
+
reason?: string;
|
|
11
|
+
requestTokens?: number;
|
|
12
|
+
selectionPenalty?: number;
|
|
13
|
+
stopMessageActive: boolean;
|
|
14
|
+
stopMessageMode?: 'on' | 'off' | 'auto';
|
|
15
|
+
stopMessageRemaining?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface RouteStats {
|
|
18
|
+
hits: number;
|
|
19
|
+
lastProvider: string;
|
|
20
|
+
lastHit: RouteLastHit;
|
|
21
|
+
}
|
|
22
|
+
export declare class RouteAnalytics {
|
|
23
|
+
private routeStats;
|
|
24
|
+
incrementRouteStat(routeName: string, providerKey: string, hitRecord: VirtualRouterHitRecord): void;
|
|
25
|
+
getRouteStats(routeName: string): RouteStats | undefined;
|
|
26
|
+
getAllRouteStats(): Map<string, RouteStats>;
|
|
27
|
+
extractExcludedProviderKeySet(metadata: RouterMetadataInput | undefined): Set<string>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route Analytics Module
|
|
3
|
+
*
|
|
4
|
+
* Route statistics and hit tracking extracted from VirtualRouterEngine.
|
|
5
|
+
*/
|
|
6
|
+
export class RouteAnalytics {
|
|
7
|
+
routeStats = new Map();
|
|
8
|
+
incrementRouteStat(routeName, providerKey, hitRecord) {
|
|
9
|
+
const nextLastHit = {
|
|
10
|
+
timestampMs: hitRecord.timestampMs,
|
|
11
|
+
...(hitRecord.hitReason ? { reason: hitRecord.hitReason } : {}),
|
|
12
|
+
...(typeof hitRecord.requestTokens === 'number' ? { requestTokens: hitRecord.requestTokens } : {}),
|
|
13
|
+
...(typeof hitRecord.selectionPenalty === 'number' ? { selectionPenalty: hitRecord.selectionPenalty } : {}),
|
|
14
|
+
stopMessageActive: hitRecord.stopMessage.active,
|
|
15
|
+
...(hitRecord.stopMessage.mode !== 'unset' ? { stopMessageMode: hitRecord.stopMessage.mode } : {}),
|
|
16
|
+
...(hitRecord.stopMessage.remaining >= 0 ? { stopMessageRemaining: hitRecord.stopMessage.remaining } : {})
|
|
17
|
+
};
|
|
18
|
+
if (!this.routeStats.has(routeName)) {
|
|
19
|
+
this.routeStats.set(routeName, { hits: 1, lastProvider: providerKey, lastHit: nextLastHit });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const stats = this.routeStats.get(routeName);
|
|
23
|
+
stats.hits += 1;
|
|
24
|
+
stats.lastProvider = providerKey;
|
|
25
|
+
stats.lastHit = nextLastHit;
|
|
26
|
+
}
|
|
27
|
+
getRouteStats(routeName) {
|
|
28
|
+
return this.routeStats.get(routeName);
|
|
29
|
+
}
|
|
30
|
+
getAllRouteStats() {
|
|
31
|
+
return this.routeStats;
|
|
32
|
+
}
|
|
33
|
+
extractExcludedProviderKeySet(metadata) {
|
|
34
|
+
if (!metadata)
|
|
35
|
+
return new Set();
|
|
36
|
+
const raw = metadata.excludedProviderKeys;
|
|
37
|
+
if (!Array.isArray(raw) || raw.length === 0)
|
|
38
|
+
return new Set();
|
|
39
|
+
const normalized = raw
|
|
40
|
+
.map((value) => (typeof value === 'string' ? value.trim() : ''))
|
|
41
|
+
.filter((value) => Boolean(value));
|
|
42
|
+
return new Set(normalized);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_ROUTE, VirtualRouterError, VirtualRouterErrorCode } from '../../types.js';
|
|
2
|
-
import { extractExcludedProviderKeySet, extractProviderId } from '../../engine-selection/key-parsing.js';
|
|
2
|
+
import { extractExcludedProviderKeySet, extractKeyAlias, extractKeyIndex, extractProviderId, getProviderModelId } from '../../engine-selection/key-parsing.js';
|
|
3
|
+
import { providerSupportsMultimodalRequest } from '../../engine-selection/multimodal-capability.js';
|
|
3
4
|
import { trySelectFromTier } from '../../engine-selection/tier-selection.js';
|
|
4
5
|
import { resolveInstructionTarget } from '../../engine-selection/instruction-target.js';
|
|
5
6
|
import { filterCandidatesByRoutingState } from '../../engine-selection/routing-state-filter.js';
|
|
@@ -49,7 +50,8 @@ export function selectProviderImpl(requestedRoute, metadata, classification, fea
|
|
|
49
50
|
stickyResolution = resolveInstructionTarget(state.stickyTarget, deps.providerRegistry);
|
|
50
51
|
if (stickyResolution && stickyResolution.mode === 'exact') {
|
|
51
52
|
const stickyKey = stickyResolution.keys[0];
|
|
52
|
-
if ((
|
|
53
|
+
if (stickyProviderMatchesRequestCapabilities(stickyKey, requestedRoute, classification, features, deps.routing, deps.providerRegistry) &&
|
|
54
|
+
(deps.quotaView ? true : deps.healthManager.isAvailable(stickyKey)) &&
|
|
53
55
|
!excludedProviderKeys.has(stickyKey) &&
|
|
54
56
|
!deps.isProviderCoolingDown(stickyKey) &&
|
|
55
57
|
isAllowedByQuota(stickyKey)) {
|
|
@@ -62,7 +64,8 @@ export function selectProviderImpl(requestedRoute, metadata, classification, fea
|
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
if (stickyResolution && stickyResolution.mode === 'filter' && stickyResolution.keys.length > 0) {
|
|
65
|
-
const liveKeys = stickyResolution.keys.filter((key) => (
|
|
67
|
+
const liveKeys = stickyResolution.keys.filter((key) => stickyProviderMatchesRequestCapabilities(key, requestedRoute, classification, features, deps.routing, deps.providerRegistry) &&
|
|
68
|
+
(deps.quotaView ? true : deps.healthManager.isAvailable(key)) &&
|
|
66
69
|
!excludedProviderKeys.has(key) &&
|
|
67
70
|
!deps.isProviderCoolingDown(key) &&
|
|
68
71
|
isAllowedByQuota(key));
|
|
@@ -175,9 +178,69 @@ export function selectProviderImpl(requestedRoute, metadata, classification, fea
|
|
|
175
178
|
allowAliasRotation
|
|
176
179
|
});
|
|
177
180
|
}
|
|
181
|
+
function stickyProviderMatchesRequestCapabilities(providerKey, requestedRoute, classification, features, routing, providerRegistry) {
|
|
182
|
+
if (!providerKey) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
if (features.hasImageAttachment) {
|
|
186
|
+
const supportsImageRoute = routeTargetsIncludeProvider(routing, 'multimodal', providerKey) ||
|
|
187
|
+
routeTargetsIncludeProvider(routing, 'vision', providerKey);
|
|
188
|
+
if (!supportsImageRoute) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
if (!providerSupportsMultimodalRequest(providerKey, features, providerRegistry)) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (requestRequiresSearchRoute(requestedRoute, classification, features)) {
|
|
196
|
+
const supportsSearchRoute = routeTargetsIncludeProvider(routing, 'web_search', providerKey) ||
|
|
197
|
+
routeTargetsIncludeProvider(routing, 'search', providerKey);
|
|
198
|
+
if (!supportsSearchRoute) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
function requestRequiresSearchRoute(requestedRoute, classification, features) {
|
|
205
|
+
const normalizedRequestedRoute = normalizeRouteAlias(requestedRoute || DEFAULT_ROUTE);
|
|
206
|
+
const normalizedClassifiedRoute = normalizeRouteAlias(classification.routeName || DEFAULT_ROUTE);
|
|
207
|
+
if (normalizedRequestedRoute === 'web_search' || normalizedRequestedRoute === 'search') {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
if (normalizedClassifiedRoute === 'web_search' || normalizedClassifiedRoute === 'search') {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
const candidates = Array.isArray(classification.candidates) ? classification.candidates : [];
|
|
214
|
+
if (candidates.some((route) => route === 'web_search' || route === 'search')) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
if (features.hasWebSearchToolDeclared === true) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
return features.metadata?.serverToolRequired === true;
|
|
221
|
+
}
|
|
222
|
+
function routeTargetsIncludeProvider(routing, routeName, providerKey) {
|
|
223
|
+
const pools = routing[routeName];
|
|
224
|
+
if (!Array.isArray(pools)) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
for (const pool of pools) {
|
|
228
|
+
if (!Array.isArray(pool.targets)) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (pool.targets.includes(providerKey)) {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
function isFinitePositiveNumber(value) {
|
|
238
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0;
|
|
239
|
+
}
|
|
178
240
|
function selectFromCandidates(routes, metadata, classification, features, state, deps, options) {
|
|
179
241
|
const allowedProviders = new Set(state.allowedProviders);
|
|
180
242
|
const disabledProviders = new Set(state.disabledProviders);
|
|
243
|
+
const excludedProviderKeys = extractExcludedProviderKeySet(features.metadata);
|
|
181
244
|
const disabledKeysMap = new Map(Array.from(state.disabledKeys.entries()).map(([provider, keys]) => [
|
|
182
245
|
provider,
|
|
183
246
|
new Set(Array.from(keys).map((k) => (typeof k === 'string' ? k : k + 1)))
|
|
@@ -187,6 +250,89 @@ function selectFromCandidates(routes, metadata, classification, features, state,
|
|
|
187
250
|
const attempted = [];
|
|
188
251
|
const visitedRoutes = new Set();
|
|
189
252
|
const routeQueue = initializeRouteQueue(routes);
|
|
253
|
+
const healthSnapshotByProviderKey = new Map(deps.healthManager.getSnapshot().map((entry) => [entry.providerKey, entry]));
|
|
254
|
+
let minRecoverableCooldownMs;
|
|
255
|
+
const recoverableCooldownHints = [];
|
|
256
|
+
const recordRecoverableCooldown = (providerKey, waitMsRaw, source) => {
|
|
257
|
+
const waitMs = Math.max(1, Math.floor(waitMsRaw));
|
|
258
|
+
if (!isFinitePositiveNumber(waitMs)) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (!Number.isFinite(minRecoverableCooldownMs) || waitMs < minRecoverableCooldownMs) {
|
|
262
|
+
minRecoverableCooldownMs = waitMs;
|
|
263
|
+
}
|
|
264
|
+
const existing = recoverableCooldownHints.find((item) => item.providerKey === providerKey && item.source === source);
|
|
265
|
+
if (!existing) {
|
|
266
|
+
recoverableCooldownHints.push({ providerKey, waitMs, source });
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (waitMs < existing.waitMs) {
|
|
270
|
+
existing.waitMs = waitMs;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
const collectRecoverableCooldownForKey = (providerKey) => {
|
|
274
|
+
const nowMs = Date.now();
|
|
275
|
+
if (deps.quotaView) {
|
|
276
|
+
const entry = deps.quotaView(providerKey);
|
|
277
|
+
if (!entry) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (isFinitePositiveNumber(entry.blacklistUntil) && entry.blacklistUntil > nowMs) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (isFinitePositiveNumber(entry.cooldownUntil) && entry.cooldownUntil > nowMs) {
|
|
284
|
+
recordRecoverableCooldown(providerKey, entry.cooldownUntil - nowMs, 'quota.cooldown');
|
|
285
|
+
}
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (typeof deps.getProviderCooldownRemainingMs === 'function') {
|
|
289
|
+
const localCooldownMs = deps.getProviderCooldownRemainingMs(providerKey);
|
|
290
|
+
if (isFinitePositiveNumber(localCooldownMs)) {
|
|
291
|
+
recordRecoverableCooldown(providerKey, localCooldownMs, 'router.cooldown');
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const healthState = healthSnapshotByProviderKey.get(providerKey);
|
|
295
|
+
if (healthState && isFinitePositiveNumber(healthState.cooldownExpiresAt) && healthState.cooldownExpiresAt > nowMs) {
|
|
296
|
+
recordRecoverableCooldown(providerKey, healthState.cooldownExpiresAt - nowMs, 'health.cooldown');
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
const isEligibleTargetForCurrentAttempt = (providerKey) => {
|
|
300
|
+
if (!providerKey || excludedProviderKeys.has(providerKey)) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
if (options.requiredProviderKeys && options.requiredProviderKeys.size > 0 && !options.requiredProviderKeys.has(providerKey)) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
const providerId = extractProviderId(providerKey);
|
|
307
|
+
if (!providerId) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
if (allowedProviders.size > 0 && !allowedProviders.has(providerId)) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
if (disabledProviders.has(providerId)) {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
const disabledKeys = disabledKeysMap.get(providerId);
|
|
317
|
+
if (disabledKeys && disabledKeys.size > 0) {
|
|
318
|
+
const keyAlias = extractKeyAlias(providerKey);
|
|
319
|
+
const keyIndex = extractKeyIndex(providerKey);
|
|
320
|
+
if (keyAlias && disabledKeys.has(keyAlias)) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
if (keyIndex !== undefined && disabledKeys.has(keyIndex + 1)) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const disabledModelSet = disabledModels.get(providerId);
|
|
328
|
+
if (disabledModelSet && disabledModelSet.size > 0) {
|
|
329
|
+
const modelId = getProviderModelId(providerKey, deps.providerRegistry);
|
|
330
|
+
if (modelId && disabledModelSet.has(modelId)) {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return true;
|
|
335
|
+
};
|
|
190
336
|
const estimatedTokens = typeof features.estimatedTokens === 'number' && Number.isFinite(features.estimatedTokens)
|
|
191
337
|
? Math.max(0, features.estimatedTokens)
|
|
192
338
|
: 0;
|
|
@@ -218,8 +364,23 @@ function selectFromCandidates(routes, metadata, classification, features, state,
|
|
|
218
364
|
if (failureHint) {
|
|
219
365
|
attempted.push(failureHint);
|
|
220
366
|
}
|
|
367
|
+
if (Array.isArray(poolTier.targets) && poolTier.targets.length > 0) {
|
|
368
|
+
for (const providerKey of poolTier.targets) {
|
|
369
|
+
if (!isEligibleTargetForCurrentAttempt(providerKey)) {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
collectRecoverableCooldownForKey(providerKey);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
221
375
|
}
|
|
222
376
|
}
|
|
223
377
|
const requestedRoute = normalizeRouteAlias(classification.routeName || DEFAULT_ROUTE);
|
|
224
|
-
|
|
378
|
+
const details = { routeName: requestedRoute, attempted };
|
|
379
|
+
if (isFinitePositiveNumber(minRecoverableCooldownMs)) {
|
|
380
|
+
details.minRecoverableCooldownMs = Math.floor(minRecoverableCooldownMs);
|
|
381
|
+
details.recoverableCooldownHints = recoverableCooldownHints
|
|
382
|
+
.sort((a, b) => a.waitMs - b.waitMs)
|
|
383
|
+
.slice(0, 8);
|
|
384
|
+
}
|
|
385
|
+
throw new VirtualRouterError(`All providers unavailable for route ${requestedRoute}`, VirtualRouterErrorCode.PROVIDER_NOT_AVAILABLE, details);
|
|
225
386
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sticky Session Manager Module
|
|
3
|
+
*
|
|
4
|
+
* Sticky session and alias lease management extracted from VirtualRouterEngine.
|
|
5
|
+
*/
|
|
6
|
+
export interface AliasLease {
|
|
7
|
+
sessionKey: string;
|
|
8
|
+
lastSeenAt: number;
|
|
9
|
+
}
|
|
10
|
+
export declare class StickySessionManager {
|
|
11
|
+
private aliasQueueStore;
|
|
12
|
+
private antigravityAliasLeaseStore;
|
|
13
|
+
private antigravitySessionAliasStore;
|
|
14
|
+
private antigravityAliasReuseCooldownMs;
|
|
15
|
+
constructor(aliasReuseCooldownMs?: number);
|
|
16
|
+
getAliasQueue(alias: string): string[] | undefined;
|
|
17
|
+
setAliasQueue(alias: string, queue: string[]): void;
|
|
18
|
+
getAliasLease(alias: string): AliasLease | undefined;
|
|
19
|
+
setAliasLease(alias: string, lease: AliasLease): void;
|
|
20
|
+
getSessionAlias(sessionKey: string): string | undefined;
|
|
21
|
+
setSessionAlias(sessionKey: string, alias: string): void;
|
|
22
|
+
getAliasReuseCooldownMs(): number;
|
|
23
|
+
hydrateFromStore(store: Map<string, AliasLease>): void;
|
|
24
|
+
getAllStores(): {
|
|
25
|
+
aliasQueueStore: Map<string, string[]>;
|
|
26
|
+
aliasLeaseStore: Map<string, AliasLease>;
|
|
27
|
+
sessionAliasStore: Map<string, string>;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sticky Session Manager Module
|
|
3
|
+
*
|
|
4
|
+
* Sticky session and alias lease management extracted from VirtualRouterEngine.
|
|
5
|
+
*/
|
|
6
|
+
export class StickySessionManager {
|
|
7
|
+
aliasQueueStore = new Map();
|
|
8
|
+
antigravityAliasLeaseStore = new Map();
|
|
9
|
+
antigravitySessionAliasStore = new Map();
|
|
10
|
+
antigravityAliasReuseCooldownMs = 5 * 60_000;
|
|
11
|
+
constructor(aliasReuseCooldownMs) {
|
|
12
|
+
if (typeof aliasReuseCooldownMs === 'number' && aliasReuseCooldownMs > 0) {
|
|
13
|
+
this.antigravityAliasReuseCooldownMs = aliasReuseCooldownMs;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
// Alias queue management
|
|
17
|
+
getAliasQueue(alias) {
|
|
18
|
+
return this.aliasQueueStore.get(alias);
|
|
19
|
+
}
|
|
20
|
+
setAliasQueue(alias, queue) {
|
|
21
|
+
this.aliasQueueStore.set(alias, queue);
|
|
22
|
+
}
|
|
23
|
+
// Antigravity alias lease
|
|
24
|
+
getAliasLease(alias) {
|
|
25
|
+
return this.antigravityAliasLeaseStore.get(alias);
|
|
26
|
+
}
|
|
27
|
+
setAliasLease(alias, lease) {
|
|
28
|
+
this.antigravityAliasLeaseStore.set(alias, lease);
|
|
29
|
+
}
|
|
30
|
+
// Session alias mapping
|
|
31
|
+
getSessionAlias(sessionKey) {
|
|
32
|
+
return this.antigravitySessionAliasStore.get(sessionKey);
|
|
33
|
+
}
|
|
34
|
+
setSessionAlias(sessionKey, alias) {
|
|
35
|
+
this.antigravitySessionAliasStore.set(sessionKey, alias);
|
|
36
|
+
}
|
|
37
|
+
// Cooldown resolution
|
|
38
|
+
getAliasReuseCooldownMs() {
|
|
39
|
+
return this.antigravityAliasReuseCooldownMs;
|
|
40
|
+
}
|
|
41
|
+
// Hydrate from external store (placeholder for future persistence)
|
|
42
|
+
hydrateFromStore(store) {
|
|
43
|
+
for (const [alias, lease] of store.entries()) {
|
|
44
|
+
this.antigravityAliasLeaseStore.set(alias, lease);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Get all stores for persistence
|
|
48
|
+
getAllStores() {
|
|
49
|
+
return {
|
|
50
|
+
aliasQueueStore: this.aliasQueueStore,
|
|
51
|
+
aliasLeaseStore: this.antigravityAliasLeaseStore,
|
|
52
|
+
sessionAliasStore: this.antigravitySessionAliasStore
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|