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,255 @@
1
+ /**
2
+ * Zoe Remote Server — Entry Point
3
+ *
4
+ * Creates an HTTP server with REST endpoints and WebSocket support
5
+ * for real-time streaming conversations with LLM providers.
6
+ *
7
+ * Default port: 7337
8
+ */
9
+ import * as http from "http";
10
+ import * as fs from "fs";
11
+ import * as path from "path";
12
+ import { homedir } from "os";
13
+ import { configureProviders, resolveFromEnv } from "../../core/provider-resolver.js";
14
+ import { serverGenerateText, serverStreamText } from "./server-core.js";
15
+ import { createRestHandler } from "./rest.js";
16
+ import { setupWebSocket, closeWebSocket } from "./websocket.js";
17
+ import { getOtherClients } from "./ws-handlers.js";
18
+ import { ServerSessionManager } from "./session-store.js";
19
+ import { SettingsManager } from "../../core/settings-manager.js";
20
+ import { loadMergedConfig, getConfigPaths, loadJsonConfig } from "../../core/config.js";
21
+ import { MODEL_CATALOG } from "../../models-catalog.js";
22
+ // ── Helpers ────────────────────────────────────────────────────────────
23
+ function resolveVersion() {
24
+ try {
25
+ // Try relative to dist/ first (production), then src/ (development)
26
+ const pkgPath = path.join(import.meta.dirname ?? ".", "..", "..", "package.json");
27
+ const raw = fs.readFileSync(pkgPath, "utf-8");
28
+ return JSON.parse(raw).version ?? "0.0.0";
29
+ }
30
+ catch {
31
+ return "0.0.0";
32
+ }
33
+ }
34
+ function resolvePort(options) {
35
+ if (options?.port)
36
+ return options.port;
37
+ const fromEnv = parseInt(process.env.ZOE_PORT ?? process.env.PORT ?? "", 10);
38
+ if (!isNaN(fromEnv) && fromEnv > 0)
39
+ return fromEnv;
40
+ return 7337;
41
+ }
42
+ // ── Provider initialization ────────────────────────────────────────────
43
+ function initializeProvidersFromEnv() {
44
+ const config = resolveFromEnv();
45
+ if (config) {
46
+ configureProviders(config);
47
+ }
48
+ }
49
+ function listModels() {
50
+ const result = {
51
+ openai: [],
52
+ anthropic: [],
53
+ glm: [],
54
+ "openai-compatible": [],
55
+ };
56
+ for (const [provider, entries] of Object.entries(MODEL_CATALOG)) {
57
+ if (provider in result) {
58
+ result[provider] = entries.map((e) => e.id);
59
+ }
60
+ }
61
+ return result;
62
+ }
63
+ /**
64
+ * Cached skill list — populated asynchronously at startup.
65
+ */
66
+ let cachedSkillList = [];
67
+ /**
68
+ * Initialize the skill registry and cache the skill metadata list.
69
+ * Called once during server startup.
70
+ */
71
+ export async function initializeSkills() {
72
+ try {
73
+ const { getSkillRegistry } = await import("../../skills/index.js");
74
+ const registry = getSkillRegistry();
75
+ if (registry) {
76
+ cachedSkillList = registry.getMetadata().map((s) => ({
77
+ name: s.name,
78
+ description: s.description,
79
+ tags: s.tags,
80
+ }));
81
+ }
82
+ }
83
+ catch {
84
+ // Skills system not available — keep empty list
85
+ }
86
+ }
87
+ function listSkills() {
88
+ return cachedSkillList;
89
+ }
90
+ // ── CORS helper ────────────────────────────────────────────────────────
91
+ function addCORSHeaders(req, res) {
92
+ const origin = req.headers.origin ?? "*";
93
+ res.setHeader("Access-Control-Allow-Origin", origin);
94
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS");
95
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Zoe-API-Key");
96
+ res.setHeader("Access-Control-Max-Age", "86400");
97
+ }
98
+ function isPreflight(req) {
99
+ return req.method === "OPTIONS";
100
+ }
101
+ function handlePreflight(_req, res) {
102
+ res.writeHead(204);
103
+ res.end();
104
+ }
105
+ // ── Server creation ────────────────────────────────────────────────────
106
+ /**
107
+ * Create and return the Zoe HTTP server (not yet listening).
108
+ *
109
+ * This sets up REST endpoints, WebSocket upgrade handling,
110
+ * session management, and CORS support.
111
+ */
112
+ export async function createServer(options) {
113
+ const version = resolveVersion();
114
+ const startTime = Date.now();
115
+ // Initialize providers from environment
116
+ initializeProvidersFromEnv();
117
+ const serverPermissionLevel = options?.permissionLevel ?? "moderate";
118
+ // Resolve session directory
119
+ const sessionDir = process.env.ZOE_SESSION_DIR ??
120
+ path.join(process.cwd(), ".zoe", "sessions");
121
+ const sessionTTL = (options?.sessionTTL ?? parseInt(process.env.ZOE_SESSION_TTL ?? "86400", 10)) * 1000;
122
+ // Create session manager
123
+ const sessionManager = new ServerSessionManager({
124
+ sessionDir,
125
+ sessionTTL,
126
+ });
127
+ sessionManager.startCleanup();
128
+ // Create settings handler context (shared by REST and WS)
129
+ const configPaths = getConfigPaths();
130
+ const mergedConfig = loadMergedConfig();
131
+ const projectConfig = loadJsonConfig(configPaths.local);
132
+ const globalConfig = loadJsonConfig(configPaths.global);
133
+ const settingsManager = new SettingsManager({
134
+ config: mergedConfig,
135
+ projectConfigPath: configPaths.local,
136
+ globalConfigPath: configPaths.global,
137
+ projectConfig: projectConfig.config,
138
+ globalConfig: globalConfig.config,
139
+ });
140
+ const settingsHandlerContext = {
141
+ settingsManager,
142
+ getOtherClients,
143
+ };
144
+ // Initialize gateway (if enabled)
145
+ let gatewayHandler;
146
+ let gatewayMiddleware;
147
+ try {
148
+ const gwEnabled = settingsManager.get("gateway.enabled").value;
149
+ if (gwEnabled) {
150
+ const gatewayConfig = {
151
+ enabled: true,
152
+ semanticTopK: settingsManager.get("gateway.semanticTopK").value,
153
+ defaultRateLimitPerMin: settingsManager.get("gateway.defaultRateLimitPerMin").value,
154
+ maxAuditLogsInMemory: settingsManager.get("gateway.maxAuditLogs").value,
155
+ };
156
+ const { GatewaySettingsAdapter } = await import("../../gateway/settings-adapter.js");
157
+ const gatewayStorageDir = process.env.ZOE_GATEWAY_DIR ?? path.join(homedir(), ".zoe");
158
+ const gwSettingsAdapter = new GatewaySettingsAdapter(gatewayStorageDir);
159
+ await gwSettingsAdapter.initialize();
160
+ // Use createGateway factory — registers 10 proxy tools in static registry
161
+ const { createGateway } = await import("../../gateway/index.js");
162
+ const gatewayInstance = await createGateway(gatewayConfig, gwSettingsAdapter);
163
+ if (gatewayInstance) {
164
+ const { createGatewayRestHandler } = await import("./rest-gateway.js");
165
+ gatewayHandler = createGatewayRestHandler({ gateway: gatewayInstance, settingsAdapter: gwSettingsAdapter });
166
+ // Wire semantic injection middleware
167
+ const { semanticToolInjectionMiddleware } = await import("../../core/middleware/semantic-tools.js");
168
+ gatewayMiddleware = [semanticToolInjectionMiddleware(gatewayInstance, gatewayConfig.semanticTopK)];
169
+ }
170
+ }
171
+ }
172
+ catch (e) {
173
+ console.error("[server] Gateway initialization failed:", e instanceof Error ? e.message : String(e));
174
+ }
175
+ // Create REST handler context
176
+ const restCtx = {
177
+ version,
178
+ startTime,
179
+ sessionManager,
180
+ generateText: (opts) => serverGenerateText(opts, serverPermissionLevel, gatewayMiddleware),
181
+ listModels,
182
+ listSkills,
183
+ settingsHandlerContext,
184
+ gatewayHandler,
185
+ };
186
+ const restHandler = createRestHandler(restCtx);
187
+ // Create HTTP server
188
+ const enableCors = options?.cors ?? true;
189
+ const server = http.createServer((req, res) => {
190
+ // CORS
191
+ if (enableCors) {
192
+ addCORSHeaders(req, res);
193
+ }
194
+ // Preflight
195
+ if (isPreflight(req)) {
196
+ handlePreflight(req, res);
197
+ return;
198
+ }
199
+ // Delegate to REST handler
200
+ restHandler(req, res);
201
+ });
202
+ // Create WebSocket handler context
203
+ const wsCtx = {
204
+ sessionManager,
205
+ streamText: (opts) => {
206
+ serverStreamText(opts, serverPermissionLevel, gatewayMiddleware).catch((err) => {
207
+ opts.onError({
208
+ code: "STREAM_ERROR",
209
+ message: err instanceof Error ? err.message : "Stream failed",
210
+ });
211
+ opts.onDone({
212
+ text: "",
213
+ usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0, cost: 0 },
214
+ finishReason: "error",
215
+ });
216
+ });
217
+ },
218
+ listModels,
219
+ listSkills,
220
+ maxPermissionLevel: options?.maxPermissionLevel,
221
+ settingsHandlerContext,
222
+ };
223
+ // Set up WebSocket (async, but we wait for it)
224
+ await setupWebSocket(server, wsCtx);
225
+ // Graceful shutdown handler
226
+ const shutdown = () => {
227
+ console.log("[server] Shutting down...");
228
+ sessionManager.stopCleanup();
229
+ closeWebSocket();
230
+ server.close(() => {
231
+ console.log("[server] Server closed.");
232
+ process.exit(0);
233
+ });
234
+ // Force exit after 5 seconds if connections don't close
235
+ setTimeout(() => process.exit(0), 5000);
236
+ };
237
+ process.on("SIGINT", shutdown);
238
+ process.on("SIGTERM", shutdown);
239
+ return server;
240
+ }
241
+ // ── Convenience starter ────────────────────────────────────────────────
242
+ /**
243
+ * Create and start listening. Returns the running server.
244
+ */
245
+ export async function startServer(options) {
246
+ const server = await createServer(options);
247
+ const port = resolvePort(options);
248
+ const host = options?.host ?? "0.0.0.0";
249
+ return new Promise((resolve) => {
250
+ server.listen(port, host, () => {
251
+ console.log(`[zoe] Server listening on ${host}:${port}`);
252
+ resolve(server);
253
+ });
254
+ });
255
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Zoe Server — Gateway REST Route Handlers
3
+ *
4
+ * Handles all /v1/gateway/* REST endpoints for target management,
5
+ * audit logs, usage summaries, credentials, routes, and OpenAPI imports.
6
+ */
7
+ import type { IncomingMessage, ServerResponse } from "http";
8
+ import type { MCPGateway } from "../../gateway/gateway.js";
9
+ import type { GatewaySettingsAdapter } from "../../gateway/settings-adapter.js";
10
+ export declare function createGatewayRestHandler(ctx: {
11
+ gateway: MCPGateway;
12
+ settingsAdapter: GatewaySettingsAdapter;
13
+ }): (req: IncomingMessage, res: ServerResponse, path: string, method: string) => Promise<void>;
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Zoe Server — Gateway REST Route Handlers
3
+ *
4
+ * Handles all /v1/gateway/* REST endpoints for target management,
5
+ * audit logs, usage summaries, credentials, routes, and OpenAPI imports.
6
+ */
7
+ import { authMiddleware, hasScope } from "./auth.js";
8
+ // ── Helpers ────────────────────────────────────────────────────────────
9
+ function sendJSON(res, statusCode, data) {
10
+ const body = JSON.stringify(data);
11
+ res.writeHead(statusCode, {
12
+ "Content-Type": "application/json",
13
+ "Content-Length": Buffer.byteLength(body),
14
+ });
15
+ res.end(body);
16
+ }
17
+ function sendError(res, statusCode, code, message) {
18
+ sendJSON(res, statusCode, { error: { code, message } });
19
+ }
20
+ function parseBody(req) {
21
+ return new Promise((resolve, reject) => {
22
+ const chunks = [];
23
+ req.on("data", (chunk) => chunks.push(chunk));
24
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
25
+ req.on("error", reject);
26
+ });
27
+ }
28
+ async function parseJsonBody(req, res) {
29
+ let body;
30
+ try {
31
+ body = JSON.parse(await parseBody(req));
32
+ }
33
+ catch {
34
+ sendError(res, 400, "BAD_REQUEST", "Invalid JSON in request body");
35
+ return null;
36
+ }
37
+ return body;
38
+ }
39
+ function requireAuth(req, res, scope) {
40
+ const key = authMiddleware(req);
41
+ if (!key) {
42
+ sendError(res, 401, "UNAUTHORIZED", "Missing or invalid API key");
43
+ return false;
44
+ }
45
+ if (!hasScope(key, scope)) {
46
+ sendError(res, 403, "FORBIDDEN", `API key lacks '${scope}' scope`);
47
+ return false;
48
+ }
49
+ return true;
50
+ }
51
+ function matchGatewayRoute(path, method) {
52
+ if (method === "GET" && path === "/v1/gateway/targets") {
53
+ return { handler: "list_targets" };
54
+ }
55
+ if (method === "GET" && path === "/v1/gateway/audit") {
56
+ return { handler: "audit" };
57
+ }
58
+ if (method === "GET" && path === "/v1/gateway/usage") {
59
+ return { handler: "usage" };
60
+ }
61
+ if (method === "POST" && path === "/v1/gateway/targets") {
62
+ return { handler: "register_target" };
63
+ }
64
+ const toggleMatch = path.match(/^\/v1\/gateway\/targets\/([^/]+)\/toggle$/);
65
+ if (method === "PATCH" && toggleMatch) {
66
+ return { handler: "toggle_target", name: decodeURIComponent(toggleMatch[1]) };
67
+ }
68
+ const targetDeleteMatch = path.match(/^\/v1\/gateway\/targets\/([^/]+)$/);
69
+ if (method === "DELETE" && targetDeleteMatch) {
70
+ return { handler: "unregister_target", name: decodeURIComponent(targetDeleteMatch[1]) };
71
+ }
72
+ if (method === "GET" && path === "/v1/gateway/credentials") {
73
+ return { handler: "get_credentials" };
74
+ }
75
+ const credMatch = path.match(/^\/v1\/gateway\/credentials\/(.+)$/);
76
+ if (method === "PUT" && credMatch) {
77
+ return { handler: "put_credential", key: decodeURIComponent(credMatch[1]) };
78
+ }
79
+ if (method === "POST" && path === "/v1/gateway/routes") {
80
+ return { handler: "add_route" };
81
+ }
82
+ if (method === "POST" && path === "/v1/gateway/import-openapi") {
83
+ return { handler: "import_openapi" };
84
+ }
85
+ return null;
86
+ }
87
+ // ── Main handler factory ───────────────────────────────────────────────
88
+ export function createGatewayRestHandler(ctx) {
89
+ const { gateway, settingsAdapter } = ctx;
90
+ return async function handleGatewayRoute(req, res, urlPath, method) {
91
+ const route = matchGatewayRoute(urlPath, method);
92
+ if (!route) {
93
+ sendError(res, 404, "NOT_FOUND", `No gateway route for ${method} ${urlPath}`);
94
+ return;
95
+ }
96
+ try {
97
+ switch (route.handler) {
98
+ case "list_targets": {
99
+ if (!requireAuth(req, res, "agent:read"))
100
+ return;
101
+ sendJSON(res, 200, { targets: gateway.getTargets() });
102
+ break;
103
+ }
104
+ case "audit": {
105
+ if (!requireAuth(req, res, "agent:read"))
106
+ return;
107
+ sendJSON(res, 200, { logs: gateway.getAuditLogs() });
108
+ break;
109
+ }
110
+ case "usage": {
111
+ if (!requireAuth(req, res, "agent:read"))
112
+ return;
113
+ sendJSON(res, 200, { usage: gateway.getUsageSummary() });
114
+ break;
115
+ }
116
+ case "register_target": {
117
+ if (!requireAuth(req, res, "admin"))
118
+ return;
119
+ const body = await parseJsonBody(req, res);
120
+ if (!body)
121
+ return;
122
+ if (!body.name || !body.target) {
123
+ sendError(res, 400, "BAD_REQUEST", "Fields 'name' and 'target' are required");
124
+ return;
125
+ }
126
+ await gateway.registerTarget(body.name, body.target, true);
127
+ sendJSON(res, 201, { ok: true });
128
+ break;
129
+ }
130
+ case "toggle_target": {
131
+ if (!requireAuth(req, res, "admin"))
132
+ return;
133
+ const body = await parseJsonBody(req, res);
134
+ if (!body)
135
+ return;
136
+ if (typeof body.enabled !== "boolean") {
137
+ sendError(res, 400, "BAD_REQUEST", "Field 'enabled' must be a boolean");
138
+ return;
139
+ }
140
+ const ok = await gateway.toggleTarget(route.name, body.enabled);
141
+ if (!ok) {
142
+ sendError(res, 404, "NOT_FOUND", `Target '${route.name}' not found`);
143
+ return;
144
+ }
145
+ sendJSON(res, 200, { ok: true });
146
+ break;
147
+ }
148
+ case "unregister_target": {
149
+ if (!requireAuth(req, res, "admin"))
150
+ return;
151
+ const deleted = await gateway.unregisterTarget(route.name);
152
+ if (!deleted) {
153
+ sendError(res, 404, "NOT_FOUND", `Target '${route.name}' not found`);
154
+ return;
155
+ }
156
+ sendJSON(res, 200, { ok: true });
157
+ break;
158
+ }
159
+ case "get_credentials": {
160
+ if (!requireAuth(req, res, "admin"))
161
+ return;
162
+ sendJSON(res, 200, { keys: settingsAdapter.listCredentialKeys() });
163
+ break;
164
+ }
165
+ case "put_credential": {
166
+ if (!requireAuth(req, res, "admin"))
167
+ return;
168
+ const body = await parseJsonBody(req, res);
169
+ if (!body)
170
+ return;
171
+ if (!body.value) {
172
+ sendError(res, 400, "BAD_REQUEST", "Field 'value' is required");
173
+ return;
174
+ }
175
+ await settingsAdapter.setCredential(route.key, body.value);
176
+ sendJSON(res, 200, { ok: true });
177
+ break;
178
+ }
179
+ case "add_route": {
180
+ if (!requireAuth(req, res, "admin"))
181
+ return;
182
+ const body = await parseJsonBody(req, res);
183
+ if (!body)
184
+ return;
185
+ if (!body.pattern || !body.target) {
186
+ sendError(res, 400, "BAD_REQUEST", "Fields 'pattern' and 'target' are required");
187
+ return;
188
+ }
189
+ await gateway.addRoute(body.pattern, body.target, body.priority ?? 0);
190
+ sendJSON(res, 201, { ok: true });
191
+ break;
192
+ }
193
+ case "import_openapi": {
194
+ if (!requireAuth(req, res, "admin"))
195
+ return;
196
+ const body = await parseJsonBody(req, res);
197
+ if (!body)
198
+ return;
199
+ if (!body.name || !body.specUrl) {
200
+ sendError(res, 400, "BAD_REQUEST", "Fields 'name' and 'specUrl' are required");
201
+ return;
202
+ }
203
+ const { importOpenApiSpec } = await import("../../gateway/openapi-importer.js");
204
+ const result = await importOpenApiSpec(gateway, body.name, body.specUrl, { baseUrl: body.baseUrl, isAdmin: true });
205
+ sendJSON(res, 201, result);
206
+ break;
207
+ }
208
+ default:
209
+ sendError(res, 404, "NOT_FOUND", "Unknown gateway endpoint");
210
+ }
211
+ }
212
+ catch (err) {
213
+ const message = err instanceof Error ? err.message : "Gateway request failed";
214
+ console.error("[rest-gateway] Error:", message);
215
+ sendError(res, 500, "INTERNAL_ERROR", message);
216
+ }
217
+ };
218
+ }
@@ -0,0 +1,37 @@
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 type { IncomingMessage, ServerResponse } from "http";
8
+ import type { ProviderType, SkillMetadata, GenerateTextResult } from "../../core/types.js";
9
+ import { ServerSessionManager } from "./session-store.js";
10
+ import { type SettingsHandlerContext } from "./settings-handlers.js";
11
+ export interface RestHandlerContext {
12
+ version: string;
13
+ startTime: number;
14
+ sessionManager: ServerSessionManager;
15
+ /** Generate text using the SDK */
16
+ generateText: (options: {
17
+ message: string;
18
+ model?: string;
19
+ provider?: ProviderType;
20
+ tools?: string[];
21
+ maxSteps?: number;
22
+ skills?: string[];
23
+ }) => Promise<GenerateTextResult>;
24
+ /** List available models grouped by provider */
25
+ listModels: () => Record<ProviderType, string[]>;
26
+ /** List available skill metadata */
27
+ listSkills: () => SkillMetadata[];
28
+ /** Settings handler context — required for settings/provider routes */
29
+ settingsHandlerContext?: SettingsHandlerContext;
30
+ /** Gateway REST handler — delegated for all /v1/gateway/* routes */
31
+ gatewayHandler?: (req: IncomingMessage, res: ServerResponse, path: string, method: string) => Promise<void>;
32
+ }
33
+ /**
34
+ * Creates the main REST request handler.
35
+ * Returns a function compatible with http.createServer().
36
+ */
37
+ export declare function createRestHandler(ctx: RestHandlerContext): (req: IncomingMessage, res: ServerResponse) => Promise<void>;