zoe-agent 0.3.1
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 +154 -0
- package/LICENSE +96 -0
- package/README.md +568 -0
- package/dist/adapters/cli/agent.d.ts +59 -0
- package/dist/adapters/cli/agent.js +232 -0
- package/dist/adapters/cli/bootstrap.d.ts +25 -0
- package/dist/adapters/cli/bootstrap.js +204 -0
- package/dist/adapters/cli/commands/build-registry.d.ts +14 -0
- package/dist/adapters/cli/commands/build-registry.js +88 -0
- package/dist/adapters/cli/commands/clear.d.ts +7 -0
- package/dist/adapters/cli/commands/clear.js +10 -0
- package/dist/adapters/cli/commands/compact.d.ts +13 -0
- package/dist/adapters/cli/commands/compact.js +96 -0
- package/dist/adapters/cli/commands/exit.d.ts +7 -0
- package/dist/adapters/cli/commands/exit.js +9 -0
- package/dist/adapters/cli/commands/gateway.d.ts +7 -0
- package/dist/adapters/cli/commands/gateway.js +152 -0
- package/dist/adapters/cli/commands/help.d.ts +9 -0
- package/dist/adapters/cli/commands/help.js +12 -0
- package/dist/adapters/cli/commands/models.d.ts +10 -0
- package/dist/adapters/cli/commands/models.js +32 -0
- package/dist/adapters/cli/commands/registry.d.ts +70 -0
- package/dist/adapters/cli/commands/registry.js +111 -0
- package/dist/adapters/cli/commands/settings-utils.d.ts +38 -0
- package/dist/adapters/cli/commands/settings-utils.js +182 -0
- package/dist/adapters/cli/commands/settings.d.ts +9 -0
- package/dist/adapters/cli/commands/settings.js +395 -0
- package/dist/adapters/cli/commands/skills.d.ts +7 -0
- package/dist/adapters/cli/commands/skills.js +21 -0
- package/dist/adapters/cli/config-loader.d.ts +27 -0
- package/dist/adapters/cli/config-loader.js +48 -0
- package/dist/adapters/cli/docker-utils.d.ts +37 -0
- package/dist/adapters/cli/docker-utils.js +90 -0
- package/dist/adapters/cli/index.d.ts +2 -0
- package/dist/adapters/cli/index.js +88 -0
- package/dist/adapters/cli/repl.d.ts +22 -0
- package/dist/adapters/cli/repl.js +256 -0
- package/dist/adapters/cli/setup.d.ts +19 -0
- package/dist/adapters/cli/setup.js +613 -0
- package/dist/adapters/cli/system-prompts.d.ts +56 -0
- package/dist/adapters/cli/system-prompts.js +131 -0
- package/dist/adapters/cli/tui/app.d.ts +58 -0
- package/dist/adapters/cli/tui/app.js +314 -0
- package/dist/adapters/cli/tui/components/assistant-message.d.ts +5 -0
- package/dist/adapters/cli/tui/components/assistant-message.js +9 -0
- package/dist/adapters/cli/tui/components/autocomplete.d.ts +19 -0
- package/dist/adapters/cli/tui/components/autocomplete.js +75 -0
- package/dist/adapters/cli/tui/components/command-palette.d.ts +15 -0
- package/dist/adapters/cli/tui/components/command-palette.js +50 -0
- package/dist/adapters/cli/tui/components/diff-viewer.d.ts +5 -0
- package/dist/adapters/cli/tui/components/diff-viewer.js +109 -0
- package/dist/adapters/cli/tui/components/error-message.d.ts +5 -0
- package/dist/adapters/cli/tui/components/error-message.js +8 -0
- package/dist/adapters/cli/tui/components/footer.d.ts +20 -0
- package/dist/adapters/cli/tui/components/footer.js +19 -0
- package/dist/adapters/cli/tui/components/goal-status.d.ts +12 -0
- package/dist/adapters/cli/tui/components/goal-status.js +22 -0
- package/dist/adapters/cli/tui/components/info-message.d.ts +5 -0
- package/dist/adapters/cli/tui/components/info-message.js +8 -0
- package/dist/adapters/cli/tui/components/logo-banner.d.ts +7 -0
- package/dist/adapters/cli/tui/components/logo-banner.js +33 -0
- package/dist/adapters/cli/tui/components/markdown.d.ts +9 -0
- package/dist/adapters/cli/tui/components/markdown.js +92 -0
- package/dist/adapters/cli/tui/components/message-area.d.ts +19 -0
- package/dist/adapters/cli/tui/components/message-area.js +55 -0
- package/dist/adapters/cli/tui/components/permission-prompt.d.ts +13 -0
- package/dist/adapters/cli/tui/components/permission-prompt.js +32 -0
- package/dist/adapters/cli/tui/components/prompt-area.d.ts +22 -0
- package/dist/adapters/cli/tui/components/prompt-area.js +68 -0
- package/dist/adapters/cli/tui/components/text-input.d.ts +27 -0
- package/dist/adapters/cli/tui/components/text-input.js +142 -0
- package/dist/adapters/cli/tui/components/tool-call-block.d.ts +11 -0
- package/dist/adapters/cli/tui/components/tool-call-block.js +68 -0
- package/dist/adapters/cli/tui/components/user-message.d.ts +5 -0
- package/dist/adapters/cli/tui/components/user-message.js +8 -0
- package/dist/adapters/cli/tui/diff/file-write-meta.d.ts +11 -0
- package/dist/adapters/cli/tui/diff/file-write-meta.js +11 -0
- package/dist/adapters/cli/tui/diff/line-diff.d.ts +17 -0
- package/dist/adapters/cli/tui/diff/line-diff.js +44 -0
- package/dist/adapters/cli/tui/feed-serializer.d.ts +29 -0
- package/dist/adapters/cli/tui/feed-serializer.js +70 -0
- package/dist/adapters/cli/tui/file-index.d.ts +8 -0
- package/dist/adapters/cli/tui/file-index.js +41 -0
- package/dist/adapters/cli/tui/hooks/use-agent.d.ts +54 -0
- package/dist/adapters/cli/tui/hooks/use-agent.js +177 -0
- package/dist/adapters/cli/tui/hooks/use-feed.d.ts +16 -0
- package/dist/adapters/cli/tui/hooks/use-feed.js +25 -0
- package/dist/adapters/cli/tui/hooks/use-file-watcher.d.ts +10 -0
- package/dist/adapters/cli/tui/hooks/use-file-watcher.js +43 -0
- package/dist/adapters/cli/tui/hooks/use-keybindings.d.ts +16 -0
- package/dist/adapters/cli/tui/hooks/use-keybindings.js +25 -0
- package/dist/adapters/cli/tui/hooks/use-theme.d.ts +8 -0
- package/dist/adapters/cli/tui/hooks/use-theme.js +12 -0
- package/dist/adapters/cli/tui/index.d.ts +19 -0
- package/dist/adapters/cli/tui/index.js +206 -0
- package/dist/adapters/cli/tui/ink-reset.d.ts +29 -0
- package/dist/adapters/cli/tui/ink-reset.js +57 -0
- package/dist/adapters/cli/tui/layout.d.ts +15 -0
- package/dist/adapters/cli/tui/layout.js +15 -0
- package/dist/adapters/cli/tui/logo/gradient.d.ts +11 -0
- package/dist/adapters/cli/tui/logo/gradient.js +31 -0
- package/dist/adapters/cli/tui/overlays/help-dialog.d.ts +4 -0
- package/dist/adapters/cli/tui/overlays/help-dialog.js +26 -0
- package/dist/adapters/cli/tui/overlays/model-selector.d.ts +14 -0
- package/dist/adapters/cli/tui/overlays/model-selector.js +43 -0
- package/dist/adapters/cli/tui/overlays/session-selector.d.ts +35 -0
- package/dist/adapters/cli/tui/overlays/session-selector.js +162 -0
- package/dist/adapters/cli/tui/overlays/settings-overlay.d.ts +24 -0
- package/dist/adapters/cli/tui/overlays/settings-overlay.js +126 -0
- package/dist/adapters/cli/tui/session-export.d.ts +21 -0
- package/dist/adapters/cli/tui/session-export.js +63 -0
- package/dist/adapters/cli/tui/theme.d.ts +23 -0
- package/dist/adapters/cli/tui/theme.js +22 -0
- package/dist/adapters/cli/tui/types.d.ts +52 -0
- package/dist/adapters/cli/tui/types.js +12 -0
- package/dist/adapters/sdk/agent.d.ts +20 -0
- package/dist/adapters/sdk/agent.js +356 -0
- package/dist/adapters/sdk/http.d.ts +43 -0
- package/dist/adapters/sdk/http.js +61 -0
- package/dist/adapters/sdk/index.d.ts +58 -0
- package/dist/adapters/sdk/index.js +209 -0
- package/dist/adapters/sdk/settings.d.ts +18 -0
- package/dist/adapters/sdk/settings.js +57 -0
- package/dist/adapters/sdk/tools.d.ts +7 -0
- package/dist/adapters/sdk/tools.js +13 -0
- package/dist/adapters/server/auth.d.ts +53 -0
- package/dist/adapters/server/auth.js +168 -0
- package/dist/adapters/server/index.d.ts +40 -0
- package/dist/adapters/server/index.js +255 -0
- package/dist/adapters/server/rest-gateway.d.ts +13 -0
- package/dist/adapters/server/rest-gateway.js +218 -0
- package/dist/adapters/server/rest.d.ts +37 -0
- package/dist/adapters/server/rest.js +341 -0
- package/dist/adapters/server/server-core.d.ts +55 -0
- package/dist/adapters/server/server-core.js +121 -0
- package/dist/adapters/server/session-store.d.ts +81 -0
- package/dist/adapters/server/session-store.js +272 -0
- package/dist/adapters/server/settings-handlers.d.ts +24 -0
- package/dist/adapters/server/settings-handlers.js +360 -0
- package/dist/adapters/server/standalone.d.ts +19 -0
- package/dist/adapters/server/standalone.js +113 -0
- package/dist/adapters/server/websocket.d.ts +26 -0
- package/dist/adapters/server/websocket.js +68 -0
- package/dist/adapters/server/ws-handlers.d.ts +32 -0
- package/dist/adapters/server/ws-handlers.js +523 -0
- package/dist/adapters/server/ws-types.d.ts +304 -0
- package/dist/adapters/server/ws-types.js +7 -0
- package/dist/core/agent-loop.d.ts +68 -0
- package/dist/core/agent-loop.js +423 -0
- package/dist/core/config.d.ts +115 -0
- package/dist/core/config.js +189 -0
- package/dist/core/errors.d.ts +58 -0
- package/dist/core/errors.js +88 -0
- package/dist/core/hooks.d.ts +35 -0
- package/dist/core/hooks.js +49 -0
- package/dist/core/index.d.ts +23 -0
- package/dist/core/index.js +29 -0
- package/dist/core/message-convert.d.ts +41 -0
- package/dist/core/message-convert.js +94 -0
- package/dist/core/middleware/auth.d.ts +24 -0
- package/dist/core/middleware/auth.js +28 -0
- package/dist/core/middleware/logging.d.ts +23 -0
- package/dist/core/middleware/logging.js +28 -0
- package/dist/core/middleware/rate-limit.d.ts +27 -0
- package/dist/core/middleware/rate-limit.js +38 -0
- package/dist/core/middleware/semantic-tools.d.ts +10 -0
- package/dist/core/middleware/semantic-tools.js +43 -0
- package/dist/core/middleware.d.ts +48 -0
- package/dist/core/middleware.js +38 -0
- package/dist/core/permission.d.ts +25 -0
- package/dist/core/permission.js +50 -0
- package/dist/core/provider-config.d.ts +129 -0
- package/dist/core/provider-config.js +273 -0
- package/dist/core/provider-env.d.ts +39 -0
- package/dist/core/provider-env.js +142 -0
- package/dist/core/provider-resolver.d.ts +12 -0
- package/dist/core/provider-resolver.js +12 -0
- package/dist/core/session-store.d.ts +75 -0
- package/dist/core/session-store.js +245 -0
- package/dist/core/settings-manager.d.ts +57 -0
- package/dist/core/settings-manager.js +359 -0
- package/dist/core/settings-schema.d.ts +38 -0
- package/dist/core/settings-schema.js +171 -0
- package/dist/core/skill-catalog.d.ts +6 -0
- package/dist/core/skill-catalog.js +17 -0
- package/dist/core/skill-invoker.d.ts +127 -0
- package/dist/core/skill-invoker.js +182 -0
- package/dist/core/stream-accumulator.d.ts +21 -0
- package/dist/core/stream-accumulator.js +51 -0
- package/dist/core/stream-manager.d.ts +58 -0
- package/dist/core/stream-manager.js +212 -0
- package/dist/core/tool-executor.d.ts +84 -0
- package/dist/core/tool-executor.js +256 -0
- package/dist/core/types.d.ts +259 -0
- package/dist/core/types.js +11 -0
- package/dist/gateway/gateway.d.ts +52 -0
- package/dist/gateway/gateway.js +537 -0
- package/dist/gateway/index.d.ts +21 -0
- package/dist/gateway/index.js +31 -0
- package/dist/gateway/openapi-importer.d.ts +15 -0
- package/dist/gateway/openapi-importer.js +66 -0
- package/dist/gateway/semantic-scorer.d.ts +7 -0
- package/dist/gateway/semantic-scorer.js +24 -0
- package/dist/gateway/settings-adapter.d.ts +49 -0
- package/dist/gateway/settings-adapter.js +137 -0
- package/dist/gateway/tool-factory.d.ts +9 -0
- package/dist/gateway/tool-factory.js +414 -0
- package/dist/gateway/types.d.ts +68 -0
- package/dist/gateway/types.js +7 -0
- package/dist/models-catalog.js +46 -0
- package/dist/providers/anthropic.d.ts +22 -0
- package/dist/providers/anthropic.js +148 -0
- package/dist/providers/factory.d.ts +10 -0
- package/dist/providers/factory.js +25 -0
- package/dist/providers/openai.d.ts +15 -0
- package/dist/providers/openai.js +71 -0
- package/dist/providers/types.d.ts +48 -0
- package/dist/providers/types.js +1 -0
- package/dist/skills/args.d.ts +37 -0
- package/dist/skills/args.js +99 -0
- package/dist/skills/index.d.ts +11 -0
- package/dist/skills/index.js +23 -0
- package/dist/skills/loader.d.ts +3 -0
- package/dist/skills/loader.js +59 -0
- package/dist/skills/parser.d.ts +7 -0
- package/dist/skills/parser.js +152 -0
- package/dist/skills/registry.d.ts +13 -0
- package/dist/skills/registry.js +74 -0
- package/dist/skills/resolver.d.ts +19 -0
- package/dist/skills/resolver.js +116 -0
- package/dist/skills/types.d.ts +74 -0
- package/dist/skills/types.js +50 -0
- package/dist/tools/browser.d.ts +2 -0
- package/dist/tools/browser.js +68 -0
- package/dist/tools/core.d.ts +20 -0
- package/dist/tools/core.js +244 -0
- package/dist/tools/email.d.ts +2 -0
- package/dist/tools/email.js +61 -0
- package/dist/tools/image.d.ts +2 -0
- package/dist/tools/image.js +257 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +88 -0
- package/dist/tools/interface.d.ts +22 -0
- package/dist/tools/interface.js +1 -0
- package/dist/tools/notify.d.ts +2 -0
- package/dist/tools/notify.js +100 -0
- package/dist/tools/prompt-optimizer.d.ts +2 -0
- package/dist/tools/prompt-optimizer.js +65 -0
- package/dist/tools/screenshot.d.ts +2 -0
- package/dist/tools/screenshot.js +184 -0
- package/dist/tools/search.d.ts +2 -0
- package/dist/tools/search.js +78 -0
- package/dist/tools/todos.d.ts +10 -0
- package/dist/tools/todos.js +50 -0
- package/package.json +119 -0
- package/skills/docker-ops/SKILL.md +329 -0
- package/skills/k8s-deploy/SKILL.md +397 -0
- package/skills/log-analyzer/SKILL.md +331 -0
- package/skills/speckit-analyze/SKILL.md +260 -0
- package/skills/speckit-checklist/SKILL.md +374 -0
- package/skills/speckit-clarify/SKILL.md +286 -0
- package/skills/speckit-constitution/SKILL.md +157 -0
- package/skills/speckit-implement/SKILL.md +224 -0
- package/skills/speckit-plan/SKILL.md +171 -0
- package/skills/speckit-specify/SKILL.md +346 -0
- package/skills/speckit-tasks/SKILL.md +215 -0
- package/skills/speckit-taskstoissues/SKILL.md +107 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zoe Server — REST Endpoint Handlers
|
|
3
|
+
*
|
|
4
|
+
* Processes incoming HTTP requests and routes them to the appropriate
|
|
5
|
+
* handler. All responses are JSON with proper Content-Type headers.
|
|
6
|
+
*/
|
|
7
|
+
import { authMiddleware, hasScope } from "./auth.js";
|
|
8
|
+
import { hashKey } from "./session-store.js";
|
|
9
|
+
import { handleGetSettings, handlePatchSettings, handleGetSettingsSchema, handlePostProvider, handlePatchProvider, handleDeleteProvider, } from "./settings-handlers.js";
|
|
10
|
+
// ── Helpers ────────────────────────────────────────────────────────────
|
|
11
|
+
function sendJSON(res, statusCode, data) {
|
|
12
|
+
const body = JSON.stringify(data);
|
|
13
|
+
res.writeHead(statusCode, {
|
|
14
|
+
"Content-Type": "application/json",
|
|
15
|
+
"Content-Length": Buffer.byteLength(body),
|
|
16
|
+
});
|
|
17
|
+
res.end(body);
|
|
18
|
+
}
|
|
19
|
+
function sendError(res, statusCode, code, message) {
|
|
20
|
+
sendJSON(res, statusCode, { error: { code, message } });
|
|
21
|
+
}
|
|
22
|
+
function parseBody(req) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const chunks = [];
|
|
25
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
26
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
27
|
+
req.on("error", reject);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function matchRoute(url, method) {
|
|
31
|
+
// Strip query string
|
|
32
|
+
const path = url.split("?")[0];
|
|
33
|
+
if (method === "GET" && path === "/v1/health") {
|
|
34
|
+
return { handler: "health", params: {} };
|
|
35
|
+
}
|
|
36
|
+
if (method === "GET" && path === "/v1/models") {
|
|
37
|
+
return { handler: "models", params: {} };
|
|
38
|
+
}
|
|
39
|
+
if (method === "GET" && path === "/v1/skills") {
|
|
40
|
+
return { handler: "skills", params: {} };
|
|
41
|
+
}
|
|
42
|
+
if (method === "POST" && path === "/v1/chat") {
|
|
43
|
+
return { handler: "chat", params: {} };
|
|
44
|
+
}
|
|
45
|
+
// Settings routes
|
|
46
|
+
if (method === "GET" && path === "/v1/settings/schema") {
|
|
47
|
+
return { handler: "settings_schema", params: {} };
|
|
48
|
+
}
|
|
49
|
+
if (method === "GET" && path === "/v1/settings") {
|
|
50
|
+
return { handler: "settings", params: {} };
|
|
51
|
+
}
|
|
52
|
+
if (method === "PATCH" && path === "/v1/settings") {
|
|
53
|
+
return { handler: "settings_patch", params: {} };
|
|
54
|
+
}
|
|
55
|
+
const settingsCategoryMatch = path.match(/^\/v1\/settings\/([a-z]+)$/);
|
|
56
|
+
if (settingsCategoryMatch) {
|
|
57
|
+
if (method === "GET")
|
|
58
|
+
return { handler: "settings", params: { category: settingsCategoryMatch[1] } };
|
|
59
|
+
if (method === "PATCH")
|
|
60
|
+
return { handler: "settings_patch", params: { category: settingsCategoryMatch[1] } };
|
|
61
|
+
}
|
|
62
|
+
// Provider routes
|
|
63
|
+
if (method === "POST" && path === "/v1/providers") {
|
|
64
|
+
return { handler: "provider_post", params: {} };
|
|
65
|
+
}
|
|
66
|
+
const providerMatch = path.match(/^\/v1\/providers\/([a-z-]+)$/);
|
|
67
|
+
if (providerMatch) {
|
|
68
|
+
if (method === "PATCH")
|
|
69
|
+
return { handler: "provider_patch", params: { type: providerMatch[1] } };
|
|
70
|
+
if (method === "DELETE")
|
|
71
|
+
return { handler: "provider_delete", params: { type: providerMatch[1] } };
|
|
72
|
+
}
|
|
73
|
+
// GET /v1/sessions/:id
|
|
74
|
+
const sessionMatch = path.match(/^\/v1\/sessions\/([a-f0-9-]+)$/);
|
|
75
|
+
if (method === "GET" && sessionMatch) {
|
|
76
|
+
return { handler: "session", params: { id: sessionMatch[1] } };
|
|
77
|
+
}
|
|
78
|
+
// Gateway routes — delegate to gateway handler
|
|
79
|
+
if (path.startsWith("/v1/gateway")) {
|
|
80
|
+
return { handler: "gateway", params: { path, method } };
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
// ── Main request handler ───────────────────────────────────────────────
|
|
85
|
+
/**
|
|
86
|
+
* Creates the main REST request handler.
|
|
87
|
+
* Returns a function compatible with http.createServer().
|
|
88
|
+
*/
|
|
89
|
+
export function createRestHandler(ctx) {
|
|
90
|
+
return async function handleRequest(req, res) {
|
|
91
|
+
const route = matchRoute(req.url ?? "/", req.method ?? "GET");
|
|
92
|
+
if (!route) {
|
|
93
|
+
sendError(res, 404, "NOT_FOUND", `No route for ${req.method} ${req.url}`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
switch (route.handler) {
|
|
98
|
+
case "health":
|
|
99
|
+
await handleHealth(req, res, ctx);
|
|
100
|
+
break;
|
|
101
|
+
case "models":
|
|
102
|
+
await handleModels(req, res, ctx);
|
|
103
|
+
break;
|
|
104
|
+
case "skills":
|
|
105
|
+
await handleSkills(req, res, ctx);
|
|
106
|
+
break;
|
|
107
|
+
case "chat":
|
|
108
|
+
await handleChat(req, res, ctx);
|
|
109
|
+
break;
|
|
110
|
+
case "session":
|
|
111
|
+
await handleGetSession(req, res, ctx, route.params.id);
|
|
112
|
+
break;
|
|
113
|
+
case "settings":
|
|
114
|
+
await handleSettingsGet(req, res, ctx, route.params.category);
|
|
115
|
+
break;
|
|
116
|
+
case "settings_schema":
|
|
117
|
+
await handleSettingsSchema(req, res, ctx);
|
|
118
|
+
break;
|
|
119
|
+
case "settings_patch":
|
|
120
|
+
await handleSettingsPatch(req, res, ctx, route.params.category);
|
|
121
|
+
break;
|
|
122
|
+
case "provider_post":
|
|
123
|
+
await handleProviderPost(req, res, ctx);
|
|
124
|
+
break;
|
|
125
|
+
case "provider_patch":
|
|
126
|
+
await handleProviderPatch(req, res, ctx, route.params.type);
|
|
127
|
+
break;
|
|
128
|
+
case "provider_delete":
|
|
129
|
+
await handleProviderDelete(req, res, ctx, route.params.type);
|
|
130
|
+
break;
|
|
131
|
+
case "gateway":
|
|
132
|
+
if (!ctx.gatewayHandler) {
|
|
133
|
+
sendError(res, 503, "SERVICE_UNAVAILABLE", "Gateway not configured");
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
await ctx.gatewayHandler(req, res, route.params.path, route.params.method);
|
|
137
|
+
break;
|
|
138
|
+
default:
|
|
139
|
+
sendError(res, 404, "NOT_FOUND", "Unknown endpoint");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
const message = err instanceof Error ? err.message : "Internal server error";
|
|
144
|
+
console.error("[rest] Unhandled error:", message);
|
|
145
|
+
sendError(res, 500, "INTERNAL_ERROR", message);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// ── Individual handlers ────────────────────────────────────────────────
|
|
150
|
+
async function handleHealth(_req, res, ctx) {
|
|
151
|
+
sendJSON(res, 200, {
|
|
152
|
+
status: "ok",
|
|
153
|
+
version: ctx.version,
|
|
154
|
+
uptime: Math.floor((Date.now() - ctx.startTime) / 1000),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
async function handleModels(req, res, ctx) {
|
|
158
|
+
const key = authMiddleware(req);
|
|
159
|
+
if (!key) {
|
|
160
|
+
sendError(res, 401, "UNAUTHORIZED", "Missing or invalid API key");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
sendJSON(res, 200, { models: ctx.listModels() });
|
|
164
|
+
}
|
|
165
|
+
async function handleSkills(req, res, ctx) {
|
|
166
|
+
const key = authMiddleware(req);
|
|
167
|
+
if (!key) {
|
|
168
|
+
sendError(res, 401, "UNAUTHORIZED", "Missing or invalid API key");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
sendJSON(res, 200, { skills: ctx.listSkills() });
|
|
172
|
+
}
|
|
173
|
+
async function handleChat(req, res, ctx) {
|
|
174
|
+
// Auth
|
|
175
|
+
const key = authMiddleware(req);
|
|
176
|
+
if (!key) {
|
|
177
|
+
sendError(res, 401, "UNAUTHORIZED", "Missing or invalid API key");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (!hasScope(key, "agent:run")) {
|
|
181
|
+
sendError(res, 403, "FORBIDDEN", "API key lacks 'agent:run' scope");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// Parse body
|
|
185
|
+
let body;
|
|
186
|
+
try {
|
|
187
|
+
body = await parseBody(req);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
sendError(res, 400, "BAD_REQUEST", "Failed to read request body");
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
let parsed;
|
|
194
|
+
try {
|
|
195
|
+
parsed = JSON.parse(body);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
sendError(res, 400, "BAD_REQUEST", "Invalid JSON in request body");
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
// Validate
|
|
202
|
+
if (!parsed.message || typeof parsed.message !== "string") {
|
|
203
|
+
sendError(res, 400, "BAD_REQUEST", "Field 'message' is required and must be a string");
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// Execute
|
|
207
|
+
try {
|
|
208
|
+
const result = await ctx.generateText({
|
|
209
|
+
message: parsed.message,
|
|
210
|
+
model: parsed.model,
|
|
211
|
+
provider: parsed.provider,
|
|
212
|
+
tools: parsed.tools,
|
|
213
|
+
maxSteps: parsed.maxSteps ?? 10,
|
|
214
|
+
skills: parsed.skills,
|
|
215
|
+
});
|
|
216
|
+
sendJSON(res, 200, {
|
|
217
|
+
text: result.text,
|
|
218
|
+
toolCalls: result.toolCalls,
|
|
219
|
+
usage: result.usage,
|
|
220
|
+
finishReason: result.finishReason,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
const message = err instanceof Error ? err.message : "Generation failed";
|
|
225
|
+
const isProviderError = message.includes("not configured") || message.includes("API key");
|
|
226
|
+
sendJSON(res, isProviderError ? 502 : 500, {
|
|
227
|
+
error: {
|
|
228
|
+
code: isProviderError ? "PROVIDER_ERROR" : "GENERATION_ERROR",
|
|
229
|
+
message,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async function handleGetSession(req, res, ctx, sessionId) {
|
|
235
|
+
const key = authMiddleware(req);
|
|
236
|
+
if (!key) {
|
|
237
|
+
sendError(res, 401, "UNAUTHORIZED", "Missing or invalid API key");
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (!hasScope(key, "agent:read")) {
|
|
241
|
+
sendError(res, 403, "FORBIDDEN", "API key lacks 'agent:read' scope");
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const session = await ctx.sessionManager.getSession(sessionId, hashKey(key.key));
|
|
245
|
+
if (!session) {
|
|
246
|
+
sendError(res, 404, "NOT_FOUND", `Session ${sessionId} not found`);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
sendJSON(res, 200, session);
|
|
250
|
+
}
|
|
251
|
+
// ── Settings handler wrappers ────────────────────────────────────────────
|
|
252
|
+
// Note: These require a SettingsHandlerContext with settingsManager.
|
|
253
|
+
// The server setup code must extend RestHandlerContext or provide settingsManager separately.
|
|
254
|
+
// For now, these check for settingsManager availability and return 503 if not configured.
|
|
255
|
+
function getSettingsCtx(ctx) {
|
|
256
|
+
return ctx.settingsHandlerContext ?? null;
|
|
257
|
+
}
|
|
258
|
+
async function handleSettingsGet(req, res, ctx, category) {
|
|
259
|
+
const key = authMiddleware(req);
|
|
260
|
+
if (!key) {
|
|
261
|
+
sendError(res, 401, "UNAUTHORIZED", "Missing or invalid API key");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
req.apiKey = key;
|
|
265
|
+
const sCtx = getSettingsCtx(ctx);
|
|
266
|
+
if (!sCtx) {
|
|
267
|
+
sendError(res, 503, "SERVICE_UNAVAILABLE", "Settings not configured");
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
await handleGetSettings(req, res, sCtx, category);
|
|
271
|
+
}
|
|
272
|
+
async function handleSettingsSchema(req, res, ctx) {
|
|
273
|
+
const key = authMiddleware(req);
|
|
274
|
+
if (!key) {
|
|
275
|
+
sendError(res, 401, "UNAUTHORIZED", "Missing or invalid API key");
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
req.apiKey = key;
|
|
279
|
+
const sCtx = getSettingsCtx(ctx);
|
|
280
|
+
if (!sCtx) {
|
|
281
|
+
sendError(res, 503, "SERVICE_UNAVAILABLE", "Settings not configured");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
await handleGetSettingsSchema(req, res, sCtx);
|
|
285
|
+
}
|
|
286
|
+
async function handleSettingsPatch(req, res, ctx, category) {
|
|
287
|
+
const key = authMiddleware(req);
|
|
288
|
+
if (!key) {
|
|
289
|
+
sendError(res, 401, "UNAUTHORIZED", "Missing or invalid API key");
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
req.apiKey = key;
|
|
293
|
+
const sCtx = getSettingsCtx(ctx);
|
|
294
|
+
if (!sCtx) {
|
|
295
|
+
sendError(res, 503, "SERVICE_UNAVAILABLE", "Settings not configured");
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
await handlePatchSettings(req, res, sCtx, category);
|
|
299
|
+
}
|
|
300
|
+
async function handleProviderPost(req, res, ctx) {
|
|
301
|
+
const key = authMiddleware(req);
|
|
302
|
+
if (!key) {
|
|
303
|
+
sendError(res, 401, "UNAUTHORIZED", "Missing or invalid API key");
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
req.apiKey = key;
|
|
307
|
+
const sCtx = getSettingsCtx(ctx);
|
|
308
|
+
if (!sCtx) {
|
|
309
|
+
sendError(res, 503, "SERVICE_UNAVAILABLE", "Settings not configured");
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
await handlePostProvider(req, res, sCtx);
|
|
313
|
+
}
|
|
314
|
+
async function handleProviderPatch(req, res, ctx, type) {
|
|
315
|
+
const key = authMiddleware(req);
|
|
316
|
+
if (!key) {
|
|
317
|
+
sendError(res, 401, "UNAUTHORIZED", "Missing or invalid API key");
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
req.apiKey = key;
|
|
321
|
+
const sCtx = getSettingsCtx(ctx);
|
|
322
|
+
if (!sCtx) {
|
|
323
|
+
sendError(res, 503, "SERVICE_UNAVAILABLE", "Settings not configured");
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
await handlePatchProvider(req, res, sCtx, type);
|
|
327
|
+
}
|
|
328
|
+
async function handleProviderDelete(req, res, ctx, type) {
|
|
329
|
+
const key = authMiddleware(req);
|
|
330
|
+
if (!key) {
|
|
331
|
+
sendError(res, 401, "UNAUTHORIZED", "Missing or invalid API key");
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
req.apiKey = key;
|
|
335
|
+
const sCtx = getSettingsCtx(ctx);
|
|
336
|
+
if (!sCtx) {
|
|
337
|
+
sendError(res, 503, "SERVICE_UNAVAILABLE", "Settings not configured");
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
await handleDeleteProvider(req, res, sCtx, type);
|
|
341
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ProviderType, GenerateTextResult, Usage, PermissionLevel, ApproveToolFn } from "../../core/types.js";
|
|
2
|
+
import type { Middleware } from "../../core/middleware.js";
|
|
3
|
+
/**
|
|
4
|
+
* Server-side generateText using core agent loop directly.
|
|
5
|
+
*/
|
|
6
|
+
export declare function serverGenerateText(options: {
|
|
7
|
+
message: string;
|
|
8
|
+
model?: string;
|
|
9
|
+
provider?: ProviderType;
|
|
10
|
+
tools?: string[];
|
|
11
|
+
maxSteps?: number;
|
|
12
|
+
skills?: string[];
|
|
13
|
+
}, permissionLevel: PermissionLevel, middleware?: Middleware[]): Promise<GenerateTextResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Server-side streamText using core agent loop directly.
|
|
16
|
+
*/
|
|
17
|
+
export declare function serverStreamText(opts: {
|
|
18
|
+
message: string;
|
|
19
|
+
model?: string;
|
|
20
|
+
provider?: ProviderType;
|
|
21
|
+
tools?: string[];
|
|
22
|
+
maxSteps?: number;
|
|
23
|
+
skills?: string[];
|
|
24
|
+
sessionId?: string;
|
|
25
|
+
permissionLevel?: PermissionLevel;
|
|
26
|
+
approveTool?: ApproveToolFn;
|
|
27
|
+
onText: (delta: string) => void;
|
|
28
|
+
onToolCall: (info: {
|
|
29
|
+
name: string;
|
|
30
|
+
args: Record<string, unknown>;
|
|
31
|
+
callId: string;
|
|
32
|
+
}) => void;
|
|
33
|
+
onToolResult: (info: {
|
|
34
|
+
callId: string;
|
|
35
|
+
output: string;
|
|
36
|
+
success: boolean;
|
|
37
|
+
}) => void;
|
|
38
|
+
onStep: (step: {
|
|
39
|
+
type: string;
|
|
40
|
+
content?: string;
|
|
41
|
+
timestamp: number;
|
|
42
|
+
}) => void;
|
|
43
|
+
onError: (error: {
|
|
44
|
+
code: string;
|
|
45
|
+
message: string;
|
|
46
|
+
provider?: string;
|
|
47
|
+
tool?: string;
|
|
48
|
+
}) => void;
|
|
49
|
+
onDone: (result: {
|
|
50
|
+
text: string;
|
|
51
|
+
usage: Usage;
|
|
52
|
+
finishReason: string;
|
|
53
|
+
}) => void;
|
|
54
|
+
signal?: AbortSignal;
|
|
55
|
+
}, serverPermissionLevel: PermissionLevel, middleware?: Middleware[]): Promise<void>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { runAgentLoop } from "../../core/agent-loop.js";
|
|
2
|
+
import { createHookExecutor } from "../../core/hooks.js";
|
|
3
|
+
import { resolveTools, getAllToolDefinitions } from "../../core/tool-executor.js";
|
|
4
|
+
import { generateId, now } from "../../core/message-convert.js";
|
|
5
|
+
import { getProvider } from "../../core/provider-resolver.js";
|
|
6
|
+
/**
|
|
7
|
+
* Server-side generateText using core agent loop directly.
|
|
8
|
+
*/
|
|
9
|
+
export async function serverGenerateText(options, permissionLevel, middleware) {
|
|
10
|
+
// Resolve provider
|
|
11
|
+
const { provider: llmProvider, model } = await getProvider(options.provider);
|
|
12
|
+
// Resolve tools
|
|
13
|
+
const toolDefs = options.tools ? resolveTools(options.tools) : getAllToolDefinitions();
|
|
14
|
+
// Hooks
|
|
15
|
+
const hooks = createHookExecutor();
|
|
16
|
+
// Build message list
|
|
17
|
+
const messages = [];
|
|
18
|
+
messages.push({
|
|
19
|
+
id: generateId(),
|
|
20
|
+
role: "user",
|
|
21
|
+
content: options.message,
|
|
22
|
+
timestamp: now(),
|
|
23
|
+
});
|
|
24
|
+
// Run the agent loop
|
|
25
|
+
const result = await runAgentLoop({
|
|
26
|
+
provider: llmProvider,
|
|
27
|
+
model: options.model ?? model,
|
|
28
|
+
messages,
|
|
29
|
+
toolDefs,
|
|
30
|
+
maxSteps: options.maxSteps ?? 5,
|
|
31
|
+
hooks,
|
|
32
|
+
permissionLevel,
|
|
33
|
+
middleware,
|
|
34
|
+
config: { agentName: "server" },
|
|
35
|
+
});
|
|
36
|
+
// Extract final text from last assistant message
|
|
37
|
+
const lastAssistant = [...result.messages]
|
|
38
|
+
.reverse()
|
|
39
|
+
.find((m) => m.role === "assistant" && m.content);
|
|
40
|
+
const text = lastAssistant?.content ?? "";
|
|
41
|
+
return {
|
|
42
|
+
text,
|
|
43
|
+
steps: result.steps,
|
|
44
|
+
toolCalls: result.toolCalls,
|
|
45
|
+
usage: result.usage,
|
|
46
|
+
finishReason: result.finishReason,
|
|
47
|
+
messages: result.messages,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Server-side streamText using core agent loop directly.
|
|
52
|
+
*/
|
|
53
|
+
export async function serverStreamText(opts, serverPermissionLevel, middleware) {
|
|
54
|
+
try {
|
|
55
|
+
// Resolve provider
|
|
56
|
+
const { provider: llmProvider, model } = await getProvider(opts.provider);
|
|
57
|
+
// Resolve tools
|
|
58
|
+
const toolDefs = opts.tools ? resolveTools(opts.tools) : getAllToolDefinitions();
|
|
59
|
+
// Hooks
|
|
60
|
+
const hooks = createHookExecutor();
|
|
61
|
+
// Build message list
|
|
62
|
+
const messages = [];
|
|
63
|
+
messages.push({
|
|
64
|
+
id: generateId(),
|
|
65
|
+
role: "user",
|
|
66
|
+
content: opts.message,
|
|
67
|
+
timestamp: now(),
|
|
68
|
+
});
|
|
69
|
+
// Accumulate text for the final result
|
|
70
|
+
let accumulatedText = "";
|
|
71
|
+
// Run the agent loop with onStep callbacks
|
|
72
|
+
const result = await runAgentLoop({
|
|
73
|
+
provider: llmProvider,
|
|
74
|
+
model: opts.model ?? model,
|
|
75
|
+
messages,
|
|
76
|
+
toolDefs,
|
|
77
|
+
maxSteps: opts.maxSteps ?? 5,
|
|
78
|
+
hooks,
|
|
79
|
+
permissionLevel: opts.permissionLevel ?? serverPermissionLevel,
|
|
80
|
+
approveTool: opts.approveTool,
|
|
81
|
+
signal: opts.signal,
|
|
82
|
+
middleware,
|
|
83
|
+
config: { agentName: "server" },
|
|
84
|
+
onStep: (step) => {
|
|
85
|
+
if (step.type === "text" && step.content) {
|
|
86
|
+
accumulatedText += step.content;
|
|
87
|
+
opts.onText(step.content);
|
|
88
|
+
}
|
|
89
|
+
if (step.type === "tool_call" && step.toolCall) {
|
|
90
|
+
opts.onToolCall({
|
|
91
|
+
name: step.toolCall.name,
|
|
92
|
+
args: step.toolCall.args,
|
|
93
|
+
callId: step.toolCall.id,
|
|
94
|
+
});
|
|
95
|
+
opts.onToolResult({
|
|
96
|
+
callId: step.toolCall.id,
|
|
97
|
+
output: step.toolCall.result,
|
|
98
|
+
success: !step.toolCall.result.startsWith("Error:"),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
opts.onStep(step);
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
opts.onDone({
|
|
105
|
+
text: accumulatedText,
|
|
106
|
+
usage: result.usage,
|
|
107
|
+
finishReason: result.finishReason,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
opts.onError({
|
|
112
|
+
code: "STREAM_ERROR",
|
|
113
|
+
message: err instanceof Error ? err.message : "Stream failed",
|
|
114
|
+
});
|
|
115
|
+
opts.onDone({
|
|
116
|
+
text: "",
|
|
117
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0, cost: 0 },
|
|
118
|
+
finishReason: "error",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zoe Server — Server-side Session Management
|
|
3
|
+
*
|
|
4
|
+
* Wraps a PersistenceBackend for server-specific needs:
|
|
5
|
+
* - TTL-based session expiration
|
|
6
|
+
* - Per-API-key concurrency limits
|
|
7
|
+
* - Periodic cleanup of stale sessions
|
|
8
|
+
*
|
|
9
|
+
* Raw storage is delegated to a PersistenceBackend (default: file-based).
|
|
10
|
+
* Server metadata (apiKeyHash, lastActivityAt) lives in memory and in
|
|
11
|
+
* the `metadata` field of SessionData.
|
|
12
|
+
*/
|
|
13
|
+
import type { ProviderType, Message, SessionData, PersistenceBackend } from "../../core/types.js";
|
|
14
|
+
export interface ServerSessionManagerOptions {
|
|
15
|
+
/** Session TTL in milliseconds (default: 24 hours) */
|
|
16
|
+
sessionTTL?: number;
|
|
17
|
+
/** Inactivity timeout in milliseconds (default: 30 minutes) */
|
|
18
|
+
inactivityTimeout?: number;
|
|
19
|
+
/** Max concurrent sessions per API key (default: 5) */
|
|
20
|
+
maxSessionsPerKey?: number;
|
|
21
|
+
/** Cleanup interval in milliseconds (default: 5 minutes) */
|
|
22
|
+
cleanupInterval?: number;
|
|
23
|
+
/** Directory for file-based session storage (ignored when `backend` is set) */
|
|
24
|
+
sessionDir?: string;
|
|
25
|
+
/** Custom persistence backend (overrides sessionDir) */
|
|
26
|
+
backend?: PersistenceBackend;
|
|
27
|
+
}
|
|
28
|
+
export declare function hashKey(key: string): string;
|
|
29
|
+
export declare class ServerSessionManager {
|
|
30
|
+
private sessions;
|
|
31
|
+
private sessionTTL;
|
|
32
|
+
private inactivityTimeout;
|
|
33
|
+
private maxSessionsPerKey;
|
|
34
|
+
private cleanupInterval;
|
|
35
|
+
private backend;
|
|
36
|
+
private cleanupTimer;
|
|
37
|
+
constructor(options?: ServerSessionManagerOptions);
|
|
38
|
+
/**
|
|
39
|
+
* Start the periodic cleanup timer.
|
|
40
|
+
*/
|
|
41
|
+
startCleanup(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Stop the periodic cleanup timer.
|
|
44
|
+
*/
|
|
45
|
+
stopCleanup(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Create a new session. Enforces per-API-key session limits.
|
|
48
|
+
* Returns the new SessionData or throws if the limit is exceeded.
|
|
49
|
+
* Awaits persistence — callers should await to ensure backend errors propagate.
|
|
50
|
+
*/
|
|
51
|
+
createSession(apiKey: string, provider?: ProviderType, model?: string): Promise<SessionData>;
|
|
52
|
+
/**
|
|
53
|
+
* Get a session by its ID, verifying ownership via API key hash.
|
|
54
|
+
* Returns null if the session does not exist, has expired, or is not owned
|
|
55
|
+
* by the provided API key.
|
|
56
|
+
*/
|
|
57
|
+
getSession(id: string, apiKeyHash: string): Promise<SessionData | null>;
|
|
58
|
+
/**
|
|
59
|
+
* Add a message to an existing session.
|
|
60
|
+
* Updates the last-activity timestamp.
|
|
61
|
+
*/
|
|
62
|
+
addMessage(sessionId: string, message: Message): void;
|
|
63
|
+
/**
|
|
64
|
+
* Delete a session by ID.
|
|
65
|
+
*/
|
|
66
|
+
deleteSession(id: string): void;
|
|
67
|
+
/**
|
|
68
|
+
* Get all active (non-expired) sessions.
|
|
69
|
+
*/
|
|
70
|
+
getActiveSessions(): SessionData[];
|
|
71
|
+
/**
|
|
72
|
+
* Remove expired sessions from memory and backend.
|
|
73
|
+
*/
|
|
74
|
+
cleanup(): void;
|
|
75
|
+
private getSessionsByKey;
|
|
76
|
+
private isExpired;
|
|
77
|
+
private persistSession;
|
|
78
|
+
private persistSessionAsync;
|
|
79
|
+
private loadSessionFromBackend;
|
|
80
|
+
private verifyOwnership;
|
|
81
|
+
}
|