miladyai 2.0.0-alpha.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/_rolldown/runtime.js +7 -0
- package/dist/actions/emote.js +64 -0
- package/dist/actions/restart.js +81 -0
- package/dist/actions/send-message.js +152 -0
- package/dist/agent-admin-routes.js +82 -0
- package/dist/agent-lifecycle-routes.js +79 -0
- package/dist/agent-transfer-routes.js +102 -0
- package/dist/api/agent-admin-routes.js +82 -0
- package/dist/api/agent-lifecycle-routes.js +79 -0
- package/dist/api/agent-transfer-routes.js +102 -0
- package/dist/api/apps-hyperscape-routes.js +58 -0
- package/dist/api/apps-routes.js +114 -0
- package/dist/api/auth-routes.js +56 -0
- package/dist/api/autonomy-routes.js +44 -0
- package/dist/api/bug-report-routes.js +111 -0
- package/dist/api/character-routes.js +195 -0
- package/dist/api/cloud-routes.js +330 -0
- package/dist/api/cloud-status-routes.js +155 -0
- package/dist/api/compat-utils.js +111 -0
- package/dist/api/database.js +735 -0
- package/dist/api/diagnostics-routes.js +205 -0
- package/dist/api/drop-service.js +134 -0
- package/dist/api/early-logs.js +86 -0
- package/dist/api/http-helpers.js +131 -0
- package/dist/api/knowledge-routes.js +534 -0
- package/dist/api/memory-bounds.js +71 -0
- package/dist/api/models-routes.js +28 -0
- package/dist/api/og-tracker.js +36 -0
- package/dist/api/permissions-routes.js +109 -0
- package/dist/api/plugin-validation.js +198 -0
- package/dist/api/provider-switch-config.js +41 -0
- package/dist/api/registry-routes.js +86 -0
- package/dist/api/registry-service.js +164 -0
- package/dist/api/sandbox-routes.js +1112 -0
- package/dist/api/server.js +7949 -0
- package/dist/api/subscription-routes.js +172 -0
- package/dist/api/terminal-run-limits.js +24 -0
- package/dist/api/training-routes.js +158 -0
- package/dist/api/trajectory-routes.js +300 -0
- package/dist/api/trigger-routes.js +246 -0
- package/dist/api/twitter-verify.js +134 -0
- package/dist/api/tx-service.js +108 -0
- package/dist/api/wallet-routes.js +266 -0
- package/dist/api/wallet.js +568 -0
- package/dist/api/whatsapp-routes.js +182 -0
- package/dist/api/zip-utils.js +109 -0
- package/dist/apps-hyperscape-routes.js +58 -0
- package/dist/apps-routes.js +114 -0
- package/dist/ascii.js +20 -0
- package/dist/auth/anthropic.js +44 -0
- package/dist/auth/apply-stealth.js +41 -0
- package/dist/auth/claude-code-stealth.js +78 -0
- package/dist/auth/credentials.js +156 -0
- package/dist/auth/index.js +5 -0
- package/dist/auth/openai-codex.js +66 -0
- package/dist/auth/types.js +9 -0
- package/dist/auth-routes.js +56 -0
- package/dist/autonomy-routes.js +44 -0
- package/dist/bug-report-routes.js +111 -0
- package/dist/build-info.json +6 -0
- package/dist/character-routes.js +195 -0
- package/dist/cli/argv.js +63 -0
- package/dist/cli/banner.js +34 -0
- package/dist/cli/cli-name.js +21 -0
- package/dist/cli/cli-utils.js +16 -0
- package/dist/cli/git-commit.js +78 -0
- package/dist/cli/parse-duration.js +15 -0
- package/dist/cli/plugins-cli.js +590 -0
- package/dist/cli/profile-utils.js +9 -0
- package/dist/cli/profile.js +95 -0
- package/dist/cli/program/build-program.js +17 -0
- package/dist/cli/program/command-registry.js +23 -0
- package/dist/cli/program/help.js +47 -0
- package/dist/cli/program/preaction.js +33 -0
- package/dist/cli/program/register.config.js +106 -0
- package/dist/cli/program/register.configure.js +20 -0
- package/dist/cli/program/register.dashboard.js +124 -0
- package/dist/cli/program/register.models.js +23 -0
- package/dist/cli/program/register.setup.js +36 -0
- package/dist/cli/program/register.start.js +22 -0
- package/dist/cli/program/register.subclis.js +70 -0
- package/dist/cli/program/register.tui.js +163 -0
- package/dist/cli/program/register.update.js +154 -0
- package/dist/cli/program.js +3 -0
- package/dist/cli/run-main.js +37 -0
- package/dist/cli/version.js +7 -0
- package/dist/cloud/validate-url.js +93 -0
- package/dist/cloud-routes.js +330 -0
- package/dist/cloud-status-routes.js +155 -0
- package/dist/compat-utils.js +111 -0
- package/dist/config/config.js +69 -0
- package/dist/config/env-vars.js +19 -0
- package/dist/config/includes.js +121 -0
- package/dist/config/object-utils.js +7 -0
- package/dist/config/paths.js +38 -0
- package/dist/config/plugin-auto-enable.js +231 -0
- package/dist/config/schema.js +864 -0
- package/dist/config/telegram-custom-commands.js +76 -0
- package/dist/config/zod-schema.agent-runtime.js +519 -0
- package/dist/config/zod-schema.core.js +538 -0
- package/dist/config/zod-schema.hooks.js +103 -0
- package/dist/config/zod-schema.js +488 -0
- package/dist/config/zod-schema.providers-core.js +785 -0
- package/dist/config/zod-schema.session.js +73 -0
- package/dist/core-plugins.js +37 -0
- package/dist/custom-actions.js +250 -0
- package/dist/database.js +735 -0
- package/dist/diagnostics/integration-observability.js +57 -0
- package/dist/diagnostics-routes.js +205 -0
- package/dist/drop-service.js +134 -0
- package/dist/early-logs.js +24 -0
- package/dist/eliza.js +2061 -0
- package/dist/emotes/catalog.js +271 -0
- package/dist/entry.js +40 -0
- package/dist/hooks/discovery.js +167 -0
- package/dist/hooks/eligibility.js +64 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/loader.js +147 -0
- package/dist/hooks/registry.js +55 -0
- package/dist/http-helpers.js +131 -0
- package/dist/index.js +49 -0
- package/dist/knowledge-routes.js +534 -0
- package/dist/memory-bounds.js +71 -0
- package/dist/milady-plugin.js +90 -0
- package/dist/models-routes.js +28 -0
- package/dist/onboarding-names.js +78 -0
- package/dist/onboarding-presets.js +922 -0
- package/dist/package.json +1 -0
- package/dist/permissions-routes.js +109 -0
- package/dist/plugin-validation.js +107 -0
- package/dist/plugins/whatsapp/actions.js +91 -0
- package/dist/plugins/whatsapp/index.js +16 -0
- package/dist/plugins/whatsapp/service.js +270 -0
- package/dist/provider-switch-config.js +41 -0
- package/dist/providers/admin-trust.js +46 -0
- package/dist/providers/autonomous-state.js +101 -0
- package/dist/providers/session-bridge.js +86 -0
- package/dist/providers/session-utils.js +36 -0
- package/dist/providers/simple-mode.js +50 -0
- package/dist/providers/ui-catalog.js +15 -0
- package/dist/providers/workspace-provider.js +93 -0
- package/dist/providers/workspace.js +348 -0
- package/dist/registry-routes.js +86 -0
- package/dist/registry-service.js +164 -0
- package/dist/restart.js +40 -0
- package/dist/runtime/core-plugins.js +37 -0
- package/dist/runtime/custom-actions.js +250 -0
- package/dist/runtime/eliza.js +2061 -0
- package/dist/runtime/embedding-manager-support.js +185 -0
- package/dist/runtime/embedding-manager.js +193 -0
- package/dist/runtime/embedding-presets.js +54 -0
- package/dist/runtime/embedding-state.js +8 -0
- package/dist/runtime/milady-plugin.js +90 -0
- package/dist/runtime/onboarding-names.js +78 -0
- package/dist/runtime/restart.js +40 -0
- package/dist/runtime/version.js +7 -0
- package/dist/sandbox-routes.js +1112 -0
- package/dist/security/audit-log.js +149 -0
- package/dist/security/network-policy.js +70 -0
- package/dist/server.js +7949 -0
- package/dist/services/agent-export.js +559 -0
- package/dist/services/app-manager.js +389 -0
- package/dist/services/browser-capture.js +86 -0
- package/dist/services/fallback-training-service.js +128 -0
- package/dist/services/mcp-marketplace.js +134 -0
- package/dist/services/plugin-installer.js +396 -0
- package/dist/services/plugin-manager-types.js +15 -0
- package/dist/services/registry-client-app-meta.js +144 -0
- package/dist/services/registry-client-endpoints.js +166 -0
- package/dist/services/registry-client-local.js +271 -0
- package/dist/services/registry-client-network.js +93 -0
- package/dist/services/registry-client-queries.js +70 -0
- package/dist/services/registry-client.js +157 -0
- package/dist/services/sandbox-engine.js +511 -0
- package/dist/services/sandbox-manager.js +297 -0
- package/dist/services/self-updater.js +175 -0
- package/dist/services/skill-catalog-client.js +119 -0
- package/dist/services/skill-marketplace.js +521 -0
- package/dist/services/stream-manager.js +236 -0
- package/dist/services/update-checker.js +121 -0
- package/dist/services/update-notifier.js +29 -0
- package/dist/services/version-compat.js +78 -0
- package/dist/services/whatsapp-pairing.js +196 -0
- package/dist/shared/ui-catalog-prompt.js +728 -0
- package/dist/subscription-routes.js +172 -0
- package/dist/terminal/links.js +19 -0
- package/dist/terminal/palette.js +14 -0
- package/dist/terminal/theme.js +25 -0
- package/dist/terminal-run-limits.js +24 -0
- package/dist/training-routes.js +158 -0
- package/dist/trajectory-routes.js +300 -0
- package/dist/trigger-routes.js +246 -0
- package/dist/triggers/action.js +218 -0
- package/dist/triggers/runtime.js +281 -0
- package/dist/triggers/scheduling.js +295 -0
- package/dist/triggers/types.js +5 -0
- package/dist/tui/components/assistant-message.js +76 -0
- package/dist/tui/components/chat-editor.js +34 -0
- package/dist/tui/components/embeddings-overlay.js +46 -0
- package/dist/tui/components/footer.js +60 -0
- package/dist/tui/components/index.js +15 -0
- package/dist/tui/components/modal-frame.js +45 -0
- package/dist/tui/components/modal-style.js +15 -0
- package/dist/tui/components/model-selector.js +70 -0
- package/dist/tui/components/pinned-chat-layout.js +46 -0
- package/dist/tui/components/plugins-endpoints-tab.js +196 -0
- package/dist/tui/components/plugins-installed-tab-view.js +69 -0
- package/dist/tui/components/plugins-installed-tab.js +319 -0
- package/dist/tui/components/plugins-overlay-catalog.js +81 -0
- package/dist/tui/components/plugins-overlay-data-api.js +21 -0
- package/dist/tui/components/plugins-overlay-data-shared.js +20 -0
- package/dist/tui/components/plugins-overlay-data.js +323 -0
- package/dist/tui/components/plugins-overlay.js +117 -0
- package/dist/tui/components/plugins-store-tab.js +148 -0
- package/dist/tui/components/settings-overlay.js +61 -0
- package/dist/tui/components/status-bar.js +64 -0
- package/dist/tui/components/tool-execution.js +68 -0
- package/dist/tui/components/user-message.js +22 -0
- package/dist/tui/eliza-tui-bridge.js +606 -0
- package/dist/tui/index.js +370 -0
- package/dist/tui/modal-presets.js +33 -0
- package/dist/tui/model-spec.js +46 -0
- package/dist/tui/sse-parser.js +78 -0
- package/dist/tui/theme.js +110 -0
- package/dist/tui/titlebar-spinner.js +62 -0
- package/dist/tui/tui-app.js +311 -0
- package/dist/tui/ws-client.js +215 -0
- package/dist/twitter-verify.js +134 -0
- package/dist/tx-service.js +108 -0
- package/dist/utils/exec-safety.js +17 -0
- package/dist/utils/globals.js +20 -0
- package/dist/utils/milady-root.js +61 -0
- package/dist/utils/number-parsing.js +37 -0
- package/dist/version-resolver.js +37 -0
- package/dist/version.js +7 -0
- package/dist/wallet-routes.js +266 -0
- package/dist/wallet.js +568 -0
- package/dist/whatsapp-routes.js +182 -0
- package/dist/zip-utils.js +109 -0
- package/milady.mjs +14 -0
- package/package.json +111 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { logger } from "@elizaos/core";
|
|
2
|
+
|
|
3
|
+
//#region src/api/subscription-routes.ts
|
|
4
|
+
async function handleSubscriptionRoutes(ctx) {
|
|
5
|
+
const { req, res, method, pathname, state, readJsonBody, json, error } = ctx;
|
|
6
|
+
if (!pathname.startsWith("/api/subscription/")) return false;
|
|
7
|
+
if (method === "GET" && pathname === "/api/subscription/status") {
|
|
8
|
+
try {
|
|
9
|
+
const { getSubscriptionStatus } = await import("../auth/index.js");
|
|
10
|
+
json(res, { providers: getSubscriptionStatus() });
|
|
11
|
+
} catch (err) {
|
|
12
|
+
logger.error(`[api] Failed to get subscription status: ${err instanceof Error ? err.stack : err}`);
|
|
13
|
+
error(res, "Failed to get subscription status", 500);
|
|
14
|
+
}
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
if (method === "POST" && pathname === "/api/subscription/anthropic/start") {
|
|
18
|
+
try {
|
|
19
|
+
const { startAnthropicLogin } = await import("../auth/index.js");
|
|
20
|
+
const flow = await startAnthropicLogin();
|
|
21
|
+
state._anthropicFlow = flow;
|
|
22
|
+
json(res, { authUrl: flow.authUrl });
|
|
23
|
+
} catch (err) {
|
|
24
|
+
logger.error(`[api] Failed to start Anthropic login: ${err instanceof Error ? err.stack : err}`);
|
|
25
|
+
error(res, "Failed to start Anthropic login", 500);
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if (method === "POST" && pathname === "/api/subscription/anthropic/exchange") {
|
|
30
|
+
const body = await readJsonBody(req, res);
|
|
31
|
+
if (!body) return true;
|
|
32
|
+
if (!body.code) {
|
|
33
|
+
error(res, "Missing code", 400);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const { saveCredentials, applySubscriptionCredentials } = await import("../auth/index.js");
|
|
38
|
+
const flow = state._anthropicFlow;
|
|
39
|
+
if (!flow) {
|
|
40
|
+
error(res, "No active flow — call /start first", 400);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
flow.submitCode(body.code);
|
|
44
|
+
const credentials = await flow.credentials;
|
|
45
|
+
saveCredentials("anthropic-subscription", credentials);
|
|
46
|
+
await applySubscriptionCredentials(state.config);
|
|
47
|
+
delete state._anthropicFlow;
|
|
48
|
+
json(res, {
|
|
49
|
+
success: true,
|
|
50
|
+
expiresAt: credentials.expires
|
|
51
|
+
});
|
|
52
|
+
} catch (err) {
|
|
53
|
+
logger.error(`[api] Anthropic exchange failed: ${err instanceof Error ? err.stack : err}`);
|
|
54
|
+
error(res, "Anthropic exchange failed", 500);
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
if (method === "POST" && pathname === "/api/subscription/anthropic/setup-token") {
|
|
59
|
+
const body = await readJsonBody(req, res);
|
|
60
|
+
if (!body) return true;
|
|
61
|
+
if (!body.token || !body.token.startsWith("sk-ant-")) {
|
|
62
|
+
error(res, "Invalid token format — expected sk-ant-oat01-...", 400);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
process.env.ANTHROPIC_API_KEY = body.token.trim();
|
|
67
|
+
if (!state.config.env) state.config.env = {};
|
|
68
|
+
state.config.env.ANTHROPIC_API_KEY = body.token.trim();
|
|
69
|
+
ctx.saveConfig(state.config);
|
|
70
|
+
json(res, { success: true });
|
|
71
|
+
} catch (err) {
|
|
72
|
+
logger.error(`[api] Failed to save setup token: ${err instanceof Error ? err.stack : err}`);
|
|
73
|
+
error(res, "Failed to save setup token", 500);
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
if (method === "POST" && pathname === "/api/subscription/openai/start") {
|
|
78
|
+
try {
|
|
79
|
+
const { startCodexLogin } = await import("../auth/index.js");
|
|
80
|
+
if (state._codexFlow) try {
|
|
81
|
+
state._codexFlow.close();
|
|
82
|
+
} catch (err) {
|
|
83
|
+
logger.debug(`[api] OAuth flow cleanup failed: ${err instanceof Error ? err.message : err}`);
|
|
84
|
+
}
|
|
85
|
+
clearTimeout(state._codexFlowTimer);
|
|
86
|
+
const flow = await startCodexLogin();
|
|
87
|
+
state._codexFlow = flow;
|
|
88
|
+
state._codexFlowTimer = setTimeout(() => {
|
|
89
|
+
try {
|
|
90
|
+
flow.close();
|
|
91
|
+
} catch (err) {
|
|
92
|
+
logger.debug(`[api] OAuth flow cleanup failed: ${err instanceof Error ? err.message : err}`);
|
|
93
|
+
}
|
|
94
|
+
delete state._codexFlow;
|
|
95
|
+
delete state._codexFlowTimer;
|
|
96
|
+
}, 600 * 1e3);
|
|
97
|
+
json(res, {
|
|
98
|
+
authUrl: flow.authUrl,
|
|
99
|
+
state: flow.state,
|
|
100
|
+
instructions: "Open the URL in your browser. After login, if auto-redirect doesn't work, paste the full redirect URL."
|
|
101
|
+
});
|
|
102
|
+
} catch (err) {
|
|
103
|
+
logger.error(`[api] Failed to start OpenAI login: ${err instanceof Error ? err.stack : err}`);
|
|
104
|
+
error(res, "Failed to start OpenAI login", 500);
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
if (method === "POST" && pathname === "/api/subscription/openai/exchange") {
|
|
109
|
+
const body = await readJsonBody(req, res);
|
|
110
|
+
if (!body) return true;
|
|
111
|
+
try {
|
|
112
|
+
const { saveCredentials, applySubscriptionCredentials } = await import("../auth/index.js");
|
|
113
|
+
const flow = state._codexFlow;
|
|
114
|
+
if (!flow) {
|
|
115
|
+
error(res, "No active flow — call /start first", 400);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
if (body.code) flow.submitCode(body.code);
|
|
119
|
+
else if (!body.waitForCallback) {
|
|
120
|
+
error(res, "Provide either code or set waitForCallback: true", 400);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
let credentials;
|
|
124
|
+
try {
|
|
125
|
+
credentials = await flow.credentials;
|
|
126
|
+
} catch (err) {
|
|
127
|
+
try {
|
|
128
|
+
flow.close();
|
|
129
|
+
} catch (closeErr) {
|
|
130
|
+
logger.debug(`[api] OAuth flow cleanup failed: ${closeErr instanceof Error ? closeErr.message : closeErr}`);
|
|
131
|
+
}
|
|
132
|
+
delete state._codexFlow;
|
|
133
|
+
clearTimeout(state._codexFlowTimer);
|
|
134
|
+
delete state._codexFlowTimer;
|
|
135
|
+
logger.error(`[api] OpenAI exchange failed: ${err instanceof Error ? err.stack : err}`);
|
|
136
|
+
error(res, "OpenAI exchange failed", 500);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
saveCredentials("openai-codex", credentials);
|
|
140
|
+
await applySubscriptionCredentials(state.config);
|
|
141
|
+
flow.close();
|
|
142
|
+
delete state._codexFlow;
|
|
143
|
+
clearTimeout(state._codexFlowTimer);
|
|
144
|
+
delete state._codexFlowTimer;
|
|
145
|
+
json(res, {
|
|
146
|
+
success: true,
|
|
147
|
+
expiresAt: credentials.expires
|
|
148
|
+
});
|
|
149
|
+
} catch (err) {
|
|
150
|
+
logger.error(`[api] OpenAI exchange failed: ${err instanceof Error ? err.stack : err}`);
|
|
151
|
+
error(res, "OpenAI exchange failed", 500);
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
if (method === "DELETE" && pathname.startsWith("/api/subscription/")) {
|
|
156
|
+
const provider = pathname.split("/").pop();
|
|
157
|
+
if (provider === "anthropic-subscription" || provider === "openai-codex") try {
|
|
158
|
+
const { deleteCredentials } = await import("../auth/index.js");
|
|
159
|
+
deleteCredentials(provider);
|
|
160
|
+
json(res, { success: true });
|
|
161
|
+
} catch (err) {
|
|
162
|
+
logger.error(`[api] Failed to delete credentials: ${err instanceof Error ? err.stack : err}`);
|
|
163
|
+
error(res, "Failed to delete credentials", 500);
|
|
164
|
+
}
|
|
165
|
+
else error(res, `Unknown provider: ${provider}`, 400);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
//#endregion
|
|
172
|
+
export { handleSubscriptionRoutes };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { parseClampedInteger } from "../utils/number-parsing.js";
|
|
2
|
+
|
|
3
|
+
//#region src/api/terminal-run-limits.ts
|
|
4
|
+
const TERMINAL_RUN_MAX_CONCURRENT_DEFAULT = 2;
|
|
5
|
+
const TERMINAL_RUN_MAX_CONCURRENT_CAP = 16;
|
|
6
|
+
const TERMINAL_RUN_MAX_DURATION_MS_DEFAULT = 300 * 1e3;
|
|
7
|
+
const TERMINAL_RUN_MAX_DURATION_MS_CAP = 3600 * 1e3;
|
|
8
|
+
function resolveTerminalRunLimits() {
|
|
9
|
+
return {
|
|
10
|
+
maxConcurrent: parseClampedInteger(process.env.MILADY_TERMINAL_MAX_CONCURRENT ?? process.env.MILAIDY_TERMINAL_MAX_CONCURRENT, {
|
|
11
|
+
fallback: TERMINAL_RUN_MAX_CONCURRENT_DEFAULT,
|
|
12
|
+
min: 1,
|
|
13
|
+
max: TERMINAL_RUN_MAX_CONCURRENT_CAP
|
|
14
|
+
}),
|
|
15
|
+
maxDurationMs: parseClampedInteger(process.env.MILADY_TERMINAL_MAX_DURATION_MS ?? process.env.MILAIDY_TERMINAL_MAX_DURATION_MS, {
|
|
16
|
+
fallback: TERMINAL_RUN_MAX_DURATION_MS_DEFAULT,
|
|
17
|
+
min: 1e3,
|
|
18
|
+
max: TERMINAL_RUN_MAX_DURATION_MS_CAP
|
|
19
|
+
})
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
export { resolveTerminalRunLimits };
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { isLoopbackHost } from "../security/network-policy.js";
|
|
2
|
+
import { parsePositiveInteger } from "../utils/number-parsing.js";
|
|
3
|
+
|
|
4
|
+
//#region src/api/training-routes.ts
|
|
5
|
+
function resolveOllamaUrlRejection(rawUrl) {
|
|
6
|
+
const trimmed = rawUrl.trim();
|
|
7
|
+
if (!trimmed) return null;
|
|
8
|
+
let parsed;
|
|
9
|
+
try {
|
|
10
|
+
parsed = new URL(trimmed);
|
|
11
|
+
} catch {
|
|
12
|
+
return "ollamaUrl must be a valid URL";
|
|
13
|
+
}
|
|
14
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return "ollamaUrl must use http:// or https://";
|
|
15
|
+
if (!isLoopbackHost(parsed.hostname)) return "ollamaUrl must target a loopback host (localhost, 127.0.0.1, or ::1)";
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
async function handleTrainingRoutes(ctx) {
|
|
19
|
+
const { req, res, method, pathname, runtime, trainingService, json, error, readJsonBody } = ctx;
|
|
20
|
+
if (!pathname.startsWith("/api/training")) return false;
|
|
21
|
+
if (method === "GET" && pathname === "/api/training/status") {
|
|
22
|
+
json(res, {
|
|
23
|
+
...trainingService.getStatus(),
|
|
24
|
+
runtimeAvailable: runtime !== null
|
|
25
|
+
});
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (method === "GET" && pathname === "/api/training/trajectories") {
|
|
29
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
30
|
+
const limit = parsePositiveInteger(url.searchParams.get("limit"), 100);
|
|
31
|
+
const offset = Math.max(0, Number(url.searchParams.get("offset") ?? "0"));
|
|
32
|
+
json(res, await trainingService.listTrajectories({
|
|
33
|
+
limit,
|
|
34
|
+
offset
|
|
35
|
+
}));
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
const trajectoryMatch = /^\/api\/training\/trajectories\/([^/]+)$/.exec(pathname);
|
|
39
|
+
if (method === "GET" && trajectoryMatch) {
|
|
40
|
+
const trajectoryId = decodeURIComponent(trajectoryMatch[1]);
|
|
41
|
+
const detail = await trainingService.getTrajectoryById(trajectoryId);
|
|
42
|
+
if (!detail) {
|
|
43
|
+
error(res, "Trajectory not found", 404);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
json(res, { trajectory: detail });
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
if (method === "GET" && pathname === "/api/training/datasets") {
|
|
50
|
+
json(res, { datasets: trainingService.listDatasets() });
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (method === "POST" && pathname === "/api/training/datasets/build") {
|
|
54
|
+
const body = await readJsonBody(req, res);
|
|
55
|
+
if (!body) return true;
|
|
56
|
+
json(res, { dataset: await trainingService.buildDataset({
|
|
57
|
+
limit: body.limit,
|
|
58
|
+
minLlmCallsPerTrajectory: body.minLlmCallsPerTrajectory
|
|
59
|
+
}) }, 201);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (method === "GET" && pathname === "/api/training/jobs") {
|
|
63
|
+
json(res, { jobs: trainingService.listJobs() });
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
if (method === "POST" && pathname === "/api/training/jobs") {
|
|
67
|
+
const body = await readJsonBody(req, res);
|
|
68
|
+
if (!body) return true;
|
|
69
|
+
try {
|
|
70
|
+
json(res, { job: await trainingService.startTrainingJob({
|
|
71
|
+
datasetId: body.datasetId,
|
|
72
|
+
maxTrajectories: body.maxTrajectories,
|
|
73
|
+
backend: body.backend,
|
|
74
|
+
model: body.model,
|
|
75
|
+
iterations: body.iterations,
|
|
76
|
+
batchSize: body.batchSize,
|
|
77
|
+
learningRate: body.learningRate
|
|
78
|
+
}) }, 201);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
error(res, err instanceof Error ? err.message : String(err), 400);
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
const jobMatch = /^\/api\/training\/jobs\/([^/]+)$/.exec(pathname);
|
|
85
|
+
if (method === "GET" && jobMatch) {
|
|
86
|
+
const jobId = decodeURIComponent(jobMatch[1]);
|
|
87
|
+
const job = trainingService.getJob(jobId);
|
|
88
|
+
if (!job) {
|
|
89
|
+
error(res, "Training job not found", 404);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
json(res, { job });
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
const cancelMatch = /^\/api\/training\/jobs\/([^/]+)\/cancel$/.exec(pathname);
|
|
96
|
+
if (method === "POST" && cancelMatch) {
|
|
97
|
+
const jobId = decodeURIComponent(cancelMatch[1]);
|
|
98
|
+
try {
|
|
99
|
+
json(res, { job: await trainingService.cancelJob(jobId) });
|
|
100
|
+
} catch (err) {
|
|
101
|
+
error(res, err instanceof Error ? err.message : String(err), 404);
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
if (method === "GET" && pathname === "/api/training/models") {
|
|
106
|
+
json(res, { models: trainingService.listModels() });
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
const importMatch = /^\/api\/training\/models\/([^/]+)\/import-ollama$/.exec(pathname);
|
|
110
|
+
if (method === "POST" && importMatch) {
|
|
111
|
+
const modelId = decodeURIComponent(importMatch[1]);
|
|
112
|
+
const body = await readJsonBody(req, res);
|
|
113
|
+
if (!body) return true;
|
|
114
|
+
if (body.ollamaUrl !== void 0 && typeof body.ollamaUrl !== "string") {
|
|
115
|
+
error(res, "ollamaUrl must be a string", 400);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
if (typeof body.ollamaUrl === "string") {
|
|
119
|
+
const ollamaUrlRejection = resolveOllamaUrlRejection(body.ollamaUrl);
|
|
120
|
+
if (ollamaUrlRejection) {
|
|
121
|
+
error(res, ollamaUrlRejection, 400);
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
json(res, { model: await trainingService.importModelToOllama(modelId, body) });
|
|
127
|
+
} catch (err) {
|
|
128
|
+
error(res, err instanceof Error ? err.message : String(err), 400);
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
const activateMatch = /^\/api\/training\/models\/([^/]+)\/activate$/.exec(pathname);
|
|
133
|
+
if (method === "POST" && activateMatch) {
|
|
134
|
+
const modelId = decodeURIComponent(activateMatch[1]);
|
|
135
|
+
const body = await readJsonBody(req, res);
|
|
136
|
+
if (!body) return true;
|
|
137
|
+
try {
|
|
138
|
+
json(res, await trainingService.activateModel(modelId, body.providerModel));
|
|
139
|
+
} catch (err) {
|
|
140
|
+
error(res, err instanceof Error ? err.message : String(err), 400);
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
const benchmarkMatch = /^\/api\/training\/models\/([^/]+)\/benchmark$/.exec(pathname);
|
|
145
|
+
if (method === "POST" && benchmarkMatch) {
|
|
146
|
+
const modelId = decodeURIComponent(benchmarkMatch[1]);
|
|
147
|
+
try {
|
|
148
|
+
json(res, await trainingService.benchmarkModel(modelId));
|
|
149
|
+
} catch (err) {
|
|
150
|
+
error(res, err instanceof Error ? err.message : String(err), 400);
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
//#endregion
|
|
158
|
+
export { handleTrainingRoutes };
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { readJsonBody, sendJson, sendJsonError } from "./http-helpers.js";
|
|
2
|
+
import { createZipArchive } from "./zip-utils.js";
|
|
3
|
+
|
|
4
|
+
//#region src/api/trajectory-routes.ts
|
|
5
|
+
function isRouteCompatibleTrajectoryLogger(candidate) {
|
|
6
|
+
if (!candidate || typeof candidate !== "object") return false;
|
|
7
|
+
const logger = candidate;
|
|
8
|
+
return typeof logger.isEnabled === "function" && typeof logger.setEnabled === "function" && typeof logger.listTrajectories === "function" && typeof logger.getTrajectoryDetail === "function" && typeof logger.getStats === "function" && typeof logger.deleteTrajectories === "function" && typeof logger.clearAllTrajectories === "function" && typeof logger.exportTrajectories === "function";
|
|
9
|
+
}
|
|
10
|
+
function getTrajectoryLogger(runtime) {
|
|
11
|
+
const runtimeLike = runtime;
|
|
12
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13
|
+
const candidates = [];
|
|
14
|
+
const addCandidate = (candidate) => {
|
|
15
|
+
if (!candidate || seen.has(candidate)) return;
|
|
16
|
+
seen.add(candidate);
|
|
17
|
+
candidates.push(candidate);
|
|
18
|
+
};
|
|
19
|
+
if (typeof runtimeLike.getServicesByType === "function") {
|
|
20
|
+
const byType = runtimeLike.getServicesByType("trajectory_logger");
|
|
21
|
+
if (Array.isArray(byType)) for (const candidate of byType) addCandidate(candidate);
|
|
22
|
+
else addCandidate(byType);
|
|
23
|
+
}
|
|
24
|
+
if (typeof runtimeLike.getService === "function") addCandidate(runtimeLike.getService("trajectory_logger"));
|
|
25
|
+
for (const candidate of candidates) if (isRouteCompatibleTrajectoryLogger(candidate)) return candidate;
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function toNullableString(value) {
|
|
29
|
+
return typeof value === "string" ? value : null;
|
|
30
|
+
}
|
|
31
|
+
function listItemToUIRecord(item) {
|
|
32
|
+
const status = item.status === "timeout" || item.status === "error" ? "error" : item.status;
|
|
33
|
+
return {
|
|
34
|
+
id: item.id,
|
|
35
|
+
agentId: item.agentId,
|
|
36
|
+
roomId: null,
|
|
37
|
+
entityId: null,
|
|
38
|
+
conversationId: null,
|
|
39
|
+
source: item.source,
|
|
40
|
+
status,
|
|
41
|
+
startTime: item.startTime,
|
|
42
|
+
endTime: item.endTime,
|
|
43
|
+
durationMs: item.durationMs,
|
|
44
|
+
llmCallCount: item.llmCallCount,
|
|
45
|
+
providerAccessCount: 0,
|
|
46
|
+
totalPromptTokens: item.totalPromptTokens,
|
|
47
|
+
totalCompletionTokens: item.totalCompletionTokens,
|
|
48
|
+
metadata: {},
|
|
49
|
+
createdAt: item.createdAt,
|
|
50
|
+
updatedAt: item.createdAt
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function trajectoryToUIDetail(traj) {
|
|
54
|
+
const finalStatus = traj.metrics?.finalStatus ?? "completed";
|
|
55
|
+
const normalizedEndTime = typeof traj.endTime === "number" && traj.endTime > 0 ? traj.endTime : null;
|
|
56
|
+
const status = finalStatus === "timeout" || finalStatus === "terminated" || finalStatus === "error" ? "error" : finalStatus === "completed" ? "completed" : normalizedEndTime ? "completed" : "active";
|
|
57
|
+
const llmCalls = [];
|
|
58
|
+
const providerAccesses = [];
|
|
59
|
+
let totalPromptTokens = 0;
|
|
60
|
+
let totalCompletionTokens = 0;
|
|
61
|
+
const steps = traj.steps || [];
|
|
62
|
+
const trajectoryId = String(traj.trajectoryId);
|
|
63
|
+
for (let i = 0; i < steps.length; i++) {
|
|
64
|
+
const step = steps[i];
|
|
65
|
+
const stepId = typeof step.stepId === "string" ? step.stepId : `step-${i}`;
|
|
66
|
+
const calls = step.llmCalls || [];
|
|
67
|
+
for (let j = 0; j < calls.length; j++) {
|
|
68
|
+
const call = calls[j];
|
|
69
|
+
llmCalls.push({
|
|
70
|
+
id: call.callId || `${stepId}-call-${j}`,
|
|
71
|
+
trajectoryId,
|
|
72
|
+
stepId,
|
|
73
|
+
model: call.model || "unknown",
|
|
74
|
+
systemPrompt: call.systemPrompt || "",
|
|
75
|
+
userPrompt: call.userPrompt || "",
|
|
76
|
+
response: call.response || "",
|
|
77
|
+
temperature: typeof call.temperature === "number" ? call.temperature : 0,
|
|
78
|
+
maxTokens: typeof call.maxTokens === "number" ? call.maxTokens : 0,
|
|
79
|
+
purpose: call.purpose || "",
|
|
80
|
+
actionType: call.actionType || "",
|
|
81
|
+
latencyMs: call.latencyMs || 0,
|
|
82
|
+
promptTokens: call.promptTokens,
|
|
83
|
+
completionTokens: call.completionTokens,
|
|
84
|
+
timestamp: call.timestamp || step.timestamp,
|
|
85
|
+
createdAt: new Date(call.timestamp || step.timestamp).toISOString()
|
|
86
|
+
});
|
|
87
|
+
totalPromptTokens += call.promptTokens || 0;
|
|
88
|
+
totalCompletionTokens += call.completionTokens || 0;
|
|
89
|
+
}
|
|
90
|
+
const accesses = step.providerAccesses || [];
|
|
91
|
+
for (let k = 0; k < accesses.length; k++) {
|
|
92
|
+
const access = accesses[k];
|
|
93
|
+
providerAccesses.push({
|
|
94
|
+
id: access.providerId || `${stepId}-provider-${k}`,
|
|
95
|
+
trajectoryId,
|
|
96
|
+
stepId,
|
|
97
|
+
providerName: access.providerName || "unknown",
|
|
98
|
+
purpose: access.purpose || "",
|
|
99
|
+
data: access.data || {},
|
|
100
|
+
query: access.query,
|
|
101
|
+
timestamp: access.timestamp || step.timestamp,
|
|
102
|
+
createdAt: new Date(access.timestamp || step.timestamp).toISOString()
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const metadata = traj.metadata ?? {};
|
|
107
|
+
const normalizedDurationMs = status === "active" ? null : typeof traj.durationMs === "number" ? traj.durationMs : null;
|
|
108
|
+
const updatedAtMs = normalizedEndTime ?? (traj.startTime || Date.now());
|
|
109
|
+
return {
|
|
110
|
+
trajectory: {
|
|
111
|
+
id: trajectoryId,
|
|
112
|
+
agentId: String(traj.agentId),
|
|
113
|
+
roomId: toNullableString(metadata.roomId),
|
|
114
|
+
entityId: toNullableString(metadata.entityId),
|
|
115
|
+
conversationId: toNullableString(metadata.conversationId),
|
|
116
|
+
source: toNullableString(metadata.source) ?? "chat",
|
|
117
|
+
status,
|
|
118
|
+
startTime: traj.startTime,
|
|
119
|
+
endTime: normalizedEndTime,
|
|
120
|
+
durationMs: normalizedDurationMs,
|
|
121
|
+
llmCallCount: llmCalls.length,
|
|
122
|
+
providerAccessCount: providerAccesses.length,
|
|
123
|
+
totalPromptTokens,
|
|
124
|
+
totalCompletionTokens,
|
|
125
|
+
metadata,
|
|
126
|
+
createdAt: new Date(traj.startTime).toISOString(),
|
|
127
|
+
updatedAt: new Date(updatedAtMs).toISOString()
|
|
128
|
+
},
|
|
129
|
+
llmCalls,
|
|
130
|
+
providerAccesses
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
async function handleGetTrajectories(req, res, runtime) {
|
|
134
|
+
const logger = getTrajectoryLogger(runtime);
|
|
135
|
+
if (!logger) {
|
|
136
|
+
sendJsonError(res, "Trajectory logger service not available", 503);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
140
|
+
const options = {
|
|
141
|
+
limit: Math.min(500, Math.max(1, Number(url.searchParams.get("limit")) || 50)),
|
|
142
|
+
offset: Math.max(0, Number(url.searchParams.get("offset")) || 0),
|
|
143
|
+
source: url.searchParams.get("source") || void 0,
|
|
144
|
+
status: url.searchParams.get("status") || void 0,
|
|
145
|
+
startDate: url.searchParams.get("startDate") || void 0,
|
|
146
|
+
endDate: url.searchParams.get("endDate") || void 0,
|
|
147
|
+
search: url.searchParams.get("search") || void 0,
|
|
148
|
+
scenarioId: url.searchParams.get("scenarioId") || void 0,
|
|
149
|
+
batchId: url.searchParams.get("batchId") || void 0,
|
|
150
|
+
isTrainingData: url.searchParams.has("isTrainingData") ? url.searchParams.get("isTrainingData") === "true" : void 0
|
|
151
|
+
};
|
|
152
|
+
const result = await logger.listTrajectories(options);
|
|
153
|
+
sendJson(res, {
|
|
154
|
+
trajectories: result.trajectories.map(listItemToUIRecord),
|
|
155
|
+
total: result.total,
|
|
156
|
+
offset: result.offset,
|
|
157
|
+
limit: result.limit
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
async function handleGetTrajectoryDetail(_req, res, runtime, trajectoryId) {
|
|
161
|
+
const logger = getTrajectoryLogger(runtime);
|
|
162
|
+
if (!logger) {
|
|
163
|
+
sendJsonError(res, "Trajectory logger service not available", 503);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const trajectory = await logger.getTrajectoryDetail(trajectoryId);
|
|
167
|
+
if (!trajectory) {
|
|
168
|
+
sendJsonError(res, `Trajectory "${trajectoryId}" not found`, 404);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
sendJson(res, trajectoryToUIDetail(trajectory));
|
|
172
|
+
}
|
|
173
|
+
async function handleGetStats(_req, res, runtime) {
|
|
174
|
+
const logger = getTrajectoryLogger(runtime);
|
|
175
|
+
if (!logger) {
|
|
176
|
+
sendJsonError(res, "Trajectory logger service not available", 503);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
sendJson(res, await logger.getStats());
|
|
180
|
+
}
|
|
181
|
+
async function handleGetConfig(_req, res, runtime) {
|
|
182
|
+
const logger = getTrajectoryLogger(runtime);
|
|
183
|
+
if (!logger) {
|
|
184
|
+
sendJsonError(res, "Trajectory logger service not available", 503);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
sendJson(res, { enabled: logger.isEnabled() });
|
|
188
|
+
}
|
|
189
|
+
async function handlePutConfig(req, res, runtime) {
|
|
190
|
+
const logger = getTrajectoryLogger(runtime);
|
|
191
|
+
if (!logger) {
|
|
192
|
+
sendJsonError(res, "Trajectory logger service not available", 503);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const body = await readJsonBody(req, res);
|
|
196
|
+
if (!body) return;
|
|
197
|
+
if (typeof body.enabled === "boolean") logger.setEnabled(body.enabled);
|
|
198
|
+
sendJson(res, { enabled: logger.isEnabled() });
|
|
199
|
+
}
|
|
200
|
+
async function handleExportTrajectories(req, res, runtime) {
|
|
201
|
+
const logger = getTrajectoryLogger(runtime);
|
|
202
|
+
if (!logger) {
|
|
203
|
+
sendJsonError(res, "Trajectory logger service not available", 503);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const body = await readJsonBody(req, res);
|
|
207
|
+
if (!body) return;
|
|
208
|
+
if (body.format === "zip") {
|
|
209
|
+
if (typeof logger.exportTrajectoriesZip !== "function") {
|
|
210
|
+
sendJsonError(res, "Trajectory ZIP export is unavailable in the active logger", 503);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const zipOptions = {
|
|
214
|
+
includePrompts: body.includePrompts,
|
|
215
|
+
trajectoryIds: body.trajectoryIds,
|
|
216
|
+
startDate: body.startDate,
|
|
217
|
+
endDate: body.endDate,
|
|
218
|
+
scenarioId: body.scenarioId,
|
|
219
|
+
batchId: body.batchId
|
|
220
|
+
};
|
|
221
|
+
const zipResult = await logger.exportTrajectoriesZip(zipOptions);
|
|
222
|
+
const archive = createZipArchive(zipResult.entries);
|
|
223
|
+
res.statusCode = 200;
|
|
224
|
+
res.setHeader("Content-Type", "application/zip");
|
|
225
|
+
res.setHeader("Content-Disposition", `attachment; filename="${zipResult.filename}"`);
|
|
226
|
+
res.end(archive);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (body.format !== "json" && body.format !== "csv" && body.format !== "art") {
|
|
230
|
+
sendJsonError(res, "Format must be 'json', 'csv', 'art', or 'zip'", 400);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const result = await logger.exportTrajectories({
|
|
234
|
+
format: body.format,
|
|
235
|
+
includePrompts: body.includePrompts,
|
|
236
|
+
trajectoryIds: body.trajectoryIds,
|
|
237
|
+
startDate: body.startDate,
|
|
238
|
+
endDate: body.endDate,
|
|
239
|
+
scenarioId: body.scenarioId,
|
|
240
|
+
batchId: body.batchId
|
|
241
|
+
});
|
|
242
|
+
res.statusCode = 200;
|
|
243
|
+
res.setHeader("Content-Type", result.mimeType);
|
|
244
|
+
res.setHeader("Content-Disposition", `attachment; filename="${result.filename}"`);
|
|
245
|
+
res.end(result.data);
|
|
246
|
+
}
|
|
247
|
+
async function handleDeleteTrajectories(req, res, runtime) {
|
|
248
|
+
const logger = getTrajectoryLogger(runtime);
|
|
249
|
+
if (!logger) {
|
|
250
|
+
sendJsonError(res, "Trajectory logger service not available", 503);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const body = await readJsonBody(req, res);
|
|
254
|
+
if (!body) return;
|
|
255
|
+
if (body.all) {
|
|
256
|
+
sendJson(res, { deleted: await logger.clearAllTrajectories() });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (Array.isArray(body.trajectoryIds) && body.trajectoryIds.length > 0) {
|
|
260
|
+
sendJson(res, { deleted: await logger.deleteTrajectories(body.trajectoryIds) });
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
sendJson(res, { deleted: 0 });
|
|
264
|
+
}
|
|
265
|
+
async function handleTrajectoryRoute(req, res, runtime, pathname, method) {
|
|
266
|
+
if (!pathname.startsWith("/api/trajectories")) return false;
|
|
267
|
+
if (pathname === "/api/trajectories/config" && method === "GET") {
|
|
268
|
+
await handleGetConfig(req, res, runtime);
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
if (pathname === "/api/trajectories/config" && method === "PUT") {
|
|
272
|
+
await handlePutConfig(req, res, runtime);
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
if (pathname === "/api/trajectories/export" && method === "POST") {
|
|
276
|
+
await handleExportTrajectories(req, res, runtime);
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
if (pathname === "/api/trajectories" && method === "DELETE") {
|
|
280
|
+
await handleDeleteTrajectories(req, res, runtime);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
if (pathname === "/api/trajectories/stats" && method === "GET") {
|
|
284
|
+
await handleGetStats(req, res, runtime);
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
const detailMatch = pathname.match(/^\/api\/trajectories\/([^/]+)$/);
|
|
288
|
+
if (detailMatch && method === "GET") {
|
|
289
|
+
await handleGetTrajectoryDetail(req, res, runtime, detailMatch[1]);
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
if (pathname === "/api/trajectories" && method === "GET") {
|
|
293
|
+
await handleGetTrajectories(req, res, runtime);
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
//#endregion
|
|
300
|
+
export { handleTrajectoryRoute };
|