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,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>;
|