daedalus-cli 0.4.0
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/Daedalus.bat +18 -0
- package/LICENSE +21 -0
- package/README.md +254 -0
- package/dist/agents/agent.d.ts +10 -0
- package/dist/agents/agent.d.ts.map +1 -0
- package/dist/agents/agent.js +3 -0
- package/dist/agents/agent.js.map +1 -0
- package/dist/agents/orchestrator.d.ts +18 -0
- package/dist/agents/orchestrator.d.ts.map +1 -0
- package/dist/agents/orchestrator.js +171 -0
- package/dist/agents/orchestrator.js.map +1 -0
- package/dist/agents/roles.d.ts +14 -0
- package/dist/agents/roles.d.ts.map +1 -0
- package/dist/agents/roles.js +126 -0
- package/dist/agents/roles.js.map +1 -0
- package/dist/config/index.d.ts +485 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +237 -0
- package/dist/config/index.js.map +1 -0
- package/dist/highlight.d.ts +4 -0
- package/dist/highlight.d.ts.map +1 -0
- package/dist/highlight.js +42 -0
- package/dist/highlight.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1386 -0
- package/dist/index.js.map +1 -0
- package/dist/indexing/fts.d.ts +40 -0
- package/dist/indexing/fts.d.ts.map +1 -0
- package/dist/indexing/fts.js +121 -0
- package/dist/indexing/fts.js.map +1 -0
- package/dist/indexing/indexer.d.ts +22 -0
- package/dist/indexing/indexer.d.ts.map +1 -0
- package/dist/indexing/indexer.js +518 -0
- package/dist/indexing/indexer.js.map +1 -0
- package/dist/onboarding/wizard.d.ts +2 -0
- package/dist/onboarding/wizard.d.ts.map +1 -0
- package/dist/onboarding/wizard.js +231 -0
- package/dist/onboarding/wizard.js.map +1 -0
- package/dist/router/health.d.ts +6 -0
- package/dist/router/health.d.ts.map +1 -0
- package/dist/router/health.js +74 -0
- package/dist/router/health.js.map +1 -0
- package/dist/router/index.d.ts +33 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +214 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/rate-limiter.d.ts +7 -0
- package/dist/router/rate-limiter.d.ts.map +1 -0
- package/dist/router/rate-limiter.js +36 -0
- package/dist/router/rate-limiter.js.map +1 -0
- package/dist/router/types.d.ts +81 -0
- package/dist/router/types.d.ts.map +1 -0
- package/dist/router/types.js +3 -0
- package/dist/router/types.js.map +1 -0
- package/dist/session/jsonl.d.ts +20 -0
- package/dist/session/jsonl.d.ts.map +1 -0
- package/dist/session/jsonl.js +61 -0
- package/dist/session/jsonl.js.map +1 -0
- package/dist/session/manager.d.ts +42 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +184 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/session/memory.d.ts +23 -0
- package/dist/session/memory.d.ts.map +1 -0
- package/dist/session/memory.js +88 -0
- package/dist/session/memory.js.map +1 -0
- package/dist/session/sqlite.d.ts +59 -0
- package/dist/session/sqlite.d.ts.map +1 -0
- package/dist/session/sqlite.js +174 -0
- package/dist/session/sqlite.js.map +1 -0
- package/dist/tools/builtin/delegation.d.ts +17 -0
- package/dist/tools/builtin/delegation.d.ts.map +1 -0
- package/dist/tools/builtin/delegation.js +85 -0
- package/dist/tools/builtin/delegation.js.map +1 -0
- package/dist/tools/builtin/diff-ui.d.ts +21 -0
- package/dist/tools/builtin/diff-ui.d.ts.map +1 -0
- package/dist/tools/builtin/diff-ui.js +211 -0
- package/dist/tools/builtin/diff-ui.js.map +1 -0
- package/dist/tools/builtin/files.d.ts +29 -0
- package/dist/tools/builtin/files.d.ts.map +1 -0
- package/dist/tools/builtin/files.js +286 -0
- package/dist/tools/builtin/files.js.map +1 -0
- package/dist/tools/builtin/git.d.ts +7 -0
- package/dist/tools/builtin/git.d.ts.map +1 -0
- package/dist/tools/builtin/git.js +11 -0
- package/dist/tools/builtin/git.js.map +1 -0
- package/dist/tools/builtin/indexing.d.ts +22 -0
- package/dist/tools/builtin/indexing.d.ts.map +1 -0
- package/dist/tools/builtin/indexing.js +159 -0
- package/dist/tools/builtin/indexing.js.map +1 -0
- package/dist/tools/builtin/project-config.d.ts +17 -0
- package/dist/tools/builtin/project-config.d.ts.map +1 -0
- package/dist/tools/builtin/project-config.js +66 -0
- package/dist/tools/builtin/project-config.js.map +1 -0
- package/dist/tools/builtin/terminal.d.ts +7 -0
- package/dist/tools/builtin/terminal.d.ts.map +1 -0
- package/dist/tools/builtin/terminal.js +99 -0
- package/dist/tools/builtin/terminal.js.map +1 -0
- package/dist/tools/builtin/todo.d.ts +20 -0
- package/dist/tools/builtin/todo.d.ts.map +1 -0
- package/dist/tools/builtin/todo.js +36 -0
- package/dist/tools/builtin/todo.js.map +1 -0
- package/dist/tools/builtin/web.d.ts +10 -0
- package/dist/tools/builtin/web.d.ts.map +1 -0
- package/dist/tools/builtin/web.js +67 -0
- package/dist/tools/builtin/web.js.map +1 -0
- package/dist/tools/daedalus-spinner.d.ts +29 -0
- package/dist/tools/daedalus-spinner.d.ts.map +1 -0
- package/dist/tools/daedalus-spinner.js +77 -0
- package/dist/tools/daedalus-spinner.js.map +1 -0
- package/dist/tools/definitions.d.ts +5 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +296 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/executor.d.ts +4 -0
- package/dist/tools/executor.d.ts.map +1 -0
- package/dist/tools/executor.js +86 -0
- package/dist/tools/executor.js.map +1 -0
- package/dist/tools/mcp/http.d.ts +23 -0
- package/dist/tools/mcp/http.d.ts.map +1 -0
- package/dist/tools/mcp/http.js +200 -0
- package/dist/tools/mcp/http.js.map +1 -0
- package/dist/tools/mcp/registry.d.ts +16 -0
- package/dist/tools/mcp/registry.d.ts.map +1 -0
- package/dist/tools/mcp/registry.js +92 -0
- package/dist/tools/mcp/registry.js.map +1 -0
- package/dist/tools/mcp/stdio.d.ts +26 -0
- package/dist/tools/mcp/stdio.d.ts.map +1 -0
- package/dist/tools/mcp/stdio.js +157 -0
- package/dist/tools/mcp/stdio.js.map +1 -0
- package/dist/tools/mcp/tool-executor.d.ts +3 -0
- package/dist/tools/mcp/tool-executor.d.ts.map +1 -0
- package/dist/tools/mcp/tool-executor.js +23 -0
- package/dist/tools/mcp/tool-executor.js.map +1 -0
- package/dist/tools/mcp/types.d.ts +26 -0
- package/dist/tools/mcp/types.d.ts.map +1 -0
- package/dist/tools/mcp/types.js +3 -0
- package/dist/tools/mcp/types.js.map +1 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// Daedalus First-Run Onboarding Wizard
|
|
2
|
+
// Guides users through initial setup — local or remote LLM configuration
|
|
3
|
+
import readline from 'readline';
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
import { loadConfig, saveConfig, discoverLocalServers } from '../config/index.js';
|
|
6
|
+
import { checkModelHealth } from '../router/health.js';
|
|
7
|
+
// ── Utility: prompt for input ──
|
|
8
|
+
function question(rl, query) {
|
|
9
|
+
return new Promise(resolve => rl.question(query, resolve));
|
|
10
|
+
}
|
|
11
|
+
// ── Check if any model is actually reachable ──
|
|
12
|
+
async function hasAnyHealthyModel(config) {
|
|
13
|
+
const enabled = config.router.chain.filter(m => m.enabled);
|
|
14
|
+
if (enabled.length === 0)
|
|
15
|
+
return false;
|
|
16
|
+
const results = await Promise.allSettled(enabled.map(m => checkModelHealth(m, 5000)));
|
|
17
|
+
return results.some(r => r.status === 'fulfilled' && r.value?.healthy === true);
|
|
18
|
+
}
|
|
19
|
+
// ── Add a remote provider entry ──
|
|
20
|
+
function addRemoteProvider(config, name, endpoint, apiKey) {
|
|
21
|
+
// Disable all current entries first
|
|
22
|
+
config.router.chain.forEach(m => { m.enabled = false; });
|
|
23
|
+
// Add the new one with priority 1
|
|
24
|
+
config.router.chain.unshift({
|
|
25
|
+
name,
|
|
26
|
+
endpoint,
|
|
27
|
+
model: 'auto',
|
|
28
|
+
priority: 1,
|
|
29
|
+
enabled: true,
|
|
30
|
+
supportsTools: true,
|
|
31
|
+
apiKey: apiKey || undefined,
|
|
32
|
+
});
|
|
33
|
+
if (apiKey) {
|
|
34
|
+
const keyVar = name.toUpperCase().replace(/[^A-Z0-9]/g, '_') + '_API_KEY';
|
|
35
|
+
console.log(pc.gray(`\n 💡 API key saved in config. You can also set ${pc.bold(keyVar)} as an environment variable.`));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// ── Auto-configure with the first discovered local server ──
|
|
39
|
+
function addLocalServer(config, discovered) {
|
|
40
|
+
config.router.chain.forEach(m => { m.enabled = false; });
|
|
41
|
+
const server = discovered[0];
|
|
42
|
+
config.router.chain.unshift({
|
|
43
|
+
name: server.name.toLowerCase().replace(/\s+/g, '-'),
|
|
44
|
+
endpoint: server.endpoint,
|
|
45
|
+
model: 'auto',
|
|
46
|
+
priority: 1,
|
|
47
|
+
enabled: true,
|
|
48
|
+
supportsTools: true,
|
|
49
|
+
});
|
|
50
|
+
console.log(pc.green(` ✔ Added ${server.name} at ${server.endpoint}`));
|
|
51
|
+
}
|
|
52
|
+
// ── Main wizard entry point ──
|
|
53
|
+
export async function runOnboarding(force = false) {
|
|
54
|
+
// Quick check — if already configured with a healthy model, skip entirely (unless forced)
|
|
55
|
+
const initialConfig = loadConfig();
|
|
56
|
+
if (!force) {
|
|
57
|
+
const alreadyHealthy = await hasAnyHealthyModel(initialConfig);
|
|
58
|
+
if (alreadyHealthy)
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// Flag to detect if user made changes
|
|
62
|
+
let configChanged = false;
|
|
63
|
+
let config = loadConfig();
|
|
64
|
+
const rl = readline.createInterface({
|
|
65
|
+
input: process.stdin,
|
|
66
|
+
output: process.stdout,
|
|
67
|
+
terminal: true,
|
|
68
|
+
});
|
|
69
|
+
// ── Welcome ──
|
|
70
|
+
console.log();
|
|
71
|
+
console.log(pc.bold(pc.cyan(' ╔══════════════════════════════════════════════════╗')));
|
|
72
|
+
const welcomeLine = ' Welcome to Daedalus — First-Time Setup ';
|
|
73
|
+
console.log(pc.bold(pc.cyan(' ║' + welcomeLine + '║')));
|
|
74
|
+
console.log(pc.bold(pc.cyan(' ╚══════════════════════════════════════════════════╝')));
|
|
75
|
+
console.log();
|
|
76
|
+
console.log(pc.gray(' I need at least one LLM backend to start chatting.'));
|
|
77
|
+
console.log(pc.gray(' Let me help you get connected.'));
|
|
78
|
+
console.log();
|
|
79
|
+
// ── Step 1: Auto-discover ──
|
|
80
|
+
console.log(pc.bold('📡 Scanning for local LLM servers...'));
|
|
81
|
+
const discovered = await discoverLocalServers();
|
|
82
|
+
if (discovered.length > 0) {
|
|
83
|
+
console.log(pc.green(` Found ${discovered.length} running server(s):`));
|
|
84
|
+
for (const s of discovered) {
|
|
85
|
+
console.log(` ${pc.green('●')} ${s.name} at ${s.endpoint}`);
|
|
86
|
+
if (s.models.length > 0) {
|
|
87
|
+
console.log(` Models: ${s.models.slice(0, 3).join(', ')}${s.models.length > 3 ? '...' : ''}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
console.log();
|
|
91
|
+
const ans = await question(rl, pc.bold(' Use the first discovered server automatically? [Y/n] '));
|
|
92
|
+
if (!ans.toLowerCase().startsWith('n')) {
|
|
93
|
+
addLocalServer(config, discovered);
|
|
94
|
+
configChanged = true;
|
|
95
|
+
console.log();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.log(pc.yellow(' No local servers detected.'));
|
|
100
|
+
console.log();
|
|
101
|
+
}
|
|
102
|
+
// ── Step 2: If still not configured, show options ──
|
|
103
|
+
if (!configChanged) {
|
|
104
|
+
await showOptionsMenu(rl, config);
|
|
105
|
+
configChanged = true;
|
|
106
|
+
}
|
|
107
|
+
// ── Step 3: Save config ──
|
|
108
|
+
if (configChanged) {
|
|
109
|
+
saveConfig(config);
|
|
110
|
+
console.log(pc.green('\n✔ Configuration saved!'));
|
|
111
|
+
}
|
|
112
|
+
// ── Step 4: Test ──
|
|
113
|
+
console.log(pc.bold('\n🔍 Testing connection...'));
|
|
114
|
+
const healthy = await hasAnyHealthyModel(config);
|
|
115
|
+
if (healthy) {
|
|
116
|
+
console.log(pc.green(' ✅ Connection successful! Ready to chat.'));
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.log(pc.yellow(' ⚠ Still no healthy model. Run /doctor anytime to diagnose.'));
|
|
120
|
+
console.log(pc.gray(' You can also type /config to see current settings.'));
|
|
121
|
+
}
|
|
122
|
+
console.log(pc.gray('\n Tip: type /help or /commands anytime to see what I can do.'));
|
|
123
|
+
console.log();
|
|
124
|
+
rl.close();
|
|
125
|
+
// Ensure stdin is back to normal for the main REPL
|
|
126
|
+
if (process.stdin.isPaused()) {
|
|
127
|
+
process.stdin.resume();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// ── Options menu when no server is found ──
|
|
131
|
+
async function showOptionsMenu(rl, config) {
|
|
132
|
+
// eslint-disable-next-line no-constant-condition
|
|
133
|
+
while (true) {
|
|
134
|
+
console.log(pc.bold('\n ⚙ What would you like to do?'));
|
|
135
|
+
console.log();
|
|
136
|
+
console.log(' 1) Start a local server and scan again');
|
|
137
|
+
console.log(' 2) Connect to a remote API (OpenAI, Groq, OpenRouter, etc.)');
|
|
138
|
+
console.log(' 3) I\'ll configure it myself later');
|
|
139
|
+
console.log();
|
|
140
|
+
const choice = await question(rl, pc.bold(' Your choice (1-3): '));
|
|
141
|
+
const trimmed = choice.trim();
|
|
142
|
+
if (trimmed === '1') {
|
|
143
|
+
console.log(pc.gray('\n Please start one of these in another terminal:'));
|
|
144
|
+
console.log(pc.gray(' • LM Studio — open app, start server on :1234'));
|
|
145
|
+
console.log(pc.gray(' • Ollama — run "ollama serve"'));
|
|
146
|
+
console.log(pc.gray(' • llama.cpp — run "./server" (default :8080)'));
|
|
147
|
+
console.log(pc.gray(' • vLLM — run "vllm serve" (default :8000)'));
|
|
148
|
+
console.log();
|
|
149
|
+
await question(rl, pc.bold(' Press Enter after starting your server...'));
|
|
150
|
+
console.log(pc.bold('\n📡 Re-scanning...'));
|
|
151
|
+
const discovered = await discoverLocalServers();
|
|
152
|
+
if (discovered.length > 0) {
|
|
153
|
+
console.log(pc.green(` Found ${discovered.length} server(s)!`));
|
|
154
|
+
addLocalServer(config, discovered);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
console.log(pc.yellow(' Still nothing detected. Try again or choose another option.'));
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (trimmed === '2') {
|
|
163
|
+
await configureRemoteProvider(rl, config);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (trimmed === '3') {
|
|
167
|
+
console.log(pc.gray('\n No problem! Run /doctor anytime to auto-detect servers,'));
|
|
168
|
+
console.log(pc.gray(' or edit ~/.daedalus/config.json manually.'));
|
|
169
|
+
console.log(pc.gray(' Type /config to see current settings.'));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
console.log(pc.red(' Invalid choice. Please enter 1, 2, or 3.'));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// ── Remote provider configuration ──
|
|
176
|
+
async function configureRemoteProvider(rl, config) {
|
|
177
|
+
console.log(pc.bold('\n 🌐 Remote API Configuration'));
|
|
178
|
+
console.log();
|
|
179
|
+
console.log(' Popular providers:');
|
|
180
|
+
console.log(' 1) OpenAI — https://api.openai.com/v1');
|
|
181
|
+
console.log(' 2) Groq — https://api.groq.com/openai/v1');
|
|
182
|
+
console.log(' 3) OpenRouter — https://openrouter.ai/api/v1');
|
|
183
|
+
console.log(' 4) Anthropic — https://api.anthropic.com/v1');
|
|
184
|
+
console.log(' 5) Custom URL');
|
|
185
|
+
console.log();
|
|
186
|
+
const presets = {
|
|
187
|
+
'1': { name: 'openai', endpoint: 'https://api.openai.com/v1' },
|
|
188
|
+
'2': { name: 'groq', endpoint: 'https://api.groq.com/openai/v1' },
|
|
189
|
+
'3': { name: 'openrouter', endpoint: 'https://openrouter.ai/api/v1' },
|
|
190
|
+
'4': { name: 'anthropic', endpoint: 'https://api.anthropic.com/v1' },
|
|
191
|
+
};
|
|
192
|
+
const choice = await question(rl, pc.bold(' Select provider (1-5): '));
|
|
193
|
+
const trimmed = choice.trim();
|
|
194
|
+
let baseUrl = '';
|
|
195
|
+
let providerName = '';
|
|
196
|
+
if (presets[trimmed]) {
|
|
197
|
+
baseUrl = presets[trimmed].endpoint;
|
|
198
|
+
providerName = presets[trimmed].name;
|
|
199
|
+
console.log(pc.gray(` Using: ${baseUrl}`));
|
|
200
|
+
}
|
|
201
|
+
else if (trimmed === '5') {
|
|
202
|
+
baseUrl = await question(rl, pc.bold(' Enter API base URL: '));
|
|
203
|
+
providerName = 'custom';
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
console.log(pc.yellow(' Invalid choice, defaulting to custom.'));
|
|
207
|
+
baseUrl = await question(rl, pc.bold(' Enter API base URL: '));
|
|
208
|
+
providerName = 'custom';
|
|
209
|
+
}
|
|
210
|
+
const apiKey = await question(rl, pc.bold(' Enter your API key (or leave blank if none): '));
|
|
211
|
+
// Validate — try a simple request
|
|
212
|
+
console.log(pc.gray('\n Testing connection...'));
|
|
213
|
+
try {
|
|
214
|
+
const testEndpoint = baseUrl.replace(/\/+$/, '') + '/models';
|
|
215
|
+
const res = await fetch(testEndpoint, {
|
|
216
|
+
headers: apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {},
|
|
217
|
+
signal: AbortSignal.timeout(10000),
|
|
218
|
+
});
|
|
219
|
+
if (res.ok) {
|
|
220
|
+
console.log(pc.green(' ✅ Connection successful!'));
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
console.log(pc.yellow(` ⚠ Server responded with status ${res.status} — but I'll save it anyway.`));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
console.log(pc.yellow(' ⚠ Could not reach the server — but I\'ll save it anyway.'));
|
|
228
|
+
}
|
|
229
|
+
addRemoteProvider(config, providerName, baseUrl, apiKey || undefined);
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=wizard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wizard.js","sourceRoot":"","sources":["../../src/onboarding/wizard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ModelEntry, ModelHealth } from './types.js';
|
|
2
|
+
export declare function checkModelHealth(model: ModelEntry, timeout: number): Promise<ModelHealth>;
|
|
3
|
+
export declare function getCachedHealth(model: ModelEntry): ModelHealth | undefined;
|
|
4
|
+
export declare function markHealthy(model: ModelEntry, latencyMs: number): void;
|
|
5
|
+
export declare function markUnhealthy(model: ModelEntry, error: string): void;
|
|
6
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/router/health.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAIrD,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAmD/F;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,CAG1E;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAQtE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CASpE"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Health checking for models
|
|
2
|
+
const healthCache = new Map();
|
|
3
|
+
export async function checkModelHealth(model, timeout) {
|
|
4
|
+
const cacheKey = `${model.endpoint}|${model.model}`;
|
|
5
|
+
const cached = healthCache.get(cacheKey);
|
|
6
|
+
// Return cached if recent (< 30s)
|
|
7
|
+
if (cached && Date.now() - cached.lastCheck < 30000) {
|
|
8
|
+
return cached;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const start = Date.now();
|
|
12
|
+
const controller = new AbortController();
|
|
13
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
14
|
+
// Use /models endpoint to check health
|
|
15
|
+
const response = await fetch(`${model.endpoint}/models`, {
|
|
16
|
+
signal: controller.signal,
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
});
|
|
19
|
+
clearTimeout(timeoutId);
|
|
20
|
+
const latency = Date.now() - start;
|
|
21
|
+
if (response.ok) {
|
|
22
|
+
const health = {
|
|
23
|
+
healthy: true,
|
|
24
|
+
lastCheck: Date.now(),
|
|
25
|
+
latencyMs: latency,
|
|
26
|
+
consecutiveFailures: 0,
|
|
27
|
+
};
|
|
28
|
+
healthCache.set(cacheKey, health);
|
|
29
|
+
return health;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
throw new Error(`HTTP ${response.status}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
const cached = healthCache.get(cacheKey) ?? {
|
|
37
|
+
healthy: false,
|
|
38
|
+
lastCheck: 0,
|
|
39
|
+
consecutiveFailures: 0,
|
|
40
|
+
};
|
|
41
|
+
const health = {
|
|
42
|
+
healthy: false,
|
|
43
|
+
lastCheck: Date.now(),
|
|
44
|
+
error: err.message,
|
|
45
|
+
consecutiveFailures: (cached?.consecutiveFailures ?? 0) + 1,
|
|
46
|
+
};
|
|
47
|
+
healthCache.set(cacheKey, health);
|
|
48
|
+
return health;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function getCachedHealth(model) {
|
|
52
|
+
const cacheKey = `${model.endpoint}|${model.model}`;
|
|
53
|
+
return healthCache.get(cacheKey);
|
|
54
|
+
}
|
|
55
|
+
export function markHealthy(model, latencyMs) {
|
|
56
|
+
const cacheKey = `${model.endpoint}|${model.model}`;
|
|
57
|
+
healthCache.set(cacheKey, {
|
|
58
|
+
healthy: true,
|
|
59
|
+
lastCheck: Date.now(),
|
|
60
|
+
latencyMs,
|
|
61
|
+
consecutiveFailures: 0,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export function markUnhealthy(model, error) {
|
|
65
|
+
const cacheKey = `${model.endpoint}|${model.model}`;
|
|
66
|
+
const cached = healthCache.get(cacheKey) ?? { healthy: false, lastCheck: 0, consecutiveFailures: 0 };
|
|
67
|
+
healthCache.set(cacheKey, {
|
|
68
|
+
healthy: false,
|
|
69
|
+
lastCheck: Date.now(),
|
|
70
|
+
error,
|
|
71
|
+
consecutiveFailures: cached.consecutiveFailures + 1,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/router/health.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ModelEntry, RouterConfig, RouteResult, ChatRequest, ChatResponse, StreamChunk } from './types.js';
|
|
2
|
+
export { RouteResult, RouterConfig };
|
|
3
|
+
export declare class LocalRouter {
|
|
4
|
+
private config;
|
|
5
|
+
private clients;
|
|
6
|
+
private healthCheckTimer;
|
|
7
|
+
private rateLimiters;
|
|
8
|
+
private roundRobinIndex;
|
|
9
|
+
private discoveredModels;
|
|
10
|
+
constructor(config: RouterConfig);
|
|
11
|
+
private initializeRateLimiters;
|
|
12
|
+
private getOrCreateClient;
|
|
13
|
+
startHealthChecks(): Promise<void>;
|
|
14
|
+
stopHealthChecks(): Promise<void>;
|
|
15
|
+
private runHealthChecks;
|
|
16
|
+
getEnabledModels(): ModelEntry[];
|
|
17
|
+
getHealthyModels(): ModelEntry[];
|
|
18
|
+
route(request: ChatRequest): Promise<RouteResult>;
|
|
19
|
+
private estimateTokens;
|
|
20
|
+
chatCompletion(request: ChatRequest): Promise<ChatResponse>;
|
|
21
|
+
chatStream(request: ChatRequest): AsyncGenerator<StreamChunk>;
|
|
22
|
+
private discoverModel;
|
|
23
|
+
listModels(): Promise<string[]>;
|
|
24
|
+
getConfig(): RouterConfig;
|
|
25
|
+
updateConfig(config: Partial<RouterConfig>): void;
|
|
26
|
+
get chat(): {
|
|
27
|
+
completions: {
|
|
28
|
+
create: (request: ChatRequest) => Promise<ChatResponse>;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export declare function createRouter(config?: Partial<RouterConfig>): LocalRouter;
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/router/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,UAAU,EAEV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC;AAIrC,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,gBAAgB,CAA+B;IACvD,OAAO,CAAC,YAAY,CAAgE;IACpF,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,gBAAgB,CAAkC;gBAE9C,MAAM,EAAE,YAAY;IAKhC,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,iBAAiB;IAenB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAUlC,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;YAOzB,eAAe;IAK7B,gBAAgB,IAAI,UAAU,EAAE;IAIhC,gBAAgB,IAAI,UAAU,EAAE;IAQ1B,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IA6CvD,OAAO,CAAC,cAAc;IAShB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAwB1D,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc,CAAC,WAAW,CAAC;YA4BtD,aAAa;IAiBrB,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAcrC,SAAS,IAAI,YAAY;IAIzB,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI;IAMjD,IAAI,IAAI;;8BAGgB,WAAW;;MAGlC;CACF;AAGD,wBAAgB,YAAY,CAAC,MAAM,GAAE,OAAO,CAAC,YAAY,CAAM,GAAG,WAAW,CAU5E"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// Daedalus Local Router - Main routing logic
|
|
2
|
+
import { OpenAI } from 'openai';
|
|
3
|
+
import { createTokenBucket, consumeTokens, getWaitTime } from './rate-limiter.js';
|
|
4
|
+
import { checkModelHealth, getCachedHealth, markHealthy, markUnhealthy } from './health.js';
|
|
5
|
+
export class LocalRouter {
|
|
6
|
+
config;
|
|
7
|
+
clients = new Map();
|
|
8
|
+
healthCheckTimer = null;
|
|
9
|
+
rateLimiters = new Map();
|
|
10
|
+
roundRobinIndex = 0;
|
|
11
|
+
discoveredModels = new Map(); // endpoint key -> model id
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.initializeRateLimiters();
|
|
15
|
+
}
|
|
16
|
+
initializeRateLimiters() {
|
|
17
|
+
for (const model of this.config.chain) {
|
|
18
|
+
if (model.enabled) {
|
|
19
|
+
const key = `${model.endpoint}|${model.model}`;
|
|
20
|
+
// Use TPM as capacity if configured, otherwise estimate from RPM (~4K tokens per request)
|
|
21
|
+
const tpm = this.config.defaultRateLimit.tpm || model.maxTokens || this.config.defaultRateLimit.rpm * 4000;
|
|
22
|
+
this.rateLimiters.set(key, createTokenBucket(tpm, tpm / 60));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
getOrCreateClient(model) {
|
|
27
|
+
const key = `${model.endpoint}|${model.model}`;
|
|
28
|
+
let client = this.clients.get(key);
|
|
29
|
+
if (!client) {
|
|
30
|
+
client = new OpenAI({
|
|
31
|
+
baseURL: model.endpoint,
|
|
32
|
+
apiKey: model.apiKey || 'not-needed', // Use apiKey from config if provided
|
|
33
|
+
timeout: this.config.requestTimeout,
|
|
34
|
+
});
|
|
35
|
+
this.clients.set(key, client);
|
|
36
|
+
}
|
|
37
|
+
return client;
|
|
38
|
+
}
|
|
39
|
+
async startHealthChecks() {
|
|
40
|
+
// Initial health check
|
|
41
|
+
await this.runHealthChecks();
|
|
42
|
+
// Periodic health checks
|
|
43
|
+
this.healthCheckTimer = setInterval(() => {
|
|
44
|
+
this.runHealthChecks();
|
|
45
|
+
}, this.config.healthCheckInterval);
|
|
46
|
+
}
|
|
47
|
+
async stopHealthChecks() {
|
|
48
|
+
if (this.healthCheckTimer) {
|
|
49
|
+
clearInterval(this.healthCheckTimer);
|
|
50
|
+
this.healthCheckTimer = null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async runHealthChecks() {
|
|
54
|
+
const enabledModels = this.config.chain.filter(m => m.enabled);
|
|
55
|
+
await Promise.all(enabledModels.map(m => checkModelHealth(m, 5000)));
|
|
56
|
+
}
|
|
57
|
+
getEnabledModels() {
|
|
58
|
+
return this.config.chain.filter(m => m.enabled);
|
|
59
|
+
}
|
|
60
|
+
getHealthyModels() {
|
|
61
|
+
const enabled = this.getEnabledModels();
|
|
62
|
+
return enabled.filter(m => {
|
|
63
|
+
const health = getCachedHealth(m);
|
|
64
|
+
return health?.healthy !== false; // Unknown = assume healthy
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async route(request) {
|
|
68
|
+
const healthyModels = this.getHealthyModels();
|
|
69
|
+
if (healthyModels.length === 0) {
|
|
70
|
+
throw new Error('No healthy models available. Check your local servers (LM Studio, Ollama, etc.)');
|
|
71
|
+
}
|
|
72
|
+
let selectedModel;
|
|
73
|
+
switch (this.config.strategy) {
|
|
74
|
+
case 'priority':
|
|
75
|
+
selectedModel = [...healthyModels].sort((a, b) => a.priority - b.priority)[0];
|
|
76
|
+
break;
|
|
77
|
+
case 'round-robin':
|
|
78
|
+
selectedModel = healthyModels[this.roundRobinIndex % healthyModels.length];
|
|
79
|
+
this.roundRobinIndex++;
|
|
80
|
+
break;
|
|
81
|
+
case 'fastest':
|
|
82
|
+
selectedModel = [...healthyModels].sort((a, b) => {
|
|
83
|
+
const ha = getCachedHealth(a);
|
|
84
|
+
const hb = getCachedHealth(b);
|
|
85
|
+
return (ha?.latencyMs ?? Infinity) - (hb?.latencyMs ?? Infinity);
|
|
86
|
+
})[0];
|
|
87
|
+
break;
|
|
88
|
+
default:
|
|
89
|
+
selectedModel = healthyModels[0];
|
|
90
|
+
}
|
|
91
|
+
// Check rate limit
|
|
92
|
+
const rateLimiter = this.rateLimiters.get(`${selectedModel.endpoint}|${selectedModel.model}`);
|
|
93
|
+
if (rateLimiter) {
|
|
94
|
+
const estimatedTokens = this.estimateTokens(request);
|
|
95
|
+
if (!consumeTokens(rateLimiter, estimatedTokens)) {
|
|
96
|
+
const waitMs = getWaitTime(rateLimiter, estimatedTokens);
|
|
97
|
+
throw new Error(`Rate limited. Wait ${waitMs}ms or try another model.`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const health = getCachedHealth(selectedModel) ?? { healthy: true, lastCheck: Date.now(), consecutiveFailures: 0 };
|
|
101
|
+
return { model: selectedModel, health };
|
|
102
|
+
}
|
|
103
|
+
estimateTokens(request) {
|
|
104
|
+
let totalChars = 0;
|
|
105
|
+
for (const msg of request.messages) {
|
|
106
|
+
totalChars += JSON.stringify(msg).length;
|
|
107
|
+
}
|
|
108
|
+
// Rough estimate: 4 chars per token
|
|
109
|
+
return Math.ceil(totalChars / 4) + (request.max_tokens ?? 4096);
|
|
110
|
+
}
|
|
111
|
+
async chatCompletion(request) {
|
|
112
|
+
const { model } = await this.route(request);
|
|
113
|
+
const client = this.getOrCreateClient(model);
|
|
114
|
+
const key = `${model.endpoint}|${model.model}`;
|
|
115
|
+
const actualModel = model.model === 'auto'
|
|
116
|
+
? await this.discoverModel(client, key)
|
|
117
|
+
: model.model;
|
|
118
|
+
try {
|
|
119
|
+
const start = Date.now();
|
|
120
|
+
const response = await client.chat.completions.create({
|
|
121
|
+
...request,
|
|
122
|
+
model: actualModel,
|
|
123
|
+
});
|
|
124
|
+
markHealthy(model, Date.now() - start);
|
|
125
|
+
return response;
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
markUnhealthy(model, err.message);
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async *chatStream(request) {
|
|
133
|
+
const { model } = await this.route(request);
|
|
134
|
+
const client = this.getOrCreateClient(model);
|
|
135
|
+
const key = `${model.endpoint}|${model.model}`;
|
|
136
|
+
const actualModel = model.model === 'auto'
|
|
137
|
+
? await this.discoverModel(client, key)
|
|
138
|
+
: model.model;
|
|
139
|
+
try {
|
|
140
|
+
const start = Date.now();
|
|
141
|
+
const stream = await client.chat.completions.create({
|
|
142
|
+
...request,
|
|
143
|
+
model: actualModel,
|
|
144
|
+
stream: true,
|
|
145
|
+
});
|
|
146
|
+
for await (const chunk of stream) {
|
|
147
|
+
yield chunk;
|
|
148
|
+
}
|
|
149
|
+
markHealthy(model, Date.now() - start);
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
markUnhealthy(model, err.message);
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async discoverModel(client, cacheKey) {
|
|
157
|
+
const cached = this.discoveredModels.get(cacheKey);
|
|
158
|
+
if (cached)
|
|
159
|
+
return cached;
|
|
160
|
+
try {
|
|
161
|
+
const models = await client.models.list();
|
|
162
|
+
if (models.data.length > 0) {
|
|
163
|
+
const id = models.data[0].id;
|
|
164
|
+
this.discoveredModels.set(cacheKey, id);
|
|
165
|
+
return id;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch { /* ignore — will throw below */ }
|
|
169
|
+
throw new Error(`No models found at ${cacheKey.split('|')[0]}. ` +
|
|
170
|
+
`Ensure your local server (LM Studio, Ollama, etc.) is running and has at least one model loaded.`);
|
|
171
|
+
}
|
|
172
|
+
async listModels() {
|
|
173
|
+
const models = [];
|
|
174
|
+
for (const entry of this.getEnabledModels()) {
|
|
175
|
+
try {
|
|
176
|
+
const client = this.getOrCreateClient(entry);
|
|
177
|
+
const list = await client.models.list();
|
|
178
|
+
for (const m of list.data) {
|
|
179
|
+
models.push(`${entry.name}:${m.id}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch { /* ignore */ }
|
|
183
|
+
}
|
|
184
|
+
return models;
|
|
185
|
+
}
|
|
186
|
+
getConfig() {
|
|
187
|
+
return { ...this.config };
|
|
188
|
+
}
|
|
189
|
+
updateConfig(config) {
|
|
190
|
+
this.config = { ...this.config, ...config };
|
|
191
|
+
this.initializeRateLimiters();
|
|
192
|
+
}
|
|
193
|
+
// OpenAI-compatible interface for delegation tool
|
|
194
|
+
get chat() {
|
|
195
|
+
return {
|
|
196
|
+
completions: {
|
|
197
|
+
create: (request) => this.chatCompletion(request),
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Factory function for easy creation
|
|
203
|
+
export function createRouter(config = {}) {
|
|
204
|
+
const defaultConfig = {
|
|
205
|
+
strategy: 'priority',
|
|
206
|
+
chain: [],
|
|
207
|
+
healthCheckInterval: 30000,
|
|
208
|
+
requestTimeout: 120000,
|
|
209
|
+
defaultRateLimit: { rpm: 60, tpm: 100000 },
|
|
210
|
+
...config,
|
|
211
|
+
};
|
|
212
|
+
return new LocalRouter(defaultConfig);
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/router/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { TokenBucket } from './types.js';
|
|
2
|
+
export declare function createTokenBucket(capacity: number, refillRate: number): TokenBucket;
|
|
3
|
+
export declare function consumeTokens(bucket: TokenBucket, tokens: number): boolean;
|
|
4
|
+
export declare function refillBucket(bucket: TokenBucket): void;
|
|
5
|
+
export declare function getAvailableTokens(bucket: TokenBucket): number;
|
|
6
|
+
export declare function getWaitTime(bucket: TokenBucket, tokens: number): number;
|
|
7
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/router/rate-limiter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,CAOnF;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAO1E;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAMtD;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAG9D;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAKvE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Token bucket rate limiter
|
|
2
|
+
export function createTokenBucket(capacity, refillRate) {
|
|
3
|
+
return {
|
|
4
|
+
tokens: capacity,
|
|
5
|
+
lastRefill: Date.now(),
|
|
6
|
+
capacity,
|
|
7
|
+
refillRate,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function consumeTokens(bucket, tokens) {
|
|
11
|
+
refillBucket(bucket);
|
|
12
|
+
if (bucket.tokens >= tokens) {
|
|
13
|
+
bucket.tokens -= tokens;
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
export function refillBucket(bucket) {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
const elapsedSeconds = (now - bucket.lastRefill) / 1000;
|
|
21
|
+
const tokensToAdd = elapsedSeconds * bucket.refillRate;
|
|
22
|
+
bucket.tokens = Math.min(bucket.capacity, bucket.tokens + tokensToAdd);
|
|
23
|
+
bucket.lastRefill = now;
|
|
24
|
+
}
|
|
25
|
+
export function getAvailableTokens(bucket) {
|
|
26
|
+
refillBucket(bucket);
|
|
27
|
+
return Math.floor(bucket.tokens);
|
|
28
|
+
}
|
|
29
|
+
export function getWaitTime(bucket, tokens) {
|
|
30
|
+
refillBucket(bucket);
|
|
31
|
+
if (bucket.tokens >= tokens)
|
|
32
|
+
return 0;
|
|
33
|
+
const needed = tokens - bucket.tokens;
|
|
34
|
+
return Math.ceil((needed / bucket.refillRate) * 1000);
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=rate-limiter.js.map
|