waypoi 0.0.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/.github/instructions/ui.instructions.md +42 -0
- package/.github/workflows/ci.yml +35 -0
- package/.github/workflows/publish.yml +71 -0
- package/.github/workflows/release.yml +48 -0
- package/.playwright-mcp/console-2026-04-04T01-41-10-746Z.log +2 -0
- package/.playwright-mcp/console-2026-04-04T01-41-28-799Z.log +3 -0
- package/.playwright-mcp/console-2026-04-05T02-26-51-909Z.log +76 -0
- package/.playwright-mcp/page-2026-04-04T01-41-10-816Z.yml +1 -0
- package/.playwright-mcp/page-2026-04-04T01-41-29-141Z.yml +77 -0
- package/.playwright-mcp/page-2026-04-04T01-41-42-633Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-04T01-42-03-929Z.yml +262 -0
- package/.playwright-mcp/page-2026-04-04T02-12-54-813Z.yml +6 -0
- package/.playwright-mcp/page-2026-04-04T02-14-58-600Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-04T02-15-03-923Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-04T02-15-07-426Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-04T02-15-25-729Z.yml +262 -0
- package/.playwright-mcp/page-2026-04-04T02-16-22-984Z.yml +262 -0
- package/.playwright-mcp/page-2026-04-04T02-17-00-599Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-04T02-17-50-874Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-05T02-26-55-570Z.yml +6 -0
- package/AGENTS.md +48 -0
- package/CHANGELOG.md +131 -0
- package/README.md +552 -0
- package/assets/agent-mode.png +0 -0
- package/assets/categorize.png +0 -0
- package/assets/dashboard.png +0 -0
- package/assets/endpoint-proxy.png +0 -0
- package/assets/icon.png +0 -0
- package/assets/mcp-generate-image.png +0 -0
- package/assets/mcp-understand-image.png +0 -0
- package/assets/peek-token-flow.png +0 -0
- package/assets/playground.png +0 -0
- package/assets/sankey.png +0 -0
- package/cli/index.ts +2805 -0
- package/cli/legacyRewrite.ts +108 -0
- package/cli/modelRef.ts +24 -0
- package/dist/cli/index.js +2536 -0
- package/dist/cli/legacyRewrite.js +92 -0
- package/dist/cli/modelRef.js +20 -0
- package/dist/src/benchmark/artifacts.js +131 -0
- package/dist/src/benchmark/capabilityClassifier.js +81 -0
- package/dist/src/benchmark/capabilityStore.js +144 -0
- package/dist/src/benchmark/config.js +238 -0
- package/dist/src/benchmark/gates.js +118 -0
- package/dist/src/benchmark/jobs.js +252 -0
- package/dist/src/benchmark/runner.js +1847 -0
- package/dist/src/benchmark/schema.js +353 -0
- package/dist/src/benchmark/suites.js +314 -0
- package/dist/src/benchmark/tinyQaDataset.js +422 -0
- package/dist/src/benchmark/types.js +25 -0
- package/dist/src/config.js +47 -0
- package/dist/src/index.js +178 -0
- package/dist/src/mcp/client.js +215 -0
- package/dist/src/mcp/discovery.js +226 -0
- package/dist/src/mcp/policy.js +65 -0
- package/dist/src/mcp/registry.js +129 -0
- package/dist/src/mcp/service.js +460 -0
- package/dist/src/middleware/auth.js +179 -0
- package/dist/src/middleware/requestCapture.js +192 -0
- package/dist/src/middleware/requestStats.js +118 -0
- package/dist/src/pools/builder.js +132 -0
- package/dist/src/pools/repository.js +69 -0
- package/dist/src/pools/scheduler.js +360 -0
- package/dist/src/pools/types.js +2 -0
- package/dist/src/protocols/adapters/dashscope.js +267 -0
- package/dist/src/protocols/adapters/inferenceV2.js +346 -0
- package/dist/src/protocols/adapters/openai.js +27 -0
- package/dist/src/protocols/registry.js +99 -0
- package/dist/src/protocols/types.js +2 -0
- package/dist/src/providers/health.js +153 -0
- package/dist/src/providers/importer.js +289 -0
- package/dist/src/providers/modelRegistry.js +313 -0
- package/dist/src/providers/repository.js +361 -0
- package/dist/src/providers/types.js +2 -0
- package/dist/src/routes/admin.js +531 -0
- package/dist/src/routes/audio.js +295 -0
- package/dist/src/routes/chat.js +240 -0
- package/dist/src/routes/embeddings.js +157 -0
- package/dist/src/routes/images.js +288 -0
- package/dist/src/routes/mcp.js +256 -0
- package/dist/src/routes/mcpService.js +100 -0
- package/dist/src/routes/models.js +48 -0
- package/dist/src/routes/responses.js +711 -0
- package/dist/src/routes/sessions.js +450 -0
- package/dist/src/routes/stats.js +270 -0
- package/dist/src/routes/ui.js +97 -0
- package/dist/src/routes/videos.js +107 -0
- package/dist/src/routing/router.js +338 -0
- package/dist/src/services/imageGeneration.js +280 -0
- package/dist/src/services/imageUnderstanding.js +352 -0
- package/dist/src/services/videoGeneration.js +79 -0
- package/dist/src/storage/captureRepository.js +1591 -0
- package/dist/src/storage/files.js +157 -0
- package/dist/src/storage/imageCache.js +346 -0
- package/dist/src/storage/repositories.js +388 -0
- package/dist/src/storage/sessionRepository.js +370 -0
- package/dist/src/storage/statsRepository.js +204 -0
- package/dist/src/transport/httpClient.js +126 -0
- package/dist/src/types.js +2 -0
- package/dist/src/utils/messageMedia.js +285 -0
- package/dist/src/utils/modelCapabilities.js +108 -0
- package/dist/src/utils/modelDiscovery.js +170 -0
- package/dist/src/version.js +5 -0
- package/dist/src/workers/captureRetention.js +25 -0
- package/dist/src/workers/configWatcher.js +91 -0
- package/dist/src/workers/healthChecker.js +21 -0
- package/dist/src/workers/statsRotation.js +41 -0
- package/docs/LLM/output_schema.md +312 -0
- package/docs/benchmark.md +208 -0
- package/docs/mcp-guidelines.md +125 -0
- package/docs/mcp-service.md +178 -0
- package/docs/opencode.md +86 -0
- package/docs/providers.md +79 -0
- package/examples/benchmark.config.yaml +28 -0
- package/examples/providers/alibaba-dashscope.yaml +88 -0
- package/examples/providers/alibaba-llm.yaml +64 -0
- package/examples/providers/alibaba-registry.yaml +7 -0
- package/examples/providers/inference-v2-ray.yaml +29 -0
- package/examples/scenarios/assets/omni-call-sample.wav +0 -0
- package/examples/scenarios/custom.jsonl +5 -0
- package/examples/scenarios/custom.yaml +40 -0
- package/model-form-v2.png +0 -0
- package/package.json +66 -0
- package/provider-form-v2.png +0 -0
- package/provider-form.png +0 -0
- package/scripts/manual-test.sh +11 -0
- package/scripts/version-from-git.js +23 -0
- package/src/benchmark/artifacts.ts +149 -0
- package/src/benchmark/capabilityClassifier.ts +99 -0
- package/src/benchmark/capabilityStore.ts +174 -0
- package/src/benchmark/config.ts +337 -0
- package/src/benchmark/gates.ts +164 -0
- package/src/benchmark/jobs.ts +312 -0
- package/src/benchmark/runner.ts +2519 -0
- package/src/benchmark/schema.ts +443 -0
- package/src/benchmark/suites.ts +323 -0
- package/src/benchmark/tinyQaDataset.ts +428 -0
- package/src/benchmark/types.ts +442 -0
- package/src/config.ts +44 -0
- package/src/index.ts +195 -0
- package/src/mcp/client.ts +305 -0
- package/src/mcp/discovery.ts +266 -0
- package/src/mcp/policy.ts +105 -0
- package/src/mcp/registry.ts +164 -0
- package/src/mcp/service.ts +611 -0
- package/src/middleware/auth.ts +251 -0
- package/src/middleware/requestCapture.ts +245 -0
- package/src/middleware/requestStats.ts +163 -0
- package/src/pools/builder.ts +159 -0
- package/src/pools/repository.ts +71 -0
- package/src/pools/scheduler.ts +425 -0
- package/src/pools/types.ts +117 -0
- package/src/protocols/adapters/dashscope.ts +335 -0
- package/src/protocols/adapters/inferenceV2.ts +428 -0
- package/src/protocols/adapters/openai.ts +32 -0
- package/src/protocols/registry.ts +117 -0
- package/src/protocols/types.ts +81 -0
- package/src/providers/health.ts +207 -0
- package/src/providers/importer.ts +402 -0
- package/src/providers/modelRegistry.ts +415 -0
- package/src/providers/repository.ts +439 -0
- package/src/providers/types.ts +113 -0
- package/src/routes/admin.ts +666 -0
- package/src/routes/audio.ts +372 -0
- package/src/routes/chat.ts +301 -0
- package/src/routes/embeddings.ts +197 -0
- package/src/routes/images.ts +356 -0
- package/src/routes/mcp.ts +320 -0
- package/src/routes/mcpService.ts +114 -0
- package/src/routes/models.ts +50 -0
- package/src/routes/responses.ts +872 -0
- package/src/routes/sessions.ts +558 -0
- package/src/routes/stats.ts +312 -0
- package/src/routes/ui.ts +96 -0
- package/src/routes/videos.ts +132 -0
- package/src/routing/router.ts +501 -0
- package/src/services/imageGeneration.ts +396 -0
- package/src/services/imageUnderstanding.ts +449 -0
- package/src/services/videoGeneration.ts +127 -0
- package/src/storage/captureRepository.ts +1835 -0
- package/src/storage/files.ts +178 -0
- package/src/storage/imageCache.ts +405 -0
- package/src/storage/repositories.ts +494 -0
- package/src/storage/sessionRepository.ts +419 -0
- package/src/storage/statsRepository.ts +238 -0
- package/src/transport/httpClient.ts +145 -0
- package/src/types.ts +322 -0
- package/src/utils/messageMedia.ts +293 -0
- package/src/utils/modelCapabilities.ts +161 -0
- package/src/utils/modelDiscovery.ts +203 -0
- package/src/workers/captureRetention.ts +25 -0
- package/src/workers/configWatcher.ts +115 -0
- package/src/workers/healthChecker.ts +22 -0
- package/src/workers/statsRotation.ts +49 -0
- package/tests/benchmarkAdminRoutes.test.ts +82 -0
- package/tests/benchmarkBasics.test.ts +116 -0
- package/tests/captureAdminRoutes.test.ts +420 -0
- package/tests/captureRepository.test.ts +797 -0
- package/tests/cliLegacyRewrite.test.ts +45 -0
- package/tests/imageGeneration.service.test.ts +107 -0
- package/tests/imageUnderstanding.service.test.ts +123 -0
- package/tests/mcpPolicy.test.ts +105 -0
- package/tests/mcpService.test.ts +1245 -0
- package/tests/modelRef.test.ts +23 -0
- package/tests/modelsRoutes.test.ts +154 -0
- package/tests/sessionMediaCache.test.ts +167 -0
- package/tests/statsRoutes.test.ts +323 -0
- package/tsconfig.json +15 -0
- package/ui/index.html +16 -0
- package/ui/package-lock.json +8521 -0
- package/ui/package.json +52 -0
- package/ui/postcss.config.js +6 -0
- package/ui/public/assets/apple-touch-icon.png +0 -0
- package/ui/public/assets/favicon-16.png +0 -0
- package/ui/public/assets/favicon-32.png +0 -0
- package/ui/public/assets/icon-192.png +0 -0
- package/ui/public/assets/icon-512.png +0 -0
- package/ui/src/App.tsx +27 -0
- package/ui/src/api/client.ts +1503 -0
- package/ui/src/components/EndpointUsageGuide.tsx +361 -0
- package/ui/src/components/Layout.tsx +124 -0
- package/ui/src/components/MessageContent.tsx +365 -0
- package/ui/src/components/ToolCallMessage.tsx +179 -0
- package/ui/src/components/ToolPicker.tsx +442 -0
- package/ui/src/components/messageContentParser.test.ts +41 -0
- package/ui/src/components/messageContentParser.ts +73 -0
- package/ui/src/components/thinkingPreview.test.ts +27 -0
- package/ui/src/components/thinkingPreview.ts +15 -0
- package/ui/src/components/toMermaidSankey.test.ts +78 -0
- package/ui/src/components/toMermaidSankey.ts +56 -0
- package/ui/src/components/ui/button.tsx +58 -0
- package/ui/src/components/ui/input.tsx +21 -0
- package/ui/src/components/ui/textarea.tsx +21 -0
- package/ui/src/lib/utils.ts +6 -0
- package/ui/src/main.tsx +9 -0
- package/ui/src/pages/AgentPlayground.tsx +2010 -0
- package/ui/src/pages/Benchmark.tsx +988 -0
- package/ui/src/pages/Dashboard.tsx +581 -0
- package/ui/src/pages/Peek.tsx +962 -0
- package/ui/src/pages/Settings.tsx +2013 -0
- package/ui/src/pages/agentPlaygroundPayload.test.ts +109 -0
- package/ui/src/pages/agentPlaygroundPayload.ts +97 -0
- package/ui/src/pages/agentThinkingContent.test.ts +50 -0
- package/ui/src/pages/agentThinkingContent.ts +57 -0
- package/ui/src/pages/dashboardTokenUsage.test.ts +66 -0
- package/ui/src/pages/dashboardTokenUsage.ts +36 -0
- package/ui/src/pages/imageUpload.test.ts +39 -0
- package/ui/src/pages/imageUpload.ts +71 -0
- package/ui/src/pages/peekFilters.test.ts +29 -0
- package/ui/src/pages/peekFilters.ts +13 -0
- package/ui/src/pages/peekMedia.test.ts +58 -0
- package/ui/src/pages/peekMedia.ts +148 -0
- package/ui/src/pages/sessionAutoTitle.test.ts +128 -0
- package/ui/src/pages/sessionAutoTitle.ts +106 -0
- package/ui/src/stores/settings.ts +58 -0
- package/ui/src/styles/globals.css +223 -0
- package/ui/src/vite-env.d.ts +8 -0
- package/ui/tailwind.config.js +106 -0
- package/ui/tsconfig.json +32 -0
- package/ui/vite.config.ts +37 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.invalidateConfigCache = invalidateConfigCache;
|
|
4
|
+
exports.listEndpoints = listEndpoints;
|
|
5
|
+
exports.createEndpoint = createEndpoint;
|
|
6
|
+
exports.updateEndpoint = updateEndpoint;
|
|
7
|
+
exports.setEndpointDisabled = setEndpointDisabled;
|
|
8
|
+
exports.deleteEndpointByIdOrName = deleteEndpointByIdOrName;
|
|
9
|
+
exports.getEndpointByIdOrName = getEndpointByIdOrName;
|
|
10
|
+
exports.getEligibleEndpointsForModel = getEligibleEndpointsForModel;
|
|
11
|
+
exports.listEligibleEndpoints = listEligibleEndpoints;
|
|
12
|
+
exports.sortEndpointsForRouting = sortEndpointsForRouting;
|
|
13
|
+
exports.updateHealthSuccess = updateHealthSuccess;
|
|
14
|
+
exports.updateHealthFailure = updateHealthFailure;
|
|
15
|
+
exports.markEndpointDown = markEndpointDown;
|
|
16
|
+
exports.updateHealthCheck = updateHealthCheck;
|
|
17
|
+
exports.listPublicModels = listPublicModels;
|
|
18
|
+
exports.listModelsWithTypes = listModelsWithTypes;
|
|
19
|
+
exports.pickBestLlmModel = pickBestLlmModel;
|
|
20
|
+
exports.pickBestModelByCapabilities = pickBestModelByCapabilities;
|
|
21
|
+
exports.getModelCapabilitiesForEndpoint = getModelCapabilitiesForEndpoint;
|
|
22
|
+
exports.logRequest = logRequest;
|
|
23
|
+
exports.getStats = getStats;
|
|
24
|
+
exports.getUsageByEndpoint = getUsageByEndpoint;
|
|
25
|
+
const files_1 = require("./files");
|
|
26
|
+
const modelCapabilities_1 = require("../utils/modelCapabilities");
|
|
27
|
+
const modelRegistry_1 = require("../providers/modelRegistry");
|
|
28
|
+
const cache = {
|
|
29
|
+
config: null,
|
|
30
|
+
health: null,
|
|
31
|
+
lastLoadedAt: 0
|
|
32
|
+
};
|
|
33
|
+
const CACHE_TTL_MS = 1000; // 1 second TTL for cache
|
|
34
|
+
/**
|
|
35
|
+
* Invalidate the config cache. Call this when config changes externally.
|
|
36
|
+
*/
|
|
37
|
+
function invalidateConfigCache() {
|
|
38
|
+
cache.config = null;
|
|
39
|
+
cache.health = null;
|
|
40
|
+
cache.lastLoadedAt = 0;
|
|
41
|
+
console.log("[repositories] Config cache invalidated");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Check if cache is valid
|
|
45
|
+
*/
|
|
46
|
+
function isCacheValid() {
|
|
47
|
+
return cache.config !== null && (Date.now() - cache.lastLoadedAt) < CACHE_TTL_MS;
|
|
48
|
+
}
|
|
49
|
+
async function getCachedConfig(paths) {
|
|
50
|
+
if (!isCacheValid()) {
|
|
51
|
+
cache.config = await (0, files_1.loadConfig)(paths);
|
|
52
|
+
cache.lastLoadedAt = Date.now();
|
|
53
|
+
}
|
|
54
|
+
return cache.config;
|
|
55
|
+
}
|
|
56
|
+
async function getCachedHealth(paths) {
|
|
57
|
+
// Health is always fresh-loaded since it changes frequently
|
|
58
|
+
return (0, files_1.loadHealth)(paths);
|
|
59
|
+
}
|
|
60
|
+
async function listEndpoints(paths) {
|
|
61
|
+
const config = await getCachedConfig(paths);
|
|
62
|
+
const normalized = normalizeConfig(config);
|
|
63
|
+
if (normalized.changed) {
|
|
64
|
+
await (0, files_1.saveConfig)(paths, normalized.config);
|
|
65
|
+
cache.config = normalized.config;
|
|
66
|
+
}
|
|
67
|
+
const health = await getCachedHealth(paths);
|
|
68
|
+
return normalized.config.endpoints.map((endpoint) => ({
|
|
69
|
+
...endpoint,
|
|
70
|
+
health: health.endpoints[endpoint.id] ?? (0, files_1.defaultHealth)()
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
async function createEndpoint(paths, input) {
|
|
74
|
+
const normalized = normalizeConfig(await (0, files_1.loadConfig)(paths));
|
|
75
|
+
if (normalized.changed) {
|
|
76
|
+
await (0, files_1.saveConfig)(paths, normalized.config);
|
|
77
|
+
}
|
|
78
|
+
const config = normalized.config;
|
|
79
|
+
const now = new Date();
|
|
80
|
+
const endpoint = {
|
|
81
|
+
...input,
|
|
82
|
+
id: (0, files_1.newEndpointId)(),
|
|
83
|
+
health: (0, files_1.defaultHealth)(),
|
|
84
|
+
disabled: input.disabled ?? false,
|
|
85
|
+
createdAt: now,
|
|
86
|
+
updatedAt: now
|
|
87
|
+
};
|
|
88
|
+
config.endpoints.push(stripHealth(endpoint));
|
|
89
|
+
await (0, files_1.saveConfig)(paths, config);
|
|
90
|
+
const health = await (0, files_1.loadHealth)(paths);
|
|
91
|
+
health.endpoints[endpoint.id] = endpoint.health;
|
|
92
|
+
await (0, files_1.saveHealth)(paths, health);
|
|
93
|
+
return endpoint;
|
|
94
|
+
}
|
|
95
|
+
async function updateEndpoint(paths, id, patch) {
|
|
96
|
+
const config = normalizeConfig(await (0, files_1.loadConfig)(paths)).config;
|
|
97
|
+
const idx = config.endpoints.findIndex((endpoint) => endpoint.id === id);
|
|
98
|
+
if (idx === -1) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const existing = config.endpoints[idx];
|
|
102
|
+
const updated = {
|
|
103
|
+
...existing,
|
|
104
|
+
...stripHealth(patch),
|
|
105
|
+
updatedAt: new Date()
|
|
106
|
+
};
|
|
107
|
+
config.endpoints[idx] = updated;
|
|
108
|
+
await (0, files_1.saveConfig)(paths, config);
|
|
109
|
+
const health = await (0, files_1.loadHealth)(paths);
|
|
110
|
+
const healthState = health.endpoints[id] ?? (0, files_1.defaultHealth)();
|
|
111
|
+
return { ...updated, health: healthState };
|
|
112
|
+
}
|
|
113
|
+
async function setEndpointDisabled(paths, id, disabled) {
|
|
114
|
+
const endpoint = await updateEndpoint(paths, id, { disabled });
|
|
115
|
+
return endpoint;
|
|
116
|
+
}
|
|
117
|
+
async function deleteEndpointByIdOrName(paths, value) {
|
|
118
|
+
const config = normalizeConfig(await (0, files_1.loadConfig)(paths)).config;
|
|
119
|
+
const target = config.endpoints.find((endpoint) => endpoint.id === value || endpoint.name === value);
|
|
120
|
+
if (!target) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
config.endpoints = config.endpoints.filter((endpoint) => endpoint.id !== target.id);
|
|
124
|
+
await (0, files_1.saveConfig)(paths, config);
|
|
125
|
+
const health = await (0, files_1.loadHealth)(paths);
|
|
126
|
+
const healthState = health.endpoints[target.id] ?? (0, files_1.defaultHealth)();
|
|
127
|
+
delete health.endpoints[target.id];
|
|
128
|
+
await (0, files_1.saveHealth)(paths, health);
|
|
129
|
+
return { ...target, health: healthState };
|
|
130
|
+
}
|
|
131
|
+
async function getEndpointByIdOrName(paths, value) {
|
|
132
|
+
const config = normalizeConfig(await (0, files_1.loadConfig)(paths)).config;
|
|
133
|
+
const endpoint = config.endpoints.find((item) => item.id === value || item.name === value);
|
|
134
|
+
if (!endpoint) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
const health = await (0, files_1.loadHealth)(paths);
|
|
138
|
+
return { ...endpoint, health: health.endpoints[endpoint.id] ?? (0, files_1.defaultHealth)() };
|
|
139
|
+
}
|
|
140
|
+
async function getEligibleEndpointsForModel(paths, publicModel, requirements = {}) {
|
|
141
|
+
const endpoints = await listEndpoints(paths);
|
|
142
|
+
const now = new Date();
|
|
143
|
+
return endpoints.filter((endpoint) => {
|
|
144
|
+
if (endpoint.disabled) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
// Filter by endpoint type if specified
|
|
148
|
+
if (requirements.endpointType && endpoint.type !== requirements.endpointType) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
const model = endpoint.models.find((mapping) => mapping.publicName === publicModel);
|
|
152
|
+
if (!model) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const capabilities = (0, modelCapabilities_1.resolveCapabilities)(model, endpoint.type);
|
|
156
|
+
if (!(0, modelCapabilities_1.supportsRequirements)(capabilities, requirements)) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
if (endpoint.health.status === "down") {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
if (endpoint.health.downUntil && endpoint.health.downUntil > now) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
async function listEligibleEndpoints(paths) {
|
|
169
|
+
const endpoints = await listEndpoints(paths);
|
|
170
|
+
const now = new Date();
|
|
171
|
+
return endpoints.filter((endpoint) => {
|
|
172
|
+
if (endpoint.disabled) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
if (endpoint.health.status === "down") {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
if (endpoint.health.downUntil && endpoint.health.downUntil > now) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function sortEndpointsForRouting(endpoints) {
|
|
185
|
+
return endpoints.sort((a, b) => {
|
|
186
|
+
if (a.priority !== b.priority) {
|
|
187
|
+
return a.priority - b.priority;
|
|
188
|
+
}
|
|
189
|
+
const aLatency = a.health.latencyMsEwma ?? Number.POSITIVE_INFINITY;
|
|
190
|
+
const bLatency = b.health.latencyMsEwma ?? Number.POSITIVE_INFINITY;
|
|
191
|
+
return aLatency - bLatency;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
async function updateHealthSuccess(paths, endpointId, latencyMs) {
|
|
195
|
+
const health = await (0, files_1.loadHealth)(paths);
|
|
196
|
+
const now = new Date();
|
|
197
|
+
const current = health.endpoints[endpointId] ?? (0, files_1.defaultHealth)();
|
|
198
|
+
const nextLatency = ewma(current.latencyMsEwma, latencyMs);
|
|
199
|
+
health.endpoints[endpointId] = {
|
|
200
|
+
...current,
|
|
201
|
+
status: "up",
|
|
202
|
+
lastSuccessAt: now,
|
|
203
|
+
consecutiveFailures: 0,
|
|
204
|
+
latencyMsEwma: nextLatency,
|
|
205
|
+
downUntil: undefined
|
|
206
|
+
};
|
|
207
|
+
await (0, files_1.saveHealth)(paths, health);
|
|
208
|
+
}
|
|
209
|
+
async function updateHealthFailure(paths, endpointId) {
|
|
210
|
+
const health = await (0, files_1.loadHealth)(paths);
|
|
211
|
+
const now = new Date();
|
|
212
|
+
const current = health.endpoints[endpointId] ?? (0, files_1.defaultHealth)();
|
|
213
|
+
const next = {
|
|
214
|
+
...current,
|
|
215
|
+
lastFailureAt: now,
|
|
216
|
+
consecutiveFailures: current.consecutiveFailures + 1
|
|
217
|
+
};
|
|
218
|
+
health.endpoints[endpointId] = next;
|
|
219
|
+
await (0, files_1.saveHealth)(paths, health);
|
|
220
|
+
return { consecutiveFailures: next.consecutiveFailures };
|
|
221
|
+
}
|
|
222
|
+
async function markEndpointDown(paths, endpointId, downUntil) {
|
|
223
|
+
const health = await (0, files_1.loadHealth)(paths);
|
|
224
|
+
const current = health.endpoints[endpointId] ?? (0, files_1.defaultHealth)();
|
|
225
|
+
health.endpoints[endpointId] = {
|
|
226
|
+
...current,
|
|
227
|
+
status: "down",
|
|
228
|
+
downUntil
|
|
229
|
+
};
|
|
230
|
+
await (0, files_1.saveHealth)(paths, health);
|
|
231
|
+
}
|
|
232
|
+
async function updateHealthCheck(paths, endpointId, status, latencyMs) {
|
|
233
|
+
const health = await (0, files_1.loadHealth)(paths);
|
|
234
|
+
const now = new Date();
|
|
235
|
+
const current = health.endpoints[endpointId] ?? (0, files_1.defaultHealth)();
|
|
236
|
+
const next = {
|
|
237
|
+
...current,
|
|
238
|
+
status,
|
|
239
|
+
lastCheckedAt: now
|
|
240
|
+
};
|
|
241
|
+
if (latencyMs !== null) {
|
|
242
|
+
next.latencyMsEwma = ewma(current.latencyMsEwma, latencyMs);
|
|
243
|
+
next.lastSuccessAt = now;
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
next.lastFailureAt = now;
|
|
247
|
+
}
|
|
248
|
+
health.endpoints[endpointId] = next;
|
|
249
|
+
await (0, files_1.saveHealth)(paths, health);
|
|
250
|
+
}
|
|
251
|
+
async function listPublicModels(paths) {
|
|
252
|
+
const config = normalizeConfig(await (0, files_1.loadConfig)(paths)).config;
|
|
253
|
+
const names = new Set();
|
|
254
|
+
for (const endpoint of config.endpoints) {
|
|
255
|
+
for (const model of endpoint.models) {
|
|
256
|
+
names.add(model.publicName);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return Array.from(names).sort();
|
|
260
|
+
}
|
|
261
|
+
async function listModelsWithTypes(paths) {
|
|
262
|
+
// Get endpoints with health status to filter out unhealthy ones
|
|
263
|
+
const endpoints = await listEligibleEndpoints(paths);
|
|
264
|
+
const models = new Map();
|
|
265
|
+
for (const endpoint of endpoints) {
|
|
266
|
+
for (const model of endpoint.models) {
|
|
267
|
+
// First endpoint wins (in case model is on multiple endpoints)
|
|
268
|
+
if (!models.has(model.publicName)) {
|
|
269
|
+
models.set(model.publicName, {
|
|
270
|
+
id: model.publicName,
|
|
271
|
+
type: endpoint.type,
|
|
272
|
+
endpointName: endpoint.name,
|
|
273
|
+
capabilities: (0, modelCapabilities_1.resolveCapabilities)(model, endpoint.type),
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return Array.from(models.values()).sort((a, b) => a.id.localeCompare(b.id));
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Pick the best available LLM model based on endpoint priority and health.
|
|
282
|
+
* Returns the publicName of the first LLM model from the highest-priority healthy endpoint.
|
|
283
|
+
*/
|
|
284
|
+
async function pickBestLlmModel(paths) {
|
|
285
|
+
return (0, modelRegistry_1.pickBestProviderModelByCapabilities)(paths, { requiredInput: ["text"], requiredOutput: ["text"] }, "llm");
|
|
286
|
+
}
|
|
287
|
+
async function pickBestModelByCapabilities(paths, requirements, preferredEndpointType) {
|
|
288
|
+
return (0, modelRegistry_1.pickBestProviderModelByCapabilities)(paths, requirements, preferredEndpointType);
|
|
289
|
+
}
|
|
290
|
+
function getModelCapabilitiesForEndpoint(endpointType, mapping) {
|
|
291
|
+
return (0, modelCapabilities_1.resolveCapabilities)(mapping, endpointType);
|
|
292
|
+
}
|
|
293
|
+
async function logRequest(paths, log) {
|
|
294
|
+
await (0, files_1.appendRequestLog)(paths, log);
|
|
295
|
+
}
|
|
296
|
+
async function getStats(paths, windowMs) {
|
|
297
|
+
const since = Date.now() - windowMs;
|
|
298
|
+
const logs = await (0, files_1.readRequestLogs)(paths);
|
|
299
|
+
const filtered = logs.filter((log) => new Date(log.ts).getTime() >= since);
|
|
300
|
+
if (filtered.length === 0) {
|
|
301
|
+
return { total: 0, success: 0, errors: 0, avgLatencyMs: null };
|
|
302
|
+
}
|
|
303
|
+
let sumLatency = 0;
|
|
304
|
+
let latencyCount = 0;
|
|
305
|
+
let errors = 0;
|
|
306
|
+
for (const log of filtered) {
|
|
307
|
+
if (log.result.errorType) {
|
|
308
|
+
errors += 1;
|
|
309
|
+
}
|
|
310
|
+
if (typeof log.result.latencyMs === "number") {
|
|
311
|
+
sumLatency += log.result.latencyMs;
|
|
312
|
+
latencyCount += 1;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const avgLatencyMs = latencyCount > 0 ? sumLatency / latencyCount : null;
|
|
316
|
+
return {
|
|
317
|
+
total: filtered.length,
|
|
318
|
+
success: filtered.length - errors,
|
|
319
|
+
errors,
|
|
320
|
+
avgLatencyMs
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
async function getUsageByEndpoint(paths) {
|
|
324
|
+
const logs = await (0, files_1.readRequestLogs)(paths);
|
|
325
|
+
const totals = new Map();
|
|
326
|
+
for (const log of logs) {
|
|
327
|
+
const endpointId = log.route.endpointId;
|
|
328
|
+
if (!endpointId) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const entry = totals.get(endpointId) ?? { endpointName: log.route.endpointName ?? "unknown", totalTokens: 0, count: 0 };
|
|
332
|
+
entry.totalTokens += log.result.totalTokens ?? 0;
|
|
333
|
+
entry.count += 1;
|
|
334
|
+
totals.set(endpointId, entry);
|
|
335
|
+
}
|
|
336
|
+
return Array.from(totals.entries()).map(([endpointId, entry]) => ({
|
|
337
|
+
endpointId,
|
|
338
|
+
endpointName: entry.endpointName,
|
|
339
|
+
totalTokens: entry.totalTokens,
|
|
340
|
+
count: entry.count
|
|
341
|
+
}));
|
|
342
|
+
}
|
|
343
|
+
function ewma(prev, next, alpha = 0.2) {
|
|
344
|
+
if (prev === undefined) {
|
|
345
|
+
return next;
|
|
346
|
+
}
|
|
347
|
+
return alpha * next + (1 - alpha) * prev;
|
|
348
|
+
}
|
|
349
|
+
function stripHealth(endpoint) {
|
|
350
|
+
const { health: _health, ...rest } = endpoint;
|
|
351
|
+
return rest;
|
|
352
|
+
}
|
|
353
|
+
function normalizeConfig(config) {
|
|
354
|
+
let changed = false;
|
|
355
|
+
const endpoints = config.endpoints.map((endpoint) => {
|
|
356
|
+
let next = endpoint;
|
|
357
|
+
if (!endpoint.id) {
|
|
358
|
+
next = { ...next, id: (0, files_1.newEndpointId)() };
|
|
359
|
+
changed = true;
|
|
360
|
+
}
|
|
361
|
+
if (!next.type) {
|
|
362
|
+
next = { ...next, type: "llm" };
|
|
363
|
+
changed = true;
|
|
364
|
+
}
|
|
365
|
+
if (typeof next.disabled !== "boolean") {
|
|
366
|
+
next = { ...next, disabled: false };
|
|
367
|
+
changed = true;
|
|
368
|
+
}
|
|
369
|
+
if (!next.createdAt) {
|
|
370
|
+
next = { ...next, createdAt: new Date() };
|
|
371
|
+
changed = true;
|
|
372
|
+
}
|
|
373
|
+
else if (!(next.createdAt instanceof Date)) {
|
|
374
|
+
next = { ...next, createdAt: new Date(next.createdAt) };
|
|
375
|
+
changed = true;
|
|
376
|
+
}
|
|
377
|
+
if (!next.updatedAt) {
|
|
378
|
+
next = { ...next, updatedAt: new Date() };
|
|
379
|
+
changed = true;
|
|
380
|
+
}
|
|
381
|
+
else if (!(next.updatedAt instanceof Date)) {
|
|
382
|
+
next = { ...next, updatedAt: new Date(next.updatedAt) };
|
|
383
|
+
changed = true;
|
|
384
|
+
}
|
|
385
|
+
return next;
|
|
386
|
+
});
|
|
387
|
+
return { config: { ...config, endpoints }, changed };
|
|
388
|
+
}
|