daedalus-cli 3.7.1 → 3.7.3
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/CHANGELOG.md +14 -0
- package/README.md +3 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +106 -3
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/index.js +1 -1
- package/dist/commands/index.js.map +1 -1
- package/dist/config/index.d.ts +45 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +18 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/systemInfo.d.ts.map +1 -1
- package/dist/config/systemInfo.js +2 -1
- package/dist/config/systemInfo.js.map +1 -1
- package/dist/index.js +30 -14
- package/dist/index.js.map +1 -1
- package/dist/router/health.d.ts +2 -0
- package/dist/router/health.d.ts.map +1 -1
- package/dist/router/health.js +85 -0
- package/dist/router/health.js.map +1 -1
- package/dist/router/health.test.js +38 -1
- package/dist/router/health.test.js.map +1 -1
- package/dist/router/index.d.ts +30 -0
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +168 -13
- package/dist/router/index.js.map +1 -1
- package/dist/router/index.test.js +180 -0
- package/dist/router/index.test.js.map +1 -1
- package/dist/router/types.d.ts +5 -0
- package/dist/router/types.d.ts.map +1 -1
- package/dist/tools/mcp/registry.d.ts.map +1 -1
- package/dist/tools/mcp/registry.js +3 -1
- package/dist/tools/mcp/registry.js.map +1 -1
- package/dist/tui/widgets/model-select.d.ts +13 -1
- package/dist/tui/widgets/model-select.d.ts.map +1 -1
- package/dist/tui/widgets/model-select.js +29 -3
- package/dist/tui/widgets/model-select.js.map +1 -1
- package/dist/tui/widgets/model-select.test.d.ts +2 -0
- package/dist/tui/widgets/model-select.test.d.ts.map +1 -0
- package/dist/tui/widgets/model-select.test.js +38 -0
- package/dist/tui/widgets/model-select.test.js.map +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/config/hello.d.ts +0 -2
- package/dist/config/hello.d.ts.map +0 -1
- package/dist/config/hello.js +0 -3
- package/dist/config/hello.js.map +0 -1
package/dist/router/health.js
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
1
|
// Health checking for models
|
|
2
2
|
const healthCache = new Map();
|
|
3
|
+
const catalogCache = new Map();
|
|
4
|
+
const CATALOG_TTL = 5 * 60 * 1000;
|
|
5
|
+
function catalogKey(endpoint) {
|
|
6
|
+
let baseUrl = endpoint;
|
|
7
|
+
if (!/^https?:\/\//i.test(baseUrl)) {
|
|
8
|
+
baseUrl = 'https://' + baseUrl;
|
|
9
|
+
}
|
|
10
|
+
return baseUrl;
|
|
11
|
+
}
|
|
12
|
+
async function fetchEndpointCatalog(endpoint, apiKey) {
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
|
15
|
+
try {
|
|
16
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
17
|
+
if (apiKey) {
|
|
18
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
19
|
+
}
|
|
20
|
+
const response = await fetch(`${catalogKey(endpoint)}/models`, {
|
|
21
|
+
signal: controller.signal,
|
|
22
|
+
headers,
|
|
23
|
+
});
|
|
24
|
+
if (!response.ok)
|
|
25
|
+
return null;
|
|
26
|
+
const data = await response.json();
|
|
27
|
+
const ids = new Set();
|
|
28
|
+
for (const m of data?.data ?? []) {
|
|
29
|
+
if (m && typeof m.id === 'string')
|
|
30
|
+
ids.add(m.id);
|
|
31
|
+
}
|
|
32
|
+
catalogCache.set(endpoint, { ids, fetchedAt: Date.now() });
|
|
33
|
+
return ids;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
clearTimeout(timeoutId);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function getCachedCatalog(endpoint) {
|
|
43
|
+
const cached = catalogCache.get(endpoint);
|
|
44
|
+
if (cached && Date.now() - cached.fetchedAt < CATALOG_TTL)
|
|
45
|
+
return cached.ids;
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
export async function getEndpointCatalog(endpoint, apiKey) {
|
|
49
|
+
const cached = getCachedCatalog(endpoint);
|
|
50
|
+
if (cached)
|
|
51
|
+
return cached;
|
|
52
|
+
return fetchEndpointCatalog(endpoint, apiKey);
|
|
53
|
+
}
|
|
54
|
+
function notInCatalogHealth(model, cached) {
|
|
55
|
+
return {
|
|
56
|
+
healthy: false,
|
|
57
|
+
lastCheck: Date.now(),
|
|
58
|
+
error: `Model '${model.model}' is not in the catalog served by this endpoint`,
|
|
59
|
+
consecutiveFailures: (cached?.consecutiveFailures ?? 0) + 1,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
3
62
|
export async function checkModelHealth(model, timeout) {
|
|
4
63
|
const cacheKey = `${model.endpoint}|${model.model}`;
|
|
5
64
|
const cached = healthCache.get(cacheKey);
|
|
@@ -7,6 +66,13 @@ export async function checkModelHealth(model, timeout) {
|
|
|
7
66
|
if (cached && Date.now() - cached.lastCheck < 30000) {
|
|
8
67
|
return cached;
|
|
9
68
|
}
|
|
69
|
+
// Short-circuit when the endpoint catalog is cached and the model id is absent
|
|
70
|
+
const cachedCatalog = getCachedCatalog(model.endpoint);
|
|
71
|
+
if (model.model !== 'auto' && cachedCatalog && !cachedCatalog.has(model.model)) {
|
|
72
|
+
const health = notInCatalogHealth(model, cached);
|
|
73
|
+
healthCache.set(cacheKey, health);
|
|
74
|
+
return health;
|
|
75
|
+
}
|
|
10
76
|
try {
|
|
11
77
|
const start = Date.now();
|
|
12
78
|
const controller = new AbortController();
|
|
@@ -28,6 +94,25 @@ export async function checkModelHealth(model, timeout) {
|
|
|
28
94
|
clearTimeout(timeoutId);
|
|
29
95
|
const latency = Date.now() - start;
|
|
30
96
|
if (response.ok) {
|
|
97
|
+
const ids = new Set();
|
|
98
|
+
try {
|
|
99
|
+
const data = await response.json();
|
|
100
|
+
for (const m of data?.data ?? []) {
|
|
101
|
+
if (m && typeof m.id === 'string')
|
|
102
|
+
ids.add(m.id);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Non-JSON /models response — catalog unknown, skip membership check
|
|
107
|
+
}
|
|
108
|
+
if (ids.size > 0) {
|
|
109
|
+
catalogCache.set(model.endpoint, { ids, fetchedAt: Date.now() });
|
|
110
|
+
}
|
|
111
|
+
if (model.model !== 'auto' && ids.size > 0 && !ids.has(model.model)) {
|
|
112
|
+
const health = notInCatalogHealth(model, cached);
|
|
113
|
+
healthCache.set(cacheKey, health);
|
|
114
|
+
return health;
|
|
115
|
+
}
|
|
31
116
|
const health = {
|
|
32
117
|
healthy: true,
|
|
33
118
|
lastCheck: Date.now(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/router/health.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/router/health.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
-
import { checkModelHealth, getCachedHealth, markHealthy, markUnhealthy, } from './health.js';
|
|
2
|
+
import { checkModelHealth, getCachedHealth, getEndpointCatalog, markHealthy, markUnhealthy, } from './health.js';
|
|
3
3
|
let modelCounter = 0;
|
|
4
4
|
function makeModel(overrides = {}) {
|
|
5
5
|
modelCounter++;
|
|
@@ -83,5 +83,42 @@ describe('Model health checking', () => {
|
|
|
83
83
|
expect(fetchSpy).toHaveBeenCalled();
|
|
84
84
|
fetchSpy.mockRestore();
|
|
85
85
|
});
|
|
86
|
+
it('marks a model unhealthy when its id is missing from the endpoint catalog', async () => {
|
|
87
|
+
const model = makeModel({ model: 'openai/gpt-4.1' });
|
|
88
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValue({
|
|
89
|
+
ok: true,
|
|
90
|
+
status: 200,
|
|
91
|
+
json: async () => ({ data: [{ id: 'openai/gpt-oss-120b' }, { id: 'openai/gpt-5' }] }),
|
|
92
|
+
});
|
|
93
|
+
const health = await checkModelHealth(model, 1000);
|
|
94
|
+
expect(health.healthy).toBe(false);
|
|
95
|
+
expect(health.error).toContain('not in the catalog');
|
|
96
|
+
});
|
|
97
|
+
it('marks a model healthy when its id is in the endpoint catalog', async () => {
|
|
98
|
+
const model = makeModel({ model: 'openai/gpt-oss-120b' });
|
|
99
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValue({
|
|
100
|
+
ok: true,
|
|
101
|
+
status: 200,
|
|
102
|
+
json: async () => ({ data: [{ id: 'openai/gpt-oss-120b' }] }),
|
|
103
|
+
});
|
|
104
|
+
const health = await checkModelHealth(model, 1000);
|
|
105
|
+
expect(health.healthy).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
it('treats auto-routed models as catalog-agnostic', async () => {
|
|
108
|
+
const model = makeModel({ model: 'auto' });
|
|
109
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValue({
|
|
110
|
+
ok: true,
|
|
111
|
+
status: 200,
|
|
112
|
+
json: async () => ({ data: [{ id: 'some-unlisted-id' }] }),
|
|
113
|
+
});
|
|
114
|
+
const health = await checkModelHealth(model, 1000);
|
|
115
|
+
expect(health.healthy).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
it('getEndpointCatalog returns null when the fetch fails', async () => {
|
|
118
|
+
const model = makeModel();
|
|
119
|
+
vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('network down'));
|
|
120
|
+
const catalog = await getEndpointCatalog(model.endpoint, model.apiKey);
|
|
121
|
+
expect(catalog).toBeNull();
|
|
122
|
+
});
|
|
86
123
|
});
|
|
87
124
|
//# sourceMappingURL=health.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"health.test.js","sourceRoot":"","sources":["../../src/router/health.test.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"health.test.js","sourceRoot":"","sources":["../../src/router/health.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,aAAa,GACd,MAAM,aAAa,CAAC;AAGrB,IAAI,YAAY,GAAG,CAAC,CAAC;AAErB,SAAS,SAAS,CAAC,YAAiC,EAAE;IACpD,YAAY,EAAE,CAAC;IACf,OAAO;QACL,IAAI,EAAE,cAAc,YAAY,EAAE;QAClC,QAAQ,EAAE,oBAAoB,IAAI,GAAG,YAAY,KAAK;QACtD,KAAK,EAAE,YAAY,YAAY,EAAE;QACjC,QAAQ,EAAE,CAAC;QACX,OAAO,EAAE,IAAI;QACb,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IAErC,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,eAAe,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,QAAQ,EAAE,uBAAuB,EAAE,CAAC,CAAC;QAC/D,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;QAC1B,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC9B,MAAM,EAAE,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QAClC,MAAM,CAAC,EAAG,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAExC,aAAa,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QACpC,MAAM,EAAE,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QAClC,MAAM,CAAC,EAAG,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;QAC1B,WAAW,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACvB,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;QAC7B,MAAM,CAAC,MAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,CAAC,MAAO,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC;QACnD,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;QACjD,WAAW,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACvB,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;QAC7B,MAAM,CAAC,MAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,CAAC,MAAO,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnC,MAAM,CAAC,MAAO,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC;QACnD,aAAa,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAChC,MAAM,EAAE,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QAClC,MAAM,CAAC,EAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,CAAC,EAAG,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAExC,aAAa,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;QACtC,MAAM,EAAE,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QAClC,MAAM,CAAC,EAAG,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;QACpD,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC9B,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACxB,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QACtC,MAAM,CAAC,MAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,CAAC,MAAO,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;QACxD,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,QAAQ,EAAE,mBAAmB,EAAE,CAAC,CAAC;QAC3D,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAClD,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;QAC7B,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;QAC5F,MAAM,gBAAgB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACnC,MAAM,CAAC,QAAQ,CAAC,CAAC,gBAAgB,EAAE,CAAC;QACpC,QAAQ,CAAC,WAAW,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;QACxF,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;QACrD,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,iBAAiB,CAAC;YAC9C,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,qBAAqB,EAAE,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC;SAC7E,CAAC,CAAC;QACZ,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC,CAAC;QAC1D,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,iBAAiB,CAAC;YAC9C,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,qBAAqB,EAAE,CAAC,EAAE,CAAC;SACrD,CAAC,CAAC;QACZ,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3C,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,iBAAiB,CAAC;YAC9C,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,kBAAkB,EAAE,CAAC,EAAE,CAAC;SAClD,CAAC,CAAC;QACZ,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;QAC1B,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;QAC3E,MAAM,OAAO,GAAG,MAAM,kBAAkB,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACvE,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC7B,CAAC,CAAC,CAAC;AAEL,CAAC,CAAC,CAAC"}
|
package/dist/router/index.d.ts
CHANGED
|
@@ -12,8 +12,38 @@ export declare class LocalRouter {
|
|
|
12
12
|
lastRoutedModel?: string;
|
|
13
13
|
lastRoutedModelName?: string;
|
|
14
14
|
lastRoutedTier?: string;
|
|
15
|
+
lastRouteDecision?: {
|
|
16
|
+
model: ModelEntry;
|
|
17
|
+
reason: string;
|
|
18
|
+
skipped: Array<{
|
|
19
|
+
endpoint: string;
|
|
20
|
+
model: string;
|
|
21
|
+
reason: string;
|
|
22
|
+
}>;
|
|
23
|
+
};
|
|
15
24
|
private routeStats;
|
|
25
|
+
private sessionBlacklist;
|
|
26
|
+
private latencyEma;
|
|
27
|
+
private readonly slowAlpha;
|
|
16
28
|
constructor(config: RouterConfig);
|
|
29
|
+
getSessionBlacklist(): Array<{
|
|
30
|
+
endpoint: string;
|
|
31
|
+
model: string;
|
|
32
|
+
reason: string;
|
|
33
|
+
at: number;
|
|
34
|
+
}>;
|
|
35
|
+
getLastRouteDecision(): typeof this.lastRouteDecision;
|
|
36
|
+
getLatencyEma(): Array<{
|
|
37
|
+
endpoint: string;
|
|
38
|
+
model: string;
|
|
39
|
+
emaMs: number;
|
|
40
|
+
thresholdMs: number;
|
|
41
|
+
}>;
|
|
42
|
+
clearSessionBlacklist(): void;
|
|
43
|
+
addToSessionBlacklist(endpoint: string, model: string, reason: string): void;
|
|
44
|
+
private blacklistModel;
|
|
45
|
+
private isBlacklisted;
|
|
46
|
+
private recordLatency;
|
|
17
47
|
private initializeRateLimiters;
|
|
18
48
|
private getOrCreateClient;
|
|
19
49
|
startHealthChecks(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/router/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,KAAK,EAAE,WAAW,EAA0C,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EACV,UAAU,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/router/index.ts"],"names":[],"mappings":"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"}
|
package/dist/router/index.js
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
// Daedalus Local Router - Main routing logic
|
|
2
2
|
import { OpenAI } from 'openai';
|
|
3
3
|
import { createTokenBucket, consumeTokens, getWaitTime } from './rate-limiter.js';
|
|
4
|
-
import { checkModelHealth, getCachedHealth, markHealthy, markUnhealthy } from './health.js';
|
|
4
|
+
import { checkModelHealth, getCachedHealth, getEndpointCatalog, markHealthy, markUnhealthy } from './health.js';
|
|
5
|
+
function isHardFailure(err) {
|
|
6
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7
|
+
if (/not in the catalog/i.test(msg))
|
|
8
|
+
return true;
|
|
9
|
+
if (err instanceof Error && (err.name === 'AbortError' || err.name === 'APIUserAbortError'))
|
|
10
|
+
return true;
|
|
11
|
+
if (err instanceof Error && err.name === 'APIConnectionTimeoutError')
|
|
12
|
+
return true;
|
|
13
|
+
if (/timeout|timed out|timedout/i.test(msg))
|
|
14
|
+
return true;
|
|
15
|
+
const status = err?.status
|
|
16
|
+
?? err?.statusCode;
|
|
17
|
+
if (typeof status === 'number' && status >= 400 && status < 500 && status !== 429)
|
|
18
|
+
return true;
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
5
21
|
export class LocalRouter {
|
|
6
22
|
config;
|
|
7
23
|
clients = new Map();
|
|
@@ -12,11 +28,63 @@ export class LocalRouter {
|
|
|
12
28
|
lastRoutedModel;
|
|
13
29
|
lastRoutedModelName;
|
|
14
30
|
lastRoutedTier;
|
|
31
|
+
lastRouteDecision;
|
|
15
32
|
routeStats = { fast: 0, standard: 0, complex: 0, override: 0 };
|
|
33
|
+
sessionBlacklist = new Map();
|
|
34
|
+
latencyEma = new Map();
|
|
35
|
+
slowAlpha = 0.3;
|
|
16
36
|
constructor(config) {
|
|
17
37
|
this.config = config;
|
|
18
38
|
this.initializeRateLimiters();
|
|
19
39
|
}
|
|
40
|
+
getSessionBlacklist() {
|
|
41
|
+
return Array.from(this.sessionBlacklist.entries()).map(([key, entry]) => {
|
|
42
|
+
const [endpoint, model] = key.split('|');
|
|
43
|
+
return { endpoint, model, reason: entry.reason, at: entry.at };
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
getLastRouteDecision() {
|
|
47
|
+
return this.lastRouteDecision;
|
|
48
|
+
}
|
|
49
|
+
getLatencyEma() {
|
|
50
|
+
const threshold = this.config.slowModelThresholdMs ?? 0;
|
|
51
|
+
return Array.from(this.latencyEma.entries()).map(([key, ema]) => {
|
|
52
|
+
const [endpoint, model] = key.split('|');
|
|
53
|
+
return { endpoint, model, emaMs: Math.round(ema), thresholdMs: threshold };
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
clearSessionBlacklist() {
|
|
57
|
+
this.sessionBlacklist.clear();
|
|
58
|
+
}
|
|
59
|
+
addToSessionBlacklist(endpoint, model, reason) {
|
|
60
|
+
const entry = this.config.chain.find(m => m.endpoint === endpoint && m.model === model);
|
|
61
|
+
if (entry) {
|
|
62
|
+
this.blacklistModel(entry, reason);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
this.sessionBlacklist.set(`${endpoint}|${model}`, { reason, at: Date.now() });
|
|
66
|
+
markUnhealthy({ endpoint, model }, reason);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
blacklistModel(m, reason) {
|
|
70
|
+
this.sessionBlacklist.set(`${m.endpoint}|${m.model}`, { reason, at: Date.now() });
|
|
71
|
+
markUnhealthy(m, reason);
|
|
72
|
+
}
|
|
73
|
+
isBlacklisted(m) {
|
|
74
|
+
return this.sessionBlacklist.has(`${m.endpoint}|${m.model}`);
|
|
75
|
+
}
|
|
76
|
+
recordLatency(m, latencyMs) {
|
|
77
|
+
const threshold = this.config.slowModelThresholdMs ?? 0;
|
|
78
|
+
if (threshold <= 0 || this.isBlacklisted(m))
|
|
79
|
+
return;
|
|
80
|
+
const key = `${m.endpoint}|${m.model}`;
|
|
81
|
+
const prev = this.latencyEma.get(key) ?? latencyMs;
|
|
82
|
+
const ema = prev + this.slowAlpha * (latencyMs - prev);
|
|
83
|
+
this.latencyEma.set(key, ema);
|
|
84
|
+
if (ema > threshold) {
|
|
85
|
+
this.blacklistModel(m, `Avg latency ${Math.round(ema)}ms exceeds threshold ${threshold}ms`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
20
88
|
initializeRateLimiters() {
|
|
21
89
|
for (const model of this.config.chain) {
|
|
22
90
|
if (model.enabled) {
|
|
@@ -75,8 +143,19 @@ export class LocalRouter {
|
|
|
75
143
|
}
|
|
76
144
|
await Promise.all(Array.from(uniqueEndpoints.values()).map(async (m) => {
|
|
77
145
|
const health = await checkModelHealth(m, 5000);
|
|
146
|
+
const catalog = await getEndpointCatalog(m.endpoint, m.apiKey);
|
|
147
|
+
const endpointDown = !health.healthy && !/not in the catalog/i.test(health.error ?? '');
|
|
78
148
|
for (const target of enabledModels.filter(e => e.endpoint === m.endpoint)) {
|
|
79
|
-
if (
|
|
149
|
+
if (this.isBlacklisted(target))
|
|
150
|
+
continue;
|
|
151
|
+
if (endpointDown) {
|
|
152
|
+
markUnhealthy(target, health.error ?? 'unhealthy');
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (target.model !== 'auto' && catalog && !catalog.has(target.model)) {
|
|
156
|
+
markUnhealthy(target, `Model '${target.model}' is not in the catalog served by this endpoint`);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
80
159
|
markHealthy(target, health.latencyMs ?? 0);
|
|
81
160
|
}
|
|
82
161
|
}
|
|
@@ -108,6 +187,8 @@ export class LocalRouter {
|
|
|
108
187
|
continue;
|
|
109
188
|
if (candidate.supportsTools === false)
|
|
110
189
|
continue;
|
|
190
|
+
if (this.isBlacklisted(candidate))
|
|
191
|
+
continue;
|
|
111
192
|
const health = getCachedHealth(candidate);
|
|
112
193
|
if (health?.healthy === false)
|
|
113
194
|
continue;
|
|
@@ -116,12 +197,51 @@ export class LocalRouter {
|
|
|
116
197
|
return undefined;
|
|
117
198
|
}
|
|
118
199
|
async route(request, excludedModels) {
|
|
119
|
-
|
|
200
|
+
const skipped = [];
|
|
201
|
+
const noteSkipped = (m, reason) => {
|
|
202
|
+
if (!skipped.some(s => s.endpoint === m.endpoint && s.model === m.model && s.reason === reason)) {
|
|
203
|
+
skipped.push({ endpoint: m.endpoint, model: m.model, reason });
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
for (const m of this.getEnabledModels()) {
|
|
207
|
+
if (this.isBlacklisted(m))
|
|
208
|
+
noteSkipped(m, 'session-blacklisted');
|
|
209
|
+
}
|
|
210
|
+
let healthyModels = this.getHealthyModels().filter(m => {
|
|
211
|
+
if (this.isBlacklisted(m)) {
|
|
212
|
+
noteSkipped(m, 'session-blacklisted');
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
});
|
|
120
217
|
if (excludedModels && excludedModels.size > 0) {
|
|
121
|
-
healthyModels = healthyModels.filter(m =>
|
|
218
|
+
healthyModels = healthyModels.filter(m => {
|
|
219
|
+
if (excludedModels.has(m.name) || excludedModels.has(m.model)) {
|
|
220
|
+
noteSkipped(m, 'excluded-this-turn');
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
return true;
|
|
224
|
+
});
|
|
122
225
|
}
|
|
123
226
|
if (healthyModels.length === 0) {
|
|
124
|
-
healthyModels = this.config.chain.filter(m =>
|
|
227
|
+
healthyModels = this.config.chain.filter(m => {
|
|
228
|
+
if (m.enabled && !this.isBlacklisted(m) &&
|
|
229
|
+
(!excludedModels || (!excludedModels.has(m.name) && !excludedModels.has(m.model)))) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
if (m.enabled && this.isBlacklisted(m)) {
|
|
233
|
+
noteSkipped(m, 'session-blacklisted');
|
|
234
|
+
}
|
|
235
|
+
return false;
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (healthyModels.length === 0) {
|
|
239
|
+
healthyModels = this.config.chain.filter(m => {
|
|
240
|
+
if (m.enabled && (!excludedModels || (!excludedModels.has(m.name) && !excludedModels.has(m.model)))) {
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
return false;
|
|
244
|
+
});
|
|
125
245
|
}
|
|
126
246
|
if (healthyModels.length === 0) {
|
|
127
247
|
throw new Error('No healthy models available. Check your local servers (LM Studio, Ollama, etc.)');
|
|
@@ -159,7 +279,12 @@ export class LocalRouter {
|
|
|
159
279
|
const health = getCachedHealth(selectedModel) ?? { healthy: true, lastCheck: Date.now(), consecutiveFailures: 0 };
|
|
160
280
|
this.routeStats.override++;
|
|
161
281
|
this.lastRoutedTier = selectedModel.tier;
|
|
162
|
-
|
|
282
|
+
this.lastRouteDecision = {
|
|
283
|
+
model: selectedModel,
|
|
284
|
+
reason: `model override '${request.model}'`,
|
|
285
|
+
skipped: skipped.map(s => ({ ...s })),
|
|
286
|
+
};
|
|
287
|
+
return { model: selectedModel, health, reason: `model override '${request.model}'`, skipped: skipped.map(s => ({ ...s })) };
|
|
163
288
|
}
|
|
164
289
|
}
|
|
165
290
|
}
|
|
@@ -278,7 +403,9 @@ export class LocalRouter {
|
|
|
278
403
|
}
|
|
279
404
|
this.lastRoutedTier = targetTier === 'fast' ? 'fast' : targetTier === 'standard' ? 'standard' : 'intelligence';
|
|
280
405
|
const health = getCachedHealth(selectedModel) ?? { healthy: true, lastCheck: Date.now(), consecutiveFailures: 0 };
|
|
281
|
-
|
|
406
|
+
const reason = `tier '${targetTier}' via ${this.config.strategy} strategy`;
|
|
407
|
+
this.lastRouteDecision = { model: selectedModel, reason, skipped: skipped.map(s => ({ ...s })) };
|
|
408
|
+
return { model: selectedModel, health, reason, skipped: skipped.map(s => ({ ...s })) };
|
|
282
409
|
}
|
|
283
410
|
estimateTokens(request) {
|
|
284
411
|
let totalChars = 0;
|
|
@@ -316,6 +443,13 @@ export class LocalRouter {
|
|
|
316
443
|
try {
|
|
317
444
|
const { model } = await this.route(request, excludedModels);
|
|
318
445
|
selectedModel = model;
|
|
446
|
+
if (process.env.DAEDALUS_DEBUG) {
|
|
447
|
+
const skipped = this.lastRouteDecision?.skipped ?? [];
|
|
448
|
+
const skipInfo = skipped.length > 0
|
|
449
|
+
? ` (skipped ${skipped.length}: ${skipped.map(s => `${s.model}[${s.reason}]`).join(', ')})`
|
|
450
|
+
: '';
|
|
451
|
+
console.error(`[ROUTE] ${model.name} — ${this.lastRouteDecision?.reason ?? 'n/a'}${skipInfo}`);
|
|
452
|
+
}
|
|
319
453
|
const client = this.getOrCreateClient(model);
|
|
320
454
|
const key = `${model.endpoint}|${model.model}`;
|
|
321
455
|
const actualModel = model.model === 'auto'
|
|
@@ -325,7 +459,7 @@ export class LocalRouter {
|
|
|
325
459
|
this.lastRoutedModelName = model.name;
|
|
326
460
|
const { signal, ...body } = request;
|
|
327
461
|
delete body.complexity;
|
|
328
|
-
const isOfficialOpenAI = model.endpoint.includes('api.openai.com');
|
|
462
|
+
const isOfficialOpenAI = model.provider === 'openai' || model.endpoint.includes('api.openai.com');
|
|
329
463
|
if (body.tool_choice === 'required' && !isOfficialOpenAI) {
|
|
330
464
|
body.tool_choice = 'auto';
|
|
331
465
|
}
|
|
@@ -342,7 +476,9 @@ export class LocalRouter {
|
|
|
342
476
|
messages: sanitizedMessages,
|
|
343
477
|
model: actualModel,
|
|
344
478
|
}, { signal });
|
|
345
|
-
|
|
479
|
+
const elapsed = Date.now() - start;
|
|
480
|
+
markHealthy(model, elapsed);
|
|
481
|
+
this.recordLatency(model, elapsed);
|
|
346
482
|
return response;
|
|
347
483
|
}
|
|
348
484
|
catch (err) {
|
|
@@ -350,7 +486,12 @@ export class LocalRouter {
|
|
|
350
486
|
if (err instanceof Error && (err.name === 'AbortError' || err.name === 'APIUserAbortError'))
|
|
351
487
|
throw err;
|
|
352
488
|
if (selectedModel) {
|
|
353
|
-
|
|
489
|
+
if (isHardFailure(err)) {
|
|
490
|
+
this.blacklistModel(selectedModel, err instanceof Error ? err.message : String(err));
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
markUnhealthy(selectedModel, err instanceof Error ? err.message : String(err));
|
|
494
|
+
}
|
|
354
495
|
excludedModels.add(selectedModel.name);
|
|
355
496
|
excludedModels.add(selectedModel.model);
|
|
356
497
|
}
|
|
@@ -369,6 +510,13 @@ export class LocalRouter {
|
|
|
369
510
|
try {
|
|
370
511
|
const { model } = await this.route(request, excludedModels);
|
|
371
512
|
selectedModel = model;
|
|
513
|
+
if (process.env.DAEDALUS_DEBUG) {
|
|
514
|
+
const skipped = this.lastRouteDecision?.skipped ?? [];
|
|
515
|
+
const skipInfo = skipped.length > 0
|
|
516
|
+
? ` (skipped ${skipped.length}: ${skipped.map(s => `${s.model}[${s.reason}]`).join(', ')})`
|
|
517
|
+
: '';
|
|
518
|
+
console.error(`[ROUTE] ${model.name} — ${this.lastRouteDecision?.reason ?? 'n/a'}${skipInfo}`);
|
|
519
|
+
}
|
|
372
520
|
const client = this.getOrCreateClient(model);
|
|
373
521
|
const key = `${model.endpoint}|${model.model}`;
|
|
374
522
|
const actualModel = model.model === 'auto'
|
|
@@ -378,7 +526,7 @@ export class LocalRouter {
|
|
|
378
526
|
this.lastRoutedModelName = model.name;
|
|
379
527
|
const { signal, ...body } = request;
|
|
380
528
|
delete body.complexity;
|
|
381
|
-
const isOfficialOpenAI = model.endpoint.includes('api.openai.com');
|
|
529
|
+
const isOfficialOpenAI = model.provider === 'openai' || model.endpoint.includes('api.openai.com');
|
|
382
530
|
if (body.tool_choice === 'required' && !isOfficialOpenAI) {
|
|
383
531
|
body.tool_choice = 'auto';
|
|
384
532
|
}
|
|
@@ -401,7 +549,9 @@ export class LocalRouter {
|
|
|
401
549
|
for await (const chunk of stream) {
|
|
402
550
|
yield chunk;
|
|
403
551
|
}
|
|
404
|
-
|
|
552
|
+
const elapsed = Date.now() - start;
|
|
553
|
+
markHealthy(model, elapsed);
|
|
554
|
+
this.recordLatency(model, elapsed);
|
|
405
555
|
return;
|
|
406
556
|
}
|
|
407
557
|
catch (err) {
|
|
@@ -409,7 +559,12 @@ export class LocalRouter {
|
|
|
409
559
|
if (err instanceof Error && (err.name === 'AbortError' || err.name === 'APIUserAbortError'))
|
|
410
560
|
throw err;
|
|
411
561
|
if (selectedModel) {
|
|
412
|
-
|
|
562
|
+
if (isHardFailure(err)) {
|
|
563
|
+
this.blacklistModel(selectedModel, err instanceof Error ? err.message : String(err));
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
markUnhealthy(selectedModel, err instanceof Error ? err.message : String(err));
|
|
567
|
+
}
|
|
413
568
|
excludedModels.add(selectedModel.name);
|
|
414
569
|
excludedModels.add(selectedModel.model);
|
|
415
570
|
}
|