@stackmemoryai/stackmemory 0.5.31 → 0.5.33
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/cli/claude-sm.js +199 -16
- package/dist/cli/claude-sm.js.map +2 -2
- package/dist/cli/commands/context.js +0 -11
- package/dist/cli/commands/context.js.map +2 -2
- package/dist/cli/commands/linear.js +1 -14
- package/dist/cli/commands/linear.js.map +2 -2
- package/dist/cli/commands/login.js +32 -10
- package/dist/cli/commands/login.js.map +2 -2
- package/dist/cli/commands/migrate.js +80 -22
- package/dist/cli/commands/migrate.js.map +2 -2
- package/dist/cli/commands/model.js +533 -0
- package/dist/cli/commands/model.js.map +7 -0
- package/dist/cli/commands/ralph.js +93 -28
- package/dist/cli/commands/ralph.js.map +2 -2
- package/dist/cli/commands/service.js +10 -3
- package/dist/cli/commands/service.js.map +2 -2
- package/dist/cli/commands/skills.js +60 -10
- package/dist/cli/commands/skills.js.map +2 -2
- package/dist/cli/commands/sms-notify.js +342 -22
- package/dist/cli/commands/sms-notify.js.map +3 -3
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +2 -2
- package/dist/core/context/dual-stack-manager.js +23 -7
- package/dist/core/context/dual-stack-manager.js.map +2 -2
- package/dist/core/context/frame-database.js +33 -5
- package/dist/core/context/frame-database.js.map +2 -2
- package/dist/core/context/frame-digest.js +6 -1
- package/dist/core/context/frame-digest.js.map +2 -2
- package/dist/core/context/frame-manager.js +56 -9
- package/dist/core/context/frame-manager.js.map +2 -2
- package/dist/core/context/permission-manager.js +0 -11
- package/dist/core/context/permission-manager.js.map +2 -2
- package/dist/core/context/recursive-context-manager.js +15 -9
- package/dist/core/context/recursive-context-manager.js.map +2 -2
- package/dist/core/context/shared-context-layer.js +0 -11
- package/dist/core/context/shared-context-layer.js.map +2 -2
- package/dist/core/context/validation.js +6 -1
- package/dist/core/context/validation.js.map +2 -2
- package/dist/core/models/fallback-monitor.js +229 -0
- package/dist/core/models/fallback-monitor.js.map +7 -0
- package/dist/core/models/model-router.js +331 -0
- package/dist/core/models/model-router.js.map +7 -0
- package/dist/hooks/claude-code-whatsapp-hook.js +197 -0
- package/dist/hooks/claude-code-whatsapp-hook.js.map +7 -0
- package/dist/hooks/linear-task-picker.js +1 -1
- package/dist/hooks/linear-task-picker.js.map +2 -2
- package/dist/hooks/schemas.js +55 -1
- package/dist/hooks/schemas.js.map +2 -2
- package/dist/hooks/session-summary.js +5 -1
- package/dist/hooks/session-summary.js.map +2 -2
- package/dist/hooks/sms-action-runner.js +12 -1
- package/dist/hooks/sms-action-runner.js.map +2 -2
- package/dist/hooks/sms-notify.js +4 -2
- package/dist/hooks/sms-notify.js.map +2 -2
- package/dist/hooks/sms-webhook.js +23 -2
- package/dist/hooks/sms-webhook.js.map +2 -2
- package/dist/hooks/whatsapp-commands.js +376 -0
- package/dist/hooks/whatsapp-commands.js.map +7 -0
- package/dist/hooks/whatsapp-scheduler.js +317 -0
- package/dist/hooks/whatsapp-scheduler.js.map +7 -0
- package/dist/hooks/whatsapp-sync.js +375 -0
- package/dist/hooks/whatsapp-sync.js.map +7 -0
- package/package.json +2 -3
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { spawn } from "child_process";
|
|
6
|
+
import {
|
|
7
|
+
loadModelRouterConfig,
|
|
8
|
+
buildModelEnv
|
|
9
|
+
} from "./model-router.js";
|
|
10
|
+
const DEFAULT_ERROR_PATTERNS = [
|
|
11
|
+
/rate.?limit/i,
|
|
12
|
+
/429/,
|
|
13
|
+
/too.?many.?requests/i,
|
|
14
|
+
/overloaded/i,
|
|
15
|
+
/capacity/i,
|
|
16
|
+
/temporarily.?unavailable/i,
|
|
17
|
+
/503/,
|
|
18
|
+
/502/,
|
|
19
|
+
/500/,
|
|
20
|
+
/internal.?server.?error/i,
|
|
21
|
+
/timeout/i,
|
|
22
|
+
/ETIMEDOUT/,
|
|
23
|
+
/ESOCKETTIMEDOUT/,
|
|
24
|
+
/ECONNRESET/,
|
|
25
|
+
/ECONNREFUSED/
|
|
26
|
+
];
|
|
27
|
+
class FallbackMonitor {
|
|
28
|
+
config;
|
|
29
|
+
routerConfig = loadModelRouterConfig();
|
|
30
|
+
currentProvider = "anthropic";
|
|
31
|
+
restartCount = 0;
|
|
32
|
+
inFallback = false;
|
|
33
|
+
errorBuffer = "";
|
|
34
|
+
lastErrorTime = 0;
|
|
35
|
+
errorCount = 0;
|
|
36
|
+
constructor(config = {}) {
|
|
37
|
+
this.config = {
|
|
38
|
+
enabled: true,
|
|
39
|
+
maxRestarts: 3,
|
|
40
|
+
restartDelayMs: 2e3,
|
|
41
|
+
errorPatterns: DEFAULT_ERROR_PATTERNS,
|
|
42
|
+
...config
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Check if text contains error patterns that should trigger fallback
|
|
47
|
+
*/
|
|
48
|
+
detectError(text) {
|
|
49
|
+
for (const pattern of this.config.errorPatterns) {
|
|
50
|
+
if (pattern.test(text)) {
|
|
51
|
+
return {
|
|
52
|
+
shouldFallback: true,
|
|
53
|
+
reason: pattern.source
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { shouldFallback: false, reason: "" };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get environment variables for fallback provider
|
|
61
|
+
*/
|
|
62
|
+
getFallbackEnv() {
|
|
63
|
+
const fallbackProvider = this.routerConfig.fallback?.provider || "qwen";
|
|
64
|
+
const providerConfig = this.routerConfig.providers[fallbackProvider];
|
|
65
|
+
if (!providerConfig) {
|
|
66
|
+
console.error(`[fallback] Provider not configured: ${fallbackProvider}`);
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
return buildModelEnv(providerConfig);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Check if fallback is available (has API key)
|
|
73
|
+
*/
|
|
74
|
+
isFallbackAvailable() {
|
|
75
|
+
const fallbackProvider = this.routerConfig.fallback?.provider || "qwen";
|
|
76
|
+
const providerConfig = this.routerConfig.providers[fallbackProvider];
|
|
77
|
+
if (!providerConfig) return false;
|
|
78
|
+
return !!process.env[providerConfig.apiKeyEnv];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Wrap a Claude process with fallback monitoring
|
|
82
|
+
* Returns a function to spawn the process with automatic restart on failure
|
|
83
|
+
*/
|
|
84
|
+
wrapProcess(command, args, options = {}) {
|
|
85
|
+
let currentProcess = null;
|
|
86
|
+
let stopped = false;
|
|
87
|
+
const startProcess = () => {
|
|
88
|
+
const env = { ...process.env, ...options.env };
|
|
89
|
+
if (this.inFallback) {
|
|
90
|
+
const fallbackEnv = this.getFallbackEnv();
|
|
91
|
+
Object.assign(env, fallbackEnv);
|
|
92
|
+
this.currentProvider = this.routerConfig.fallback?.provider || "qwen";
|
|
93
|
+
}
|
|
94
|
+
currentProcess = spawn(command, args, {
|
|
95
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
96
|
+
env,
|
|
97
|
+
cwd: options.cwd
|
|
98
|
+
});
|
|
99
|
+
currentProcess.stdout?.on("data", (data) => {
|
|
100
|
+
const text = data.toString();
|
|
101
|
+
process.stdout.write(data);
|
|
102
|
+
this.checkForErrors(text);
|
|
103
|
+
});
|
|
104
|
+
currentProcess.stderr?.on("data", (data) => {
|
|
105
|
+
const text = data.toString();
|
|
106
|
+
process.stderr.write(data);
|
|
107
|
+
this.checkForErrors(text);
|
|
108
|
+
});
|
|
109
|
+
currentProcess.on("exit", (code, _signal) => {
|
|
110
|
+
if (stopped) return;
|
|
111
|
+
if (code !== 0 && this.shouldRestart()) {
|
|
112
|
+
console.log(
|
|
113
|
+
`
|
|
114
|
+
[fallback] Process exited with code ${code}, restarting with fallback...`
|
|
115
|
+
);
|
|
116
|
+
this.activateFallback("exit_code");
|
|
117
|
+
setTimeout(() => {
|
|
118
|
+
if (!stopped) {
|
|
119
|
+
startProcess();
|
|
120
|
+
}
|
|
121
|
+
}, this.config.restartDelayMs);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
return currentProcess;
|
|
125
|
+
};
|
|
126
|
+
return {
|
|
127
|
+
start: startProcess,
|
|
128
|
+
stop: () => {
|
|
129
|
+
stopped = true;
|
|
130
|
+
currentProcess?.kill();
|
|
131
|
+
},
|
|
132
|
+
isInFallback: () => this.inFallback,
|
|
133
|
+
getCurrentProvider: () => this.currentProvider
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check output text for errors and trigger fallback if needed
|
|
138
|
+
*/
|
|
139
|
+
checkForErrors(text) {
|
|
140
|
+
this.errorBuffer += text;
|
|
141
|
+
if (this.errorBuffer.length > 1e4) {
|
|
142
|
+
this.errorBuffer = this.errorBuffer.slice(-5e3);
|
|
143
|
+
}
|
|
144
|
+
const { shouldFallback, reason } = this.detectError(text);
|
|
145
|
+
if (shouldFallback) {
|
|
146
|
+
const now = Date.now();
|
|
147
|
+
if (now - this.lastErrorTime < 1e3) {
|
|
148
|
+
this.errorCount++;
|
|
149
|
+
} else {
|
|
150
|
+
this.errorCount = 1;
|
|
151
|
+
}
|
|
152
|
+
this.lastErrorTime = now;
|
|
153
|
+
if (this.errorCount >= 2 && !this.inFallback && this.isFallbackAvailable()) {
|
|
154
|
+
console.log(`
|
|
155
|
+
[fallback] Detected error pattern: ${reason}`);
|
|
156
|
+
this.activateFallback(reason);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Activate fallback mode
|
|
162
|
+
*/
|
|
163
|
+
activateFallback(reason) {
|
|
164
|
+
if (this.inFallback) return;
|
|
165
|
+
this.inFallback = true;
|
|
166
|
+
this.restartCount++;
|
|
167
|
+
this.currentProvider = this.routerConfig.fallback?.provider || "qwen";
|
|
168
|
+
console.log(
|
|
169
|
+
`[fallback] Switching to ${this.currentProvider} (reason: ${reason})`
|
|
170
|
+
);
|
|
171
|
+
if (this.config.onFallback) {
|
|
172
|
+
this.config.onFallback(this.currentProvider, reason);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Check if we should restart
|
|
177
|
+
*/
|
|
178
|
+
shouldRestart() {
|
|
179
|
+
return this.config.enabled && this.restartCount < this.config.maxRestarts && this.isFallbackAvailable();
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Reset fallback state
|
|
183
|
+
*/
|
|
184
|
+
reset() {
|
|
185
|
+
this.inFallback = false;
|
|
186
|
+
this.restartCount = 0;
|
|
187
|
+
this.errorCount = 0;
|
|
188
|
+
this.errorBuffer = "";
|
|
189
|
+
this.currentProvider = "anthropic";
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get current status
|
|
193
|
+
*/
|
|
194
|
+
getStatus() {
|
|
195
|
+
return {
|
|
196
|
+
inFallback: this.inFallback,
|
|
197
|
+
currentProvider: this.currentProvider,
|
|
198
|
+
restartCount: this.restartCount,
|
|
199
|
+
fallbackAvailable: this.isFallbackAvailable()
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function spawnWithFallback(command, args, options = {}) {
|
|
204
|
+
const monitor = new FallbackMonitor({
|
|
205
|
+
onFallback: options.onFallback
|
|
206
|
+
});
|
|
207
|
+
const wrapper = monitor.wrapProcess(command, args, options);
|
|
208
|
+
return wrapper.start();
|
|
209
|
+
}
|
|
210
|
+
function getEnvWithFallback() {
|
|
211
|
+
const config = loadModelRouterConfig();
|
|
212
|
+
const env = {};
|
|
213
|
+
if (config.fallback?.enabled) {
|
|
214
|
+
const fallbackProvider = config.providers[config.fallback.provider];
|
|
215
|
+
if (fallbackProvider) {
|
|
216
|
+
env["STACKMEMORY_FALLBACK_PROVIDER"] = config.fallback.provider;
|
|
217
|
+
env["STACKMEMORY_FALLBACK_MODEL"] = fallbackProvider.model;
|
|
218
|
+
env["STACKMEMORY_FALLBACK_URL"] = fallbackProvider.baseUrl || "";
|
|
219
|
+
env["STACKMEMORY_FALLBACK_KEY_ENV"] = fallbackProvider.apiKeyEnv;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return env;
|
|
223
|
+
}
|
|
224
|
+
export {
|
|
225
|
+
FallbackMonitor,
|
|
226
|
+
getEnvWithFallback,
|
|
227
|
+
spawnWithFallback
|
|
228
|
+
};
|
|
229
|
+
//# sourceMappingURL=fallback-monitor.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/core/models/fallback-monitor.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Fallback Monitor - Watches Claude output for errors and triggers automatic fallback\n * Restarts session with Qwen when Claude fails\n */\n\nimport { spawn, ChildProcess } from 'child_process';\nimport {\n loadModelRouterConfig,\n buildModelEnv,\n type ModelProvider,\n} from './model-router.js';\n\nexport interface FallbackMonitorConfig {\n enabled: boolean;\n maxRestarts: number;\n restartDelayMs: number;\n errorPatterns: RegExp[];\n onFallback?: (provider: ModelProvider, reason: string) => void;\n onRestore?: (provider: ModelProvider) => void;\n}\n\nconst DEFAULT_ERROR_PATTERNS = [\n /rate.?limit/i,\n /429/,\n /too.?many.?requests/i,\n /overloaded/i,\n /capacity/i,\n /temporarily.?unavailable/i,\n /503/,\n /502/,\n /500/,\n /internal.?server.?error/i,\n /timeout/i,\n /ETIMEDOUT/,\n /ESOCKETTIMEDOUT/,\n /ECONNRESET/,\n /ECONNREFUSED/,\n];\n\n/**\n * FallbackMonitor watches a Claude process and restarts with fallback on errors\n */\nexport class FallbackMonitor {\n private config: FallbackMonitorConfig;\n private routerConfig = loadModelRouterConfig();\n private currentProvider: ModelProvider = 'anthropic';\n private restartCount = 0;\n private inFallback = false;\n private errorBuffer = '';\n private lastErrorTime = 0;\n private errorCount = 0;\n\n constructor(config: Partial<FallbackMonitorConfig> = {}) {\n this.config = {\n enabled: true,\n maxRestarts: 3,\n restartDelayMs: 2000,\n errorPatterns: DEFAULT_ERROR_PATTERNS,\n ...config,\n };\n }\n\n /**\n * Check if text contains error patterns that should trigger fallback\n */\n detectError(text: string): { shouldFallback: boolean; reason: string } {\n for (const pattern of this.config.errorPatterns) {\n if (pattern.test(text)) {\n return {\n shouldFallback: true,\n reason: pattern.source,\n };\n }\n }\n return { shouldFallback: false, reason: '' };\n }\n\n /**\n * Get environment variables for fallback provider\n */\n getFallbackEnv(): Record<string, string> {\n const fallbackProvider = this.routerConfig.fallback?.provider || 'qwen';\n const providerConfig = this.routerConfig.providers[fallbackProvider];\n\n if (!providerConfig) {\n console.error(`[fallback] Provider not configured: ${fallbackProvider}`);\n return {};\n }\n\n return buildModelEnv(providerConfig);\n }\n\n /**\n * Check if fallback is available (has API key)\n */\n isFallbackAvailable(): boolean {\n const fallbackProvider = this.routerConfig.fallback?.provider || 'qwen';\n const providerConfig = this.routerConfig.providers[fallbackProvider];\n\n if (!providerConfig) return false;\n\n return !!process.env[providerConfig.apiKeyEnv];\n }\n\n /**\n * Wrap a Claude process with fallback monitoring\n * Returns a function to spawn the process with automatic restart on failure\n */\n wrapProcess(\n command: string,\n args: string[],\n options: { env?: NodeJS.ProcessEnv; cwd?: string } = {}\n ): {\n start: () => ChildProcess;\n stop: () => void;\n isInFallback: () => boolean;\n getCurrentProvider: () => ModelProvider;\n } {\n let currentProcess: ChildProcess | null = null;\n let stopped = false;\n\n const startProcess = (): ChildProcess => {\n const env = { ...process.env, ...options.env };\n\n // Apply fallback env if in fallback mode\n if (this.inFallback) {\n const fallbackEnv = this.getFallbackEnv();\n Object.assign(env, fallbackEnv);\n this.currentProvider = this.routerConfig.fallback?.provider || 'qwen';\n }\n\n currentProcess = spawn(command, args, {\n stdio: ['inherit', 'pipe', 'pipe'],\n env,\n cwd: options.cwd,\n });\n\n // Monitor stdout\n currentProcess.stdout?.on('data', (data: Buffer) => {\n const text = data.toString();\n process.stdout.write(data);\n this.checkForErrors(text);\n });\n\n // Monitor stderr\n currentProcess.stderr?.on('data', (data: Buffer) => {\n const text = data.toString();\n process.stderr.write(data);\n this.checkForErrors(text);\n });\n\n // Handle exit\n currentProcess.on('exit', (code, _signal) => {\n if (stopped) return;\n\n // Check if we should restart with fallback\n if (code !== 0 && this.shouldRestart()) {\n console.log(\n `\\n[fallback] Process exited with code ${code}, restarting with fallback...`\n );\n this.activateFallback('exit_code');\n setTimeout(() => {\n if (!stopped) {\n startProcess();\n }\n }, this.config.restartDelayMs);\n }\n });\n\n return currentProcess;\n };\n\n return {\n start: startProcess,\n stop: () => {\n stopped = true;\n currentProcess?.kill();\n },\n isInFallback: () => this.inFallback,\n getCurrentProvider: () => this.currentProvider,\n };\n }\n\n /**\n * Check output text for errors and trigger fallback if needed\n */\n private checkForErrors(text: string): void {\n this.errorBuffer += text;\n\n // Keep buffer manageable\n if (this.errorBuffer.length > 10000) {\n this.errorBuffer = this.errorBuffer.slice(-5000);\n }\n\n const { shouldFallback, reason } = this.detectError(text);\n\n if (shouldFallback) {\n const now = Date.now();\n\n // Debounce rapid errors\n if (now - this.lastErrorTime < 1000) {\n this.errorCount++;\n } else {\n this.errorCount = 1;\n }\n this.lastErrorTime = now;\n\n // Trigger fallback after multiple rapid errors\n if (\n this.errorCount >= 2 &&\n !this.inFallback &&\n this.isFallbackAvailable()\n ) {\n console.log(`\\n[fallback] Detected error pattern: ${reason}`);\n this.activateFallback(reason);\n }\n }\n }\n\n /**\n * Activate fallback mode\n */\n private activateFallback(reason: string): void {\n if (this.inFallback) return;\n\n this.inFallback = true;\n this.restartCount++;\n this.currentProvider = this.routerConfig.fallback?.provider || 'qwen';\n\n console.log(\n `[fallback] Switching to ${this.currentProvider} (reason: ${reason})`\n );\n\n if (this.config.onFallback) {\n this.config.onFallback(this.currentProvider, reason);\n }\n }\n\n /**\n * Check if we should restart\n */\n private shouldRestart(): boolean {\n return (\n this.config.enabled &&\n this.restartCount < this.config.maxRestarts &&\n this.isFallbackAvailable()\n );\n }\n\n /**\n * Reset fallback state\n */\n reset(): void {\n this.inFallback = false;\n this.restartCount = 0;\n this.errorCount = 0;\n this.errorBuffer = '';\n this.currentProvider = 'anthropic';\n }\n\n /**\n * Get current status\n */\n getStatus(): {\n inFallback: boolean;\n currentProvider: ModelProvider;\n restartCount: number;\n fallbackAvailable: boolean;\n } {\n return {\n inFallback: this.inFallback,\n currentProvider: this.currentProvider,\n restartCount: this.restartCount,\n fallbackAvailable: this.isFallbackAvailable(),\n };\n }\n}\n\n/**\n * Create a simple fallback-aware spawn function\n */\nexport function spawnWithFallback(\n command: string,\n args: string[],\n options: {\n env?: NodeJS.ProcessEnv;\n cwd?: string;\n onFallback?: (provider: ModelProvider, reason: string) => void;\n } = {}\n): ChildProcess {\n const monitor = new FallbackMonitor({\n onFallback: options.onFallback,\n });\n\n const wrapper = monitor.wrapProcess(command, args, options);\n return wrapper.start();\n}\n\n/**\n * Quick helper to get env vars with fallback pre-configured\n */\nexport function getEnvWithFallback(): Record<string, string> {\n const config = loadModelRouterConfig();\n const env: Record<string, string> = {};\n\n // Set fallback provider info for error recovery\n if (config.fallback?.enabled) {\n const fallbackProvider = config.providers[config.fallback.provider];\n if (fallbackProvider) {\n env['STACKMEMORY_FALLBACK_PROVIDER'] = config.fallback.provider;\n env['STACKMEMORY_FALLBACK_MODEL'] = fallbackProvider.model;\n env['STACKMEMORY_FALLBACK_URL'] = fallbackProvider.baseUrl || '';\n env['STACKMEMORY_FALLBACK_KEY_ENV'] = fallbackProvider.apiKeyEnv;\n }\n }\n\n return env;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { existsSync, readFileSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { writeFileSecure, ensureSecureDir } from "../../hooks/secure-fs.js";
|
|
9
|
+
const CONFIG_PATH = join(homedir(), ".stackmemory", "model-router.json");
|
|
10
|
+
const DEFAULT_CONFIG = {
|
|
11
|
+
enabled: false,
|
|
12
|
+
defaultProvider: "anthropic",
|
|
13
|
+
taskRouting: {},
|
|
14
|
+
fallback: {
|
|
15
|
+
enabled: true,
|
|
16
|
+
// Fallback enabled by default
|
|
17
|
+
provider: "qwen",
|
|
18
|
+
onRateLimit: true,
|
|
19
|
+
onError: true,
|
|
20
|
+
onTimeout: true,
|
|
21
|
+
maxRetries: 2,
|
|
22
|
+
retryDelayMs: 1e3
|
|
23
|
+
},
|
|
24
|
+
providers: {
|
|
25
|
+
anthropic: {
|
|
26
|
+
provider: "anthropic",
|
|
27
|
+
model: "claude-sonnet-4-20250514",
|
|
28
|
+
apiKeyEnv: "ANTHROPIC_API_KEY"
|
|
29
|
+
},
|
|
30
|
+
qwen: {
|
|
31
|
+
provider: "qwen",
|
|
32
|
+
model: "qwen3-max-2025-01-23",
|
|
33
|
+
baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
34
|
+
apiKeyEnv: "DASHSCOPE_API_KEY",
|
|
35
|
+
params: {
|
|
36
|
+
enable_thinking: true,
|
|
37
|
+
thinking_budget: 1e4
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
thinkingMode: {
|
|
42
|
+
enabled: true,
|
|
43
|
+
budget: 1e4,
|
|
44
|
+
temperature: 0.6,
|
|
45
|
+
topP: 0.95
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
function loadModelRouterConfig() {
|
|
49
|
+
try {
|
|
50
|
+
if (existsSync(CONFIG_PATH)) {
|
|
51
|
+
const data = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
52
|
+
return { ...DEFAULT_CONFIG, ...data };
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
return { ...DEFAULT_CONFIG };
|
|
57
|
+
}
|
|
58
|
+
function saveModelRouterConfig(config) {
|
|
59
|
+
try {
|
|
60
|
+
ensureSecureDir(join(homedir(), ".stackmemory"));
|
|
61
|
+
writeFileSecure(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function getModelForTask(taskType) {
|
|
66
|
+
const config = loadModelRouterConfig();
|
|
67
|
+
if (!config.enabled) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const routedProvider = config.taskRouting[taskType];
|
|
71
|
+
const provider = routedProvider || config.defaultProvider;
|
|
72
|
+
return config.providers[provider] || null;
|
|
73
|
+
}
|
|
74
|
+
function buildModelEnv(modelConfig) {
|
|
75
|
+
const env = {};
|
|
76
|
+
const apiKey = process.env[modelConfig.apiKeyEnv];
|
|
77
|
+
if (!apiKey) {
|
|
78
|
+
console.warn(`[model-router] API key not found: ${modelConfig.apiKeyEnv}`);
|
|
79
|
+
return env;
|
|
80
|
+
}
|
|
81
|
+
env["ANTHROPIC_MODEL"] = modelConfig.model;
|
|
82
|
+
env["ANTHROPIC_SMALL_FAST_MODEL"] = modelConfig.model;
|
|
83
|
+
env["ANTHROPIC_AUTH_TOKEN"] = apiKey;
|
|
84
|
+
if (modelConfig.baseUrl) {
|
|
85
|
+
env["ANTHROPIC_BASE_URL"] = modelConfig.baseUrl;
|
|
86
|
+
}
|
|
87
|
+
return env;
|
|
88
|
+
}
|
|
89
|
+
function isPlanningContext(input) {
|
|
90
|
+
const planPatterns = [
|
|
91
|
+
/\bplan\b/i,
|
|
92
|
+
/\barchitect/i,
|
|
93
|
+
/\bdesign\b/i,
|
|
94
|
+
/\bstrateg/i,
|
|
95
|
+
/\bimplement.*approach/i,
|
|
96
|
+
/\bhow.*should.*we/i,
|
|
97
|
+
/\bthink.*through/i,
|
|
98
|
+
/\breason.*about/i,
|
|
99
|
+
/\banalyze.*options/i,
|
|
100
|
+
/\btrade-?offs?/i
|
|
101
|
+
];
|
|
102
|
+
return planPatterns.some((pattern) => pattern.test(input));
|
|
103
|
+
}
|
|
104
|
+
function requiresDeepThinking(input) {
|
|
105
|
+
const thinkPatterns = [
|
|
106
|
+
/\bcomplex/i,
|
|
107
|
+
/\bdifficult/i,
|
|
108
|
+
/\btricky/i,
|
|
109
|
+
/\bcareful/i,
|
|
110
|
+
/\bstep.*by.*step/i,
|
|
111
|
+
/\bthink.*hard/i,
|
|
112
|
+
/\bultrathink/i,
|
|
113
|
+
/\b--think/i,
|
|
114
|
+
/\b--think-hard/i
|
|
115
|
+
];
|
|
116
|
+
return thinkPatterns.some((pattern) => pattern.test(input));
|
|
117
|
+
}
|
|
118
|
+
class ModelRouter {
|
|
119
|
+
config;
|
|
120
|
+
currentProvider;
|
|
121
|
+
inFallbackMode = false;
|
|
122
|
+
fallbackReason;
|
|
123
|
+
constructor() {
|
|
124
|
+
this.config = loadModelRouterConfig();
|
|
125
|
+
this.currentProvider = this.config.defaultProvider;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Route a task to the appropriate model
|
|
129
|
+
*/
|
|
130
|
+
route(taskType, input) {
|
|
131
|
+
if (!this.config.enabled) {
|
|
132
|
+
return { provider: "anthropic", env: {}, switched: false };
|
|
133
|
+
}
|
|
134
|
+
let detectedType = taskType;
|
|
135
|
+
if (input && taskType === "default") {
|
|
136
|
+
if (isPlanningContext(input)) {
|
|
137
|
+
detectedType = "plan";
|
|
138
|
+
} else if (requiresDeepThinking(input)) {
|
|
139
|
+
detectedType = "think";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const modelConfig = getModelForTask(detectedType);
|
|
143
|
+
if (!modelConfig) {
|
|
144
|
+
return { provider: "anthropic", env: {}, switched: false };
|
|
145
|
+
}
|
|
146
|
+
const switched = modelConfig.provider !== this.currentProvider;
|
|
147
|
+
this.currentProvider = modelConfig.provider;
|
|
148
|
+
return {
|
|
149
|
+
provider: modelConfig.provider,
|
|
150
|
+
env: buildModelEnv(modelConfig),
|
|
151
|
+
switched
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get current provider
|
|
156
|
+
*/
|
|
157
|
+
getCurrentProvider() {
|
|
158
|
+
return this.currentProvider;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Force switch to a specific provider
|
|
162
|
+
*/
|
|
163
|
+
switchTo(provider) {
|
|
164
|
+
const modelConfig = this.config.providers[provider];
|
|
165
|
+
if (!modelConfig) {
|
|
166
|
+
console.warn(`[model-router] Provider not configured: ${provider}`);
|
|
167
|
+
return {};
|
|
168
|
+
}
|
|
169
|
+
this.currentProvider = provider;
|
|
170
|
+
return buildModelEnv(modelConfig);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Reset to default provider
|
|
174
|
+
*/
|
|
175
|
+
reset() {
|
|
176
|
+
this.currentProvider = this.config.defaultProvider;
|
|
177
|
+
this.inFallbackMode = false;
|
|
178
|
+
this.fallbackReason = void 0;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Check if fallback is enabled and configured
|
|
182
|
+
*/
|
|
183
|
+
isFallbackEnabled() {
|
|
184
|
+
if (!this.config.fallback?.enabled) return false;
|
|
185
|
+
const fallbackProvider = this.config.providers[this.config.fallback.provider];
|
|
186
|
+
if (!fallbackProvider) return false;
|
|
187
|
+
const apiKey = process.env[fallbackProvider.apiKeyEnv];
|
|
188
|
+
return !!apiKey;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Check if error should trigger fallback
|
|
192
|
+
*/
|
|
193
|
+
shouldFallback(error) {
|
|
194
|
+
if (!this.isFallbackEnabled()) return false;
|
|
195
|
+
if (this.inFallbackMode) return false;
|
|
196
|
+
const fallback = this.config.fallback;
|
|
197
|
+
if (fallback.onRateLimit && error.status === 429) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
if (fallback.onError && error.status && error.status >= 500) {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
if (fallback.onTimeout) {
|
|
204
|
+
const isTimeout = error.code === "ETIMEDOUT" || error.code === "ESOCKETTIMEDOUT" || error.message?.toLowerCase().includes("timeout");
|
|
205
|
+
if (isTimeout) return true;
|
|
206
|
+
}
|
|
207
|
+
if (error.message?.toLowerCase().includes("overloaded")) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Activate fallback mode
|
|
214
|
+
*/
|
|
215
|
+
activateFallback(reason) {
|
|
216
|
+
if (!this.isFallbackEnabled()) {
|
|
217
|
+
console.warn("[model-router] Fallback not available");
|
|
218
|
+
return {};
|
|
219
|
+
}
|
|
220
|
+
const fallbackProvider = this.config.fallback.provider;
|
|
221
|
+
const modelConfig = this.config.providers[fallbackProvider];
|
|
222
|
+
if (!modelConfig) {
|
|
223
|
+
console.warn(
|
|
224
|
+
`[model-router] Fallback provider not configured: ${fallbackProvider}`
|
|
225
|
+
);
|
|
226
|
+
return {};
|
|
227
|
+
}
|
|
228
|
+
this.inFallbackMode = true;
|
|
229
|
+
this.fallbackReason = reason;
|
|
230
|
+
this.currentProvider = fallbackProvider;
|
|
231
|
+
console.log(
|
|
232
|
+
`[model-router] Fallback activated: ${reason} -> ${fallbackProvider}`
|
|
233
|
+
);
|
|
234
|
+
return buildModelEnv(modelConfig);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Get fallback configuration
|
|
238
|
+
*/
|
|
239
|
+
getFallbackConfig() {
|
|
240
|
+
return this.config.fallback;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Check if currently in fallback mode
|
|
244
|
+
*/
|
|
245
|
+
isInFallbackMode() {
|
|
246
|
+
return this.inFallbackMode;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get reason for fallback
|
|
250
|
+
*/
|
|
251
|
+
getFallbackReason() {
|
|
252
|
+
return this.fallbackReason;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Get fallback environment variables (for pre-configuring)
|
|
256
|
+
*/
|
|
257
|
+
getFallbackEnv() {
|
|
258
|
+
if (!this.isFallbackEnabled()) return {};
|
|
259
|
+
const fallbackProvider = this.config.fallback.provider;
|
|
260
|
+
const modelConfig = this.config.providers[fallbackProvider];
|
|
261
|
+
if (!modelConfig) return {};
|
|
262
|
+
return buildModelEnv(modelConfig);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
let routerInstance = null;
|
|
266
|
+
function getModelRouter() {
|
|
267
|
+
if (!routerInstance) {
|
|
268
|
+
routerInstance = new ModelRouter();
|
|
269
|
+
}
|
|
270
|
+
return routerInstance;
|
|
271
|
+
}
|
|
272
|
+
function getPlanModeEnv() {
|
|
273
|
+
const router = getModelRouter();
|
|
274
|
+
const result = router.route("plan");
|
|
275
|
+
return result.env;
|
|
276
|
+
}
|
|
277
|
+
function getThinkingModeEnv() {
|
|
278
|
+
const router = getModelRouter();
|
|
279
|
+
const result = router.route("think");
|
|
280
|
+
return result.env;
|
|
281
|
+
}
|
|
282
|
+
function isFallbackAvailable() {
|
|
283
|
+
const router = getModelRouter();
|
|
284
|
+
return router.isFallbackEnabled();
|
|
285
|
+
}
|
|
286
|
+
function getFallbackStatus() {
|
|
287
|
+
const router = getModelRouter();
|
|
288
|
+
const config = loadModelRouterConfig();
|
|
289
|
+
if (!config.fallback?.enabled) {
|
|
290
|
+
return {
|
|
291
|
+
enabled: false,
|
|
292
|
+
provider: null,
|
|
293
|
+
hasApiKey: false,
|
|
294
|
+
inFallback: false
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
const fallbackProvider = config.providers[config.fallback.provider];
|
|
298
|
+
const hasApiKey = fallbackProvider ? !!process.env[fallbackProvider.apiKeyEnv] : false;
|
|
299
|
+
return {
|
|
300
|
+
enabled: true,
|
|
301
|
+
provider: config.fallback.provider,
|
|
302
|
+
hasApiKey,
|
|
303
|
+
inFallback: router.isInFallbackMode(),
|
|
304
|
+
reason: router.getFallbackReason()
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function triggerFallback(reason = "manual") {
|
|
308
|
+
const router = getModelRouter();
|
|
309
|
+
return router.activateFallback(reason);
|
|
310
|
+
}
|
|
311
|
+
function resetFallback() {
|
|
312
|
+
const router = getModelRouter();
|
|
313
|
+
router.reset();
|
|
314
|
+
}
|
|
315
|
+
export {
|
|
316
|
+
ModelRouter,
|
|
317
|
+
buildModelEnv,
|
|
318
|
+
getFallbackStatus,
|
|
319
|
+
getModelForTask,
|
|
320
|
+
getModelRouter,
|
|
321
|
+
getPlanModeEnv,
|
|
322
|
+
getThinkingModeEnv,
|
|
323
|
+
isFallbackAvailable,
|
|
324
|
+
isPlanningContext,
|
|
325
|
+
loadModelRouterConfig,
|
|
326
|
+
requiresDeepThinking,
|
|
327
|
+
resetFallback,
|
|
328
|
+
saveModelRouterConfig,
|
|
329
|
+
triggerFallback
|
|
330
|
+
};
|
|
331
|
+
//# sourceMappingURL=model-router.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/core/models/model-router.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Model Router - Switch between Claude and alternative models\n * Supports routing plan/thinking tasks to specialized models like Qwen3-Max-Thinking\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { writeFileSecure, ensureSecureDir } from '../../hooks/secure-fs.js';\n\nexport type ModelProvider =\n | 'anthropic'\n | 'qwen'\n | 'openai'\n | 'ollama'\n | 'custom';\nexport type TaskType = 'default' | 'plan' | 'think' | 'code' | 'review';\n\nexport interface ModelConfig {\n provider: ModelProvider;\n model: string;\n baseUrl?: string;\n apiKeyEnv: string; // Environment variable name for API key\n headers?: Record<string, string>;\n params?: Record<string, unknown>; // Provider-specific params\n}\n\nexport interface ModelRouterConfig {\n enabled: boolean;\n defaultProvider: ModelProvider;\n\n // Route specific task types to different models\n taskRouting: {\n plan?: ModelProvider; // Planning/architecture tasks\n think?: ModelProvider; // Deep thinking/reasoning\n code?: ModelProvider; // Code generation\n review?: ModelProvider; // Code review\n };\n\n // Fallback configuration\n fallback: {\n enabled: boolean;\n provider: ModelProvider; // Fallback provider (default: qwen)\n onRateLimit: boolean; // Fallback on 429 errors\n onError: boolean; // Fallback on 5xx errors\n onTimeout: boolean; // Fallback on timeout\n maxRetries: number; // Retries before fallback\n retryDelayMs: number; // Delay between retries\n };\n\n // Provider configurations\n providers: {\n anthropic?: ModelConfig;\n qwen?: ModelConfig;\n openai?: ModelConfig;\n ollama?: ModelConfig;\n custom?: ModelConfig;\n };\n\n // Thinking mode settings\n thinkingMode: {\n enabled: boolean;\n budget?: number; // Max thinking tokens\n temperature?: number; // Recommended: 0.6 for thinking\n topP?: number; // Recommended: 0.95 for thinking\n };\n}\n\nconst CONFIG_PATH = join(homedir(), '.stackmemory', 'model-router.json');\n\nconst DEFAULT_CONFIG: ModelRouterConfig = {\n enabled: false,\n defaultProvider: 'anthropic',\n taskRouting: {},\n fallback: {\n enabled: true, // Fallback enabled by default\n provider: 'qwen',\n onRateLimit: true,\n onError: true,\n onTimeout: true,\n maxRetries: 2,\n retryDelayMs: 1000,\n },\n providers: {\n anthropic: {\n provider: 'anthropic',\n model: 'claude-sonnet-4-20250514',\n apiKeyEnv: 'ANTHROPIC_API_KEY',\n },\n qwen: {\n provider: 'qwen',\n model: 'qwen3-max-2025-01-23',\n baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',\n apiKeyEnv: 'DASHSCOPE_API_KEY',\n params: {\n enable_thinking: true,\n thinking_budget: 10000,\n },\n },\n },\n thinkingMode: {\n enabled: true,\n budget: 10000,\n temperature: 0.6,\n topP: 0.95,\n },\n};\n\n/**\n * Load model router configuration\n */\nexport function loadModelRouterConfig(): ModelRouterConfig {\n try {\n if (existsSync(CONFIG_PATH)) {\n const data = JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));\n return { ...DEFAULT_CONFIG, ...data };\n }\n } catch {\n // Use defaults\n }\n return { ...DEFAULT_CONFIG };\n}\n\n/**\n * Save model router configuration\n */\nexport function saveModelRouterConfig(config: ModelRouterConfig): void {\n try {\n ensureSecureDir(join(homedir(), '.stackmemory'));\n writeFileSecure(CONFIG_PATH, JSON.stringify(config, null, 2));\n } catch {\n // Silently fail\n }\n}\n\n/**\n * Get model config for a specific task type\n */\nexport function getModelForTask(taskType: TaskType): ModelConfig | null {\n const config = loadModelRouterConfig();\n\n if (!config.enabled) {\n return null; // Use default Claude\n }\n\n // Check task-specific routing\n const routedProvider =\n config.taskRouting[taskType as keyof typeof config.taskRouting];\n const provider = routedProvider || config.defaultProvider;\n\n return config.providers[provider] || null;\n}\n\n/**\n * Build environment variables for alternative model\n */\nexport function buildModelEnv(\n modelConfig: ModelConfig\n): Record<string, string> {\n const env: Record<string, string> = {};\n\n // Get API key from environment\n const apiKey = process.env[modelConfig.apiKeyEnv];\n if (!apiKey) {\n console.warn(`[model-router] API key not found: ${modelConfig.apiKeyEnv}`);\n return env;\n }\n\n // Set Anthropic-compatible env vars (Claude Code reads these)\n env['ANTHROPIC_MODEL'] = modelConfig.model;\n env['ANTHROPIC_SMALL_FAST_MODEL'] = modelConfig.model;\n env['ANTHROPIC_AUTH_TOKEN'] = apiKey;\n\n if (modelConfig.baseUrl) {\n env['ANTHROPIC_BASE_URL'] = modelConfig.baseUrl;\n }\n\n return env;\n}\n\n/**\n * Detect if current context is a planning task\n */\nexport function isPlanningContext(input: string): boolean {\n const planPatterns = [\n /\\bplan\\b/i,\n /\\barchitect/i,\n /\\bdesign\\b/i,\n /\\bstrateg/i,\n /\\bimplement.*approach/i,\n /\\bhow.*should.*we/i,\n /\\bthink.*through/i,\n /\\breason.*about/i,\n /\\banalyze.*options/i,\n /\\btrade-?offs?/i,\n ];\n\n return planPatterns.some((pattern) => pattern.test(input));\n}\n\n/**\n * Detect if task requires deep thinking\n */\nexport function requiresDeepThinking(input: string): boolean {\n const thinkPatterns = [\n /\\bcomplex/i,\n /\\bdifficult/i,\n /\\btricky/i,\n /\\bcareful/i,\n /\\bstep.*by.*step/i,\n /\\bthink.*hard/i,\n /\\bultrathink/i,\n /\\b--think/i,\n /\\b--think-hard/i,\n ];\n\n return thinkPatterns.some((pattern) => pattern.test(input));\n}\n\n/**\n * Error types that trigger fallback\n */\nexport type FallbackTrigger = 'rate_limit' | 'error' | 'timeout' | 'manual';\n\n/**\n * Model Router class for managing model switching\n */\nexport class ModelRouter {\n private config: ModelRouterConfig;\n private currentProvider: ModelProvider;\n private inFallbackMode: boolean = false;\n private fallbackReason?: FallbackTrigger;\n\n constructor() {\n this.config = loadModelRouterConfig();\n this.currentProvider = this.config.defaultProvider;\n }\n\n /**\n * Route a task to the appropriate model\n */\n route(\n taskType: TaskType,\n input?: string\n ): {\n provider: ModelProvider;\n env: Record<string, string>;\n switched: boolean;\n } {\n if (!this.config.enabled) {\n return { provider: 'anthropic', env: {}, switched: false };\n }\n\n // Auto-detect task type if input provided\n let detectedType = taskType;\n if (input && taskType === 'default') {\n if (isPlanningContext(input)) {\n detectedType = 'plan';\n } else if (requiresDeepThinking(input)) {\n detectedType = 'think';\n }\n }\n\n const modelConfig = getModelForTask(detectedType);\n if (!modelConfig) {\n return { provider: 'anthropic', env: {}, switched: false };\n }\n\n const switched = modelConfig.provider !== this.currentProvider;\n this.currentProvider = modelConfig.provider;\n\n return {\n provider: modelConfig.provider,\n env: buildModelEnv(modelConfig),\n switched,\n };\n }\n\n /**\n * Get current provider\n */\n getCurrentProvider(): ModelProvider {\n return this.currentProvider;\n }\n\n /**\n * Force switch to a specific provider\n */\n switchTo(provider: ModelProvider): Record<string, string> {\n const modelConfig = this.config.providers[provider];\n if (!modelConfig) {\n console.warn(`[model-router] Provider not configured: ${provider}`);\n return {};\n }\n\n this.currentProvider = provider;\n return buildModelEnv(modelConfig);\n }\n\n /**\n * Reset to default provider\n */\n reset(): void {\n this.currentProvider = this.config.defaultProvider;\n this.inFallbackMode = false;\n this.fallbackReason = undefined;\n }\n\n /**\n * Check if fallback is enabled and configured\n */\n isFallbackEnabled(): boolean {\n if (!this.config.fallback?.enabled) return false;\n\n const fallbackProvider =\n this.config.providers[this.config.fallback.provider];\n if (!fallbackProvider) return false;\n\n // Check if fallback provider has API key\n const apiKey = process.env[fallbackProvider.apiKeyEnv];\n return !!apiKey;\n }\n\n /**\n * Check if error should trigger fallback\n */\n shouldFallback(error: {\n status?: number;\n code?: string;\n message?: string;\n }): boolean {\n if (!this.isFallbackEnabled()) return false;\n if (this.inFallbackMode) return false; // Already in fallback\n\n const fallback = this.config.fallback;\n\n // Rate limit (429)\n if (fallback.onRateLimit && error.status === 429) {\n return true;\n }\n\n // Server errors (5xx)\n if (fallback.onError && error.status && error.status >= 500) {\n return true;\n }\n\n // Timeout\n if (fallback.onTimeout) {\n const isTimeout =\n error.code === 'ETIMEDOUT' ||\n error.code === 'ESOCKETTIMEDOUT' ||\n error.message?.toLowerCase().includes('timeout');\n if (isTimeout) return true;\n }\n\n // Overloaded\n if (error.message?.toLowerCase().includes('overloaded')) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Activate fallback mode\n */\n activateFallback(reason: FallbackTrigger): Record<string, string> {\n if (!this.isFallbackEnabled()) {\n console.warn('[model-router] Fallback not available');\n return {};\n }\n\n const fallbackProvider = this.config.fallback.provider;\n const modelConfig = this.config.providers[fallbackProvider];\n\n if (!modelConfig) {\n console.warn(\n `[model-router] Fallback provider not configured: ${fallbackProvider}`\n );\n return {};\n }\n\n this.inFallbackMode = true;\n this.fallbackReason = reason;\n this.currentProvider = fallbackProvider;\n\n console.log(\n `[model-router] Fallback activated: ${reason} -> ${fallbackProvider}`\n );\n\n return buildModelEnv(modelConfig);\n }\n\n /**\n * Get fallback configuration\n */\n getFallbackConfig(): ModelRouterConfig['fallback'] {\n return this.config.fallback;\n }\n\n /**\n * Check if currently in fallback mode\n */\n isInFallbackMode(): boolean {\n return this.inFallbackMode;\n }\n\n /**\n * Get reason for fallback\n */\n getFallbackReason(): FallbackTrigger | undefined {\n return this.fallbackReason;\n }\n\n /**\n * Get fallback environment variables (for pre-configuring)\n */\n getFallbackEnv(): Record<string, string> {\n if (!this.isFallbackEnabled()) return {};\n\n const fallbackProvider = this.config.fallback.provider;\n const modelConfig = this.config.providers[fallbackProvider];\n\n if (!modelConfig) return {};\n\n return buildModelEnv(modelConfig);\n }\n}\n\n// Singleton instance\nlet routerInstance: ModelRouter | null = null;\n\nexport function getModelRouter(): ModelRouter {\n if (!routerInstance) {\n routerInstance = new ModelRouter();\n }\n return routerInstance;\n}\n\n/**\n * Quick helper to get env vars for plan mode\n */\nexport function getPlanModeEnv(): Record<string, string> {\n const router = getModelRouter();\n const result = router.route('plan');\n return result.env;\n}\n\n/**\n * Quick helper to get env vars for thinking mode\n */\nexport function getThinkingModeEnv(): Record<string, string> {\n const router = getModelRouter();\n const result = router.route('think');\n return result.env;\n}\n\n/**\n * Check if fallback is available\n */\nexport function isFallbackAvailable(): boolean {\n const router = getModelRouter();\n return router.isFallbackEnabled();\n}\n\n/**\n * Get fallback status for display\n */\nexport function getFallbackStatus(): {\n enabled: boolean;\n provider: ModelProvider | null;\n hasApiKey: boolean;\n inFallback: boolean;\n reason?: FallbackTrigger;\n} {\n const router = getModelRouter();\n const config = loadModelRouterConfig();\n\n if (!config.fallback?.enabled) {\n return {\n enabled: false,\n provider: null,\n hasApiKey: false,\n inFallback: false,\n };\n }\n\n const fallbackProvider = config.providers[config.fallback.provider];\n const hasApiKey = fallbackProvider\n ? !!process.env[fallbackProvider.apiKeyEnv]\n : false;\n\n return {\n enabled: true,\n provider: config.fallback.provider,\n hasApiKey,\n inFallback: router.isInFallbackMode(),\n reason: router.getFallbackReason(),\n };\n}\n\n/**\n * Trigger fallback manually (for testing or forced switch)\n */\nexport function triggerFallback(\n reason: FallbackTrigger = 'manual'\n): Record<string, string> {\n const router = getModelRouter();\n return router.activateFallback(reason);\n}\n\n/**\n * Reset fallback state\n */\nexport function resetFallback(): void {\n const router = getModelRouter();\n router.reset();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|