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.
Files changed (267) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/LICENSE +96 -0
  3. package/README.md +568 -0
  4. package/dist/adapters/cli/agent.d.ts +59 -0
  5. package/dist/adapters/cli/agent.js +232 -0
  6. package/dist/adapters/cli/bootstrap.d.ts +25 -0
  7. package/dist/adapters/cli/bootstrap.js +204 -0
  8. package/dist/adapters/cli/commands/build-registry.d.ts +14 -0
  9. package/dist/adapters/cli/commands/build-registry.js +88 -0
  10. package/dist/adapters/cli/commands/clear.d.ts +7 -0
  11. package/dist/adapters/cli/commands/clear.js +10 -0
  12. package/dist/adapters/cli/commands/compact.d.ts +13 -0
  13. package/dist/adapters/cli/commands/compact.js +96 -0
  14. package/dist/adapters/cli/commands/exit.d.ts +7 -0
  15. package/dist/adapters/cli/commands/exit.js +9 -0
  16. package/dist/adapters/cli/commands/gateway.d.ts +7 -0
  17. package/dist/adapters/cli/commands/gateway.js +152 -0
  18. package/dist/adapters/cli/commands/help.d.ts +9 -0
  19. package/dist/adapters/cli/commands/help.js +12 -0
  20. package/dist/adapters/cli/commands/models.d.ts +10 -0
  21. package/dist/adapters/cli/commands/models.js +32 -0
  22. package/dist/adapters/cli/commands/registry.d.ts +70 -0
  23. package/dist/adapters/cli/commands/registry.js +111 -0
  24. package/dist/adapters/cli/commands/settings-utils.d.ts +38 -0
  25. package/dist/adapters/cli/commands/settings-utils.js +182 -0
  26. package/dist/adapters/cli/commands/settings.d.ts +9 -0
  27. package/dist/adapters/cli/commands/settings.js +395 -0
  28. package/dist/adapters/cli/commands/skills.d.ts +7 -0
  29. package/dist/adapters/cli/commands/skills.js +21 -0
  30. package/dist/adapters/cli/config-loader.d.ts +27 -0
  31. package/dist/adapters/cli/config-loader.js +48 -0
  32. package/dist/adapters/cli/docker-utils.d.ts +37 -0
  33. package/dist/adapters/cli/docker-utils.js +90 -0
  34. package/dist/adapters/cli/index.d.ts +2 -0
  35. package/dist/adapters/cli/index.js +88 -0
  36. package/dist/adapters/cli/repl.d.ts +22 -0
  37. package/dist/adapters/cli/repl.js +256 -0
  38. package/dist/adapters/cli/setup.d.ts +19 -0
  39. package/dist/adapters/cli/setup.js +613 -0
  40. package/dist/adapters/cli/system-prompts.d.ts +56 -0
  41. package/dist/adapters/cli/system-prompts.js +131 -0
  42. package/dist/adapters/cli/tui/app.d.ts +58 -0
  43. package/dist/adapters/cli/tui/app.js +314 -0
  44. package/dist/adapters/cli/tui/components/assistant-message.d.ts +5 -0
  45. package/dist/adapters/cli/tui/components/assistant-message.js +9 -0
  46. package/dist/adapters/cli/tui/components/autocomplete.d.ts +19 -0
  47. package/dist/adapters/cli/tui/components/autocomplete.js +75 -0
  48. package/dist/adapters/cli/tui/components/command-palette.d.ts +15 -0
  49. package/dist/adapters/cli/tui/components/command-palette.js +50 -0
  50. package/dist/adapters/cli/tui/components/diff-viewer.d.ts +5 -0
  51. package/dist/adapters/cli/tui/components/diff-viewer.js +109 -0
  52. package/dist/adapters/cli/tui/components/error-message.d.ts +5 -0
  53. package/dist/adapters/cli/tui/components/error-message.js +8 -0
  54. package/dist/adapters/cli/tui/components/footer.d.ts +20 -0
  55. package/dist/adapters/cli/tui/components/footer.js +19 -0
  56. package/dist/adapters/cli/tui/components/goal-status.d.ts +12 -0
  57. package/dist/adapters/cli/tui/components/goal-status.js +22 -0
  58. package/dist/adapters/cli/tui/components/info-message.d.ts +5 -0
  59. package/dist/adapters/cli/tui/components/info-message.js +8 -0
  60. package/dist/adapters/cli/tui/components/logo-banner.d.ts +7 -0
  61. package/dist/adapters/cli/tui/components/logo-banner.js +33 -0
  62. package/dist/adapters/cli/tui/components/markdown.d.ts +9 -0
  63. package/dist/adapters/cli/tui/components/markdown.js +92 -0
  64. package/dist/adapters/cli/tui/components/message-area.d.ts +19 -0
  65. package/dist/adapters/cli/tui/components/message-area.js +55 -0
  66. package/dist/adapters/cli/tui/components/permission-prompt.d.ts +13 -0
  67. package/dist/adapters/cli/tui/components/permission-prompt.js +32 -0
  68. package/dist/adapters/cli/tui/components/prompt-area.d.ts +22 -0
  69. package/dist/adapters/cli/tui/components/prompt-area.js +68 -0
  70. package/dist/adapters/cli/tui/components/text-input.d.ts +27 -0
  71. package/dist/adapters/cli/tui/components/text-input.js +142 -0
  72. package/dist/adapters/cli/tui/components/tool-call-block.d.ts +11 -0
  73. package/dist/adapters/cli/tui/components/tool-call-block.js +68 -0
  74. package/dist/adapters/cli/tui/components/user-message.d.ts +5 -0
  75. package/dist/adapters/cli/tui/components/user-message.js +8 -0
  76. package/dist/adapters/cli/tui/diff/file-write-meta.d.ts +11 -0
  77. package/dist/adapters/cli/tui/diff/file-write-meta.js +11 -0
  78. package/dist/adapters/cli/tui/diff/line-diff.d.ts +17 -0
  79. package/dist/adapters/cli/tui/diff/line-diff.js +44 -0
  80. package/dist/adapters/cli/tui/feed-serializer.d.ts +29 -0
  81. package/dist/adapters/cli/tui/feed-serializer.js +70 -0
  82. package/dist/adapters/cli/tui/file-index.d.ts +8 -0
  83. package/dist/adapters/cli/tui/file-index.js +41 -0
  84. package/dist/adapters/cli/tui/hooks/use-agent.d.ts +54 -0
  85. package/dist/adapters/cli/tui/hooks/use-agent.js +177 -0
  86. package/dist/adapters/cli/tui/hooks/use-feed.d.ts +16 -0
  87. package/dist/adapters/cli/tui/hooks/use-feed.js +25 -0
  88. package/dist/adapters/cli/tui/hooks/use-file-watcher.d.ts +10 -0
  89. package/dist/adapters/cli/tui/hooks/use-file-watcher.js +43 -0
  90. package/dist/adapters/cli/tui/hooks/use-keybindings.d.ts +16 -0
  91. package/dist/adapters/cli/tui/hooks/use-keybindings.js +25 -0
  92. package/dist/adapters/cli/tui/hooks/use-theme.d.ts +8 -0
  93. package/dist/adapters/cli/tui/hooks/use-theme.js +12 -0
  94. package/dist/adapters/cli/tui/index.d.ts +19 -0
  95. package/dist/adapters/cli/tui/index.js +206 -0
  96. package/dist/adapters/cli/tui/ink-reset.d.ts +29 -0
  97. package/dist/adapters/cli/tui/ink-reset.js +57 -0
  98. package/dist/adapters/cli/tui/layout.d.ts +15 -0
  99. package/dist/adapters/cli/tui/layout.js +15 -0
  100. package/dist/adapters/cli/tui/logo/gradient.d.ts +11 -0
  101. package/dist/adapters/cli/tui/logo/gradient.js +31 -0
  102. package/dist/adapters/cli/tui/overlays/help-dialog.d.ts +4 -0
  103. package/dist/adapters/cli/tui/overlays/help-dialog.js +26 -0
  104. package/dist/adapters/cli/tui/overlays/model-selector.d.ts +14 -0
  105. package/dist/adapters/cli/tui/overlays/model-selector.js +43 -0
  106. package/dist/adapters/cli/tui/overlays/session-selector.d.ts +35 -0
  107. package/dist/adapters/cli/tui/overlays/session-selector.js +162 -0
  108. package/dist/adapters/cli/tui/overlays/settings-overlay.d.ts +24 -0
  109. package/dist/adapters/cli/tui/overlays/settings-overlay.js +126 -0
  110. package/dist/adapters/cli/tui/session-export.d.ts +21 -0
  111. package/dist/adapters/cli/tui/session-export.js +63 -0
  112. package/dist/adapters/cli/tui/theme.d.ts +23 -0
  113. package/dist/adapters/cli/tui/theme.js +22 -0
  114. package/dist/adapters/cli/tui/types.d.ts +52 -0
  115. package/dist/adapters/cli/tui/types.js +12 -0
  116. package/dist/adapters/sdk/agent.d.ts +20 -0
  117. package/dist/adapters/sdk/agent.js +356 -0
  118. package/dist/adapters/sdk/http.d.ts +43 -0
  119. package/dist/adapters/sdk/http.js +61 -0
  120. package/dist/adapters/sdk/index.d.ts +58 -0
  121. package/dist/adapters/sdk/index.js +209 -0
  122. package/dist/adapters/sdk/settings.d.ts +18 -0
  123. package/dist/adapters/sdk/settings.js +57 -0
  124. package/dist/adapters/sdk/tools.d.ts +7 -0
  125. package/dist/adapters/sdk/tools.js +13 -0
  126. package/dist/adapters/server/auth.d.ts +53 -0
  127. package/dist/adapters/server/auth.js +168 -0
  128. package/dist/adapters/server/index.d.ts +40 -0
  129. package/dist/adapters/server/index.js +255 -0
  130. package/dist/adapters/server/rest-gateway.d.ts +13 -0
  131. package/dist/adapters/server/rest-gateway.js +218 -0
  132. package/dist/adapters/server/rest.d.ts +37 -0
  133. package/dist/adapters/server/rest.js +341 -0
  134. package/dist/adapters/server/server-core.d.ts +55 -0
  135. package/dist/adapters/server/server-core.js +121 -0
  136. package/dist/adapters/server/session-store.d.ts +81 -0
  137. package/dist/adapters/server/session-store.js +272 -0
  138. package/dist/adapters/server/settings-handlers.d.ts +24 -0
  139. package/dist/adapters/server/settings-handlers.js +360 -0
  140. package/dist/adapters/server/standalone.d.ts +19 -0
  141. package/dist/adapters/server/standalone.js +113 -0
  142. package/dist/adapters/server/websocket.d.ts +26 -0
  143. package/dist/adapters/server/websocket.js +68 -0
  144. package/dist/adapters/server/ws-handlers.d.ts +32 -0
  145. package/dist/adapters/server/ws-handlers.js +523 -0
  146. package/dist/adapters/server/ws-types.d.ts +304 -0
  147. package/dist/adapters/server/ws-types.js +7 -0
  148. package/dist/core/agent-loop.d.ts +68 -0
  149. package/dist/core/agent-loop.js +423 -0
  150. package/dist/core/config.d.ts +115 -0
  151. package/dist/core/config.js +189 -0
  152. package/dist/core/errors.d.ts +58 -0
  153. package/dist/core/errors.js +88 -0
  154. package/dist/core/hooks.d.ts +35 -0
  155. package/dist/core/hooks.js +49 -0
  156. package/dist/core/index.d.ts +23 -0
  157. package/dist/core/index.js +29 -0
  158. package/dist/core/message-convert.d.ts +41 -0
  159. package/dist/core/message-convert.js +94 -0
  160. package/dist/core/middleware/auth.d.ts +24 -0
  161. package/dist/core/middleware/auth.js +28 -0
  162. package/dist/core/middleware/logging.d.ts +23 -0
  163. package/dist/core/middleware/logging.js +28 -0
  164. package/dist/core/middleware/rate-limit.d.ts +27 -0
  165. package/dist/core/middleware/rate-limit.js +38 -0
  166. package/dist/core/middleware/semantic-tools.d.ts +10 -0
  167. package/dist/core/middleware/semantic-tools.js +43 -0
  168. package/dist/core/middleware.d.ts +48 -0
  169. package/dist/core/middleware.js +38 -0
  170. package/dist/core/permission.d.ts +25 -0
  171. package/dist/core/permission.js +50 -0
  172. package/dist/core/provider-config.d.ts +129 -0
  173. package/dist/core/provider-config.js +273 -0
  174. package/dist/core/provider-env.d.ts +39 -0
  175. package/dist/core/provider-env.js +142 -0
  176. package/dist/core/provider-resolver.d.ts +12 -0
  177. package/dist/core/provider-resolver.js +12 -0
  178. package/dist/core/session-store.d.ts +75 -0
  179. package/dist/core/session-store.js +245 -0
  180. package/dist/core/settings-manager.d.ts +57 -0
  181. package/dist/core/settings-manager.js +359 -0
  182. package/dist/core/settings-schema.d.ts +38 -0
  183. package/dist/core/settings-schema.js +171 -0
  184. package/dist/core/skill-catalog.d.ts +6 -0
  185. package/dist/core/skill-catalog.js +17 -0
  186. package/dist/core/skill-invoker.d.ts +127 -0
  187. package/dist/core/skill-invoker.js +182 -0
  188. package/dist/core/stream-accumulator.d.ts +21 -0
  189. package/dist/core/stream-accumulator.js +51 -0
  190. package/dist/core/stream-manager.d.ts +58 -0
  191. package/dist/core/stream-manager.js +212 -0
  192. package/dist/core/tool-executor.d.ts +84 -0
  193. package/dist/core/tool-executor.js +256 -0
  194. package/dist/core/types.d.ts +259 -0
  195. package/dist/core/types.js +11 -0
  196. package/dist/gateway/gateway.d.ts +52 -0
  197. package/dist/gateway/gateway.js +537 -0
  198. package/dist/gateway/index.d.ts +21 -0
  199. package/dist/gateway/index.js +31 -0
  200. package/dist/gateway/openapi-importer.d.ts +15 -0
  201. package/dist/gateway/openapi-importer.js +66 -0
  202. package/dist/gateway/semantic-scorer.d.ts +7 -0
  203. package/dist/gateway/semantic-scorer.js +24 -0
  204. package/dist/gateway/settings-adapter.d.ts +49 -0
  205. package/dist/gateway/settings-adapter.js +137 -0
  206. package/dist/gateway/tool-factory.d.ts +9 -0
  207. package/dist/gateway/tool-factory.js +414 -0
  208. package/dist/gateway/types.d.ts +68 -0
  209. package/dist/gateway/types.js +7 -0
  210. package/dist/models-catalog.js +46 -0
  211. package/dist/providers/anthropic.d.ts +22 -0
  212. package/dist/providers/anthropic.js +148 -0
  213. package/dist/providers/factory.d.ts +10 -0
  214. package/dist/providers/factory.js +25 -0
  215. package/dist/providers/openai.d.ts +15 -0
  216. package/dist/providers/openai.js +71 -0
  217. package/dist/providers/types.d.ts +48 -0
  218. package/dist/providers/types.js +1 -0
  219. package/dist/skills/args.d.ts +37 -0
  220. package/dist/skills/args.js +99 -0
  221. package/dist/skills/index.d.ts +11 -0
  222. package/dist/skills/index.js +23 -0
  223. package/dist/skills/loader.d.ts +3 -0
  224. package/dist/skills/loader.js +59 -0
  225. package/dist/skills/parser.d.ts +7 -0
  226. package/dist/skills/parser.js +152 -0
  227. package/dist/skills/registry.d.ts +13 -0
  228. package/dist/skills/registry.js +74 -0
  229. package/dist/skills/resolver.d.ts +19 -0
  230. package/dist/skills/resolver.js +116 -0
  231. package/dist/skills/types.d.ts +74 -0
  232. package/dist/skills/types.js +50 -0
  233. package/dist/tools/browser.d.ts +2 -0
  234. package/dist/tools/browser.js +68 -0
  235. package/dist/tools/core.d.ts +20 -0
  236. package/dist/tools/core.js +244 -0
  237. package/dist/tools/email.d.ts +2 -0
  238. package/dist/tools/email.js +61 -0
  239. package/dist/tools/image.d.ts +2 -0
  240. package/dist/tools/image.js +257 -0
  241. package/dist/tools/index.d.ts +2 -0
  242. package/dist/tools/index.js +88 -0
  243. package/dist/tools/interface.d.ts +22 -0
  244. package/dist/tools/interface.js +1 -0
  245. package/dist/tools/notify.d.ts +2 -0
  246. package/dist/tools/notify.js +100 -0
  247. package/dist/tools/prompt-optimizer.d.ts +2 -0
  248. package/dist/tools/prompt-optimizer.js +65 -0
  249. package/dist/tools/screenshot.d.ts +2 -0
  250. package/dist/tools/screenshot.js +184 -0
  251. package/dist/tools/search.d.ts +2 -0
  252. package/dist/tools/search.js +78 -0
  253. package/dist/tools/todos.d.ts +10 -0
  254. package/dist/tools/todos.js +50 -0
  255. package/package.json +119 -0
  256. package/skills/docker-ops/SKILL.md +329 -0
  257. package/skills/k8s-deploy/SKILL.md +397 -0
  258. package/skills/log-analyzer/SKILL.md +331 -0
  259. package/skills/speckit-analyze/SKILL.md +260 -0
  260. package/skills/speckit-checklist/SKILL.md +374 -0
  261. package/skills/speckit-clarify/SKILL.md +286 -0
  262. package/skills/speckit-constitution/SKILL.md +157 -0
  263. package/skills/speckit-implement/SKILL.md +224 -0
  264. package/skills/speckit-plan/SKILL.md +171 -0
  265. package/skills/speckit-specify/SKILL.md +346 -0
  266. package/skills/speckit-tasks/SKILL.md +215 -0
  267. 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
+ }