@nodetool-ai/websocket 0.7.0-rc.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent-runtime.d.ts +47 -0
- package/dist/agent/agent-runtime.d.ts.map +1 -0
- package/dist/agent/agent-runtime.js +577 -0
- package/dist/agent/agent-runtime.js.map +1 -0
- package/dist/agent/codex-agent.d.ts +41 -0
- package/dist/agent/codex-agent.d.ts.map +1 -0
- package/dist/agent/codex-agent.js +334 -0
- package/dist/agent/codex-agent.js.map +1 -0
- package/dist/agent/index.d.ts +6 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +4 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/llm-agent.d.ts +51 -0
- package/dist/agent/llm-agent.d.ts.map +1 -0
- package/dist/agent/llm-agent.js +526 -0
- package/dist/agent/llm-agent.js.map +1 -0
- package/dist/agent/mcp-tool-server.d.ts +33 -0
- package/dist/agent/mcp-tool-server.d.ts.map +1 -0
- package/dist/agent/mcp-tool-server.js +292 -0
- package/dist/agent/mcp-tool-server.js.map +1 -0
- package/dist/agent/opencode-agent.d.ts +32 -0
- package/dist/agent/opencode-agent.d.ts.map +1 -0
- package/dist/agent/opencode-agent.js +332 -0
- package/dist/agent/opencode-agent.js.map +1 -0
- package/dist/agent/pi-agent.d.ts +42 -0
- package/dist/agent/pi-agent.d.ts.map +1 -0
- package/dist/agent/pi-agent.js +429 -0
- package/dist/agent/pi-agent.js.map +1 -0
- package/dist/agent/sdk-provider.d.ts +46 -0
- package/dist/agent/sdk-provider.d.ts.map +1 -0
- package/dist/agent/sdk-provider.js +50 -0
- package/dist/agent/sdk-provider.js.map +1 -0
- package/dist/agent/socket-route.d.ts +11 -0
- package/dist/agent/socket-route.d.ts.map +1 -0
- package/dist/agent/socket-route.js +262 -0
- package/dist/agent/socket-route.js.map +1 -0
- package/dist/agent/transport.d.ts +39 -0
- package/dist/agent/transport.d.ts.map +1 -0
- package/dist/agent/transport.js +17 -0
- package/dist/agent/transport.js.map +1 -0
- package/dist/agent/types.d.ts +11 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +10 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/collection-api.d.ts +14 -0
- package/dist/collection-api.d.ts.map +1 -0
- package/dist/collection-api.js +75 -0
- package/dist/collection-api.js.map +1 -0
- package/dist/error-codes.d.ts +3 -0
- package/dist/error-codes.d.ts.map +1 -0
- package/dist/error-codes.js +2 -0
- package/dist/error-codes.js.map +1 -0
- package/dist/file-api.d.ts +12 -0
- package/dist/file-api.d.ts.map +1 -0
- package/dist/file-api.js +76 -0
- package/dist/file-api.js.map +1 -0
- package/dist/http-api.d.ts +94 -0
- package/dist/http-api.d.ts.map +1 -0
- package/dist/http-api.js +1472 -0
- package/dist/http-api.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/asset-paths.d.ts +10 -0
- package/dist/lib/asset-paths.d.ts.map +1 -0
- package/dist/lib/asset-paths.js +45 -0
- package/dist/lib/asset-paths.js.map +1 -0
- package/dist/lib/bridge.d.ts +8 -0
- package/dist/lib/bridge.d.ts.map +1 -0
- package/dist/lib/bridge.js +70 -0
- package/dist/lib/bridge.js.map +1 -0
- package/dist/mcp-server.d.ts +32 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +842 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/models-api.d.ts +12 -0
- package/dist/models-api.d.ts.map +1 -0
- package/dist/models-api.js +730 -0
- package/dist/models-api.js.map +1 -0
- package/dist/oauth-api.d.ts +28 -0
- package/dist/oauth-api.d.ts.map +1 -0
- package/dist/oauth-api.js +681 -0
- package/dist/oauth-api.js.map +1 -0
- package/dist/openai-api.d.ts +63 -0
- package/dist/openai-api.d.ts.map +1 -0
- package/dist/openai-api.js +359 -0
- package/dist/openai-api.js.map +1 -0
- package/dist/plugins/websocket.d.ts +14 -0
- package/dist/plugins/websocket.d.ts.map +1 -0
- package/dist/plugins/websocket.js +431 -0
- package/dist/plugins/websocket.js.map +1 -0
- package/dist/resolve-media-urls.d.ts +19 -0
- package/dist/resolve-media-urls.d.ts.map +1 -0
- package/dist/resolve-media-urls.js +109 -0
- package/dist/resolve-media-urls.js.map +1 -0
- package/dist/routes/assets.d.ts +22 -0
- package/dist/routes/assets.d.ts.map +1 -0
- package/dist/routes/assets.js +168 -0
- package/dist/routes/assets.js.map +1 -0
- package/dist/routes/collections.d.ts +16 -0
- package/dist/routes/collections.d.ts.map +1 -0
- package/dist/routes/collections.js +32 -0
- package/dist/routes/collections.js.map +1 -0
- package/dist/routes/files.d.ts +12 -0
- package/dist/routes/files.d.ts.map +1 -0
- package/dist/routes/files.js +11 -0
- package/dist/routes/files.js.map +1 -0
- package/dist/routes/health.d.ts +4 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +41 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/jobs.d.ts +16 -0
- package/dist/routes/jobs.d.ts.map +1 -0
- package/dist/routes/jobs.js +25 -0
- package/dist/routes/jobs.js.map +1 -0
- package/dist/routes/nodes.d.ts +8 -0
- package/dist/routes/nodes.d.ts.map +1 -0
- package/dist/routes/nodes.js +124 -0
- package/dist/routes/nodes.js.map +1 -0
- package/dist/routes/oauth.d.ts +8 -0
- package/dist/routes/oauth.d.ts.map +1 -0
- package/dist/routes/oauth.js +19 -0
- package/dist/routes/oauth.js.map +1 -0
- package/dist/routes/openai.d.ts +8 -0
- package/dist/routes/openai.d.ts.map +1 -0
- package/dist/routes/openai.js +20 -0
- package/dist/routes/openai.js.map +1 -0
- package/dist/routes/storage.d.ts +12 -0
- package/dist/routes/storage.d.ts.map +1 -0
- package/dist/routes/storage.js +13 -0
- package/dist/routes/storage.js.map +1 -0
- package/dist/routes/workflows.d.ts +18 -0
- package/dist/routes/workflows.d.ts.map +1 -0
- package/dist/routes/workflows.js +25 -0
- package/dist/routes/workflows.js.map +1 -0
- package/dist/routes/workspace.d.ts +15 -0
- package/dist/routes/workspace.d.ts.map +1 -0
- package/dist/routes/workspace.js +24 -0
- package/dist/routes/workspace.js.map +1 -0
- package/dist/screenshot-server.d.ts +21 -0
- package/dist/screenshot-server.d.ts.map +1 -0
- package/dist/screenshot-server.js +222 -0
- package/dist/screenshot-server.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +783 -0
- package/dist/server.js.map +1 -0
- package/dist/settings-registry.d.ts +29 -0
- package/dist/settings-registry.d.ts.map +1 -0
- package/dist/settings-registry.js +117 -0
- package/dist/settings-registry.js.map +1 -0
- package/dist/storage-api.d.ts +5 -0
- package/dist/storage-api.d.ts.map +1 -0
- package/dist/storage-api.js +221 -0
- package/dist/storage-api.js.map +1 -0
- package/dist/test-ui-server.d.ts +19 -0
- package/dist/test-ui-server.d.ts.map +1 -0
- package/dist/test-ui-server.js +1256 -0
- package/dist/test-ui-server.js.map +1 -0
- package/dist/trpc/context.d.ts +21 -0
- package/dist/trpc/context.d.ts.map +1 -0
- package/dist/trpc/context.js +10 -0
- package/dist/trpc/context.js.map +1 -0
- package/dist/trpc/error-formatter.d.ts +18 -0
- package/dist/trpc/error-formatter.d.ts.map +1 -0
- package/dist/trpc/error-formatter.js +41 -0
- package/dist/trpc/error-formatter.js.map +1 -0
- package/dist/trpc/index.d.ts +18 -0
- package/dist/trpc/index.d.ts.map +1 -0
- package/dist/trpc/index.js +13 -0
- package/dist/trpc/index.js.map +1 -0
- package/dist/trpc/middleware.d.ts +8 -0
- package/dist/trpc/middleware.d.ts.map +1 -0
- package/dist/trpc/middleware.js +12 -0
- package/dist/trpc/middleware.js.map +1 -0
- package/dist/trpc/router.d.ts +2543 -0
- package/dist/trpc/router.d.ts.map +1 -0
- package/dist/trpc/router.js +43 -0
- package/dist/trpc/router.js.map +1 -0
- package/dist/trpc/routers/assets.d.ts +229 -0
- package/dist/trpc/routers/assets.d.ts.map +1 -0
- package/dist/trpc/routers/assets.js +263 -0
- package/dist/trpc/routers/assets.js.map +1 -0
- package/dist/trpc/routers/collections.d.ts +88 -0
- package/dist/trpc/routers/collections.d.ts.map +1 -0
- package/dist/trpc/routers/collections.js +166 -0
- package/dist/trpc/routers/collections.js.map +1 -0
- package/dist/trpc/routers/costs.d.ts +129 -0
- package/dist/trpc/routers/costs.d.ts.map +1 -0
- package/dist/trpc/routers/costs.js +95 -0
- package/dist/trpc/routers/costs.js.map +1 -0
- package/dist/trpc/routers/files.d.ts +34 -0
- package/dist/trpc/routers/files.d.ts.map +1 -0
- package/dist/trpc/routers/files.js +92 -0
- package/dist/trpc/routers/files.js.map +1 -0
- package/dist/trpc/routers/jobs.d.ts +92 -0
- package/dist/trpc/routers/jobs.d.ts.map +1 -0
- package/dist/trpc/routers/jobs.js +100 -0
- package/dist/trpc/routers/jobs.js.map +1 -0
- package/dist/trpc/routers/mcp-config.d.ts +69 -0
- package/dist/trpc/routers/mcp-config.d.ts.map +1 -0
- package/dist/trpc/routers/mcp-config.js +290 -0
- package/dist/trpc/routers/mcp-config.js.map +1 -0
- package/dist/trpc/routers/messages.d.ts +121 -0
- package/dist/trpc/routers/messages.d.ts.map +1 -0
- package/dist/trpc/routers/messages.js +113 -0
- package/dist/trpc/routers/messages.js.map +1 -0
- package/dist/trpc/routers/models.d.ts +748 -0
- package/dist/trpc/routers/models.d.ts.map +1 -0
- package/dist/trpc/routers/models.js +1107 -0
- package/dist/trpc/routers/models.js.map +1 -0
- package/dist/trpc/routers/nodes.d.ts +48 -0
- package/dist/trpc/routers/nodes.d.ts.map +1 -0
- package/dist/trpc/routers/nodes.js +57 -0
- package/dist/trpc/routers/nodes.js.map +1 -0
- package/dist/trpc/routers/sandboxes.d.ts +64 -0
- package/dist/trpc/routers/sandboxes.d.ts.map +1 -0
- package/dist/trpc/routers/sandboxes.js +327 -0
- package/dist/trpc/routers/sandboxes.js.map +1 -0
- package/dist/trpc/routers/settings.d.ts +113 -0
- package/dist/trpc/routers/settings.d.ts.map +1 -0
- package/dist/trpc/routers/settings.js +193 -0
- package/dist/trpc/routers/settings.js.map +1 -0
- package/dist/trpc/routers/skills.d.ts +41 -0
- package/dist/trpc/routers/skills.d.ts.map +1 -0
- package/dist/trpc/routers/skills.js +194 -0
- package/dist/trpc/routers/skills.js.map +1 -0
- package/dist/trpc/routers/storage.d.ts +44 -0
- package/dist/trpc/routers/storage.d.ts.map +1 -0
- package/dist/trpc/routers/storage.js +153 -0
- package/dist/trpc/routers/storage.js.map +1 -0
- package/dist/trpc/routers/threads.d.ts +95 -0
- package/dist/trpc/routers/threads.d.ts.map +1 -0
- package/dist/trpc/routers/threads.js +150 -0
- package/dist/trpc/routers/threads.js.map +1 -0
- package/dist/trpc/routers/users.d.ts +79 -0
- package/dist/trpc/routers/users.d.ts.map +1 -0
- package/dist/trpc/routers/users.js +125 -0
- package/dist/trpc/routers/users.js.map +1 -0
- package/dist/trpc/routers/workflows.d.ts +681 -0
- package/dist/trpc/routers/workflows.d.ts.map +1 -0
- package/dist/trpc/routers/workflows.js +725 -0
- package/dist/trpc/routers/workflows.js.map +1 -0
- package/dist/trpc/routers/workspace.d.ts +127 -0
- package/dist/trpc/routers/workspace.d.ts.map +1 -0
- package/dist/trpc/routers/workspace.js +203 -0
- package/dist/trpc/routers/workspace.js.map +1 -0
- package/dist/unified-websocket-runner.d.ts +280 -0
- package/dist/unified-websocket-runner.d.ts.map +1 -0
- package/dist/unified-websocket-runner.js +3840 -0
- package/dist/unified-websocket-runner.js.map +1 -0
- package/dist/workspace-api.d.ts +14 -0
- package/dist/workspace-api.d.ts.map +1 -0
- package/dist/workspace-api.js +104 -0
- package/dist/workspace-api.js.map +1 -0
- package/dist/ws-adapter.d.ts +25 -0
- package/dist/ws-adapter.d.ts.map +1 -0
- package/dist/ws-adapter.js +63 -0
- package/dist/ws-adapter.js.map +1 -0
- package/package.json +92 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP MCP server that exposes NodeTool UI tools.
|
|
3
|
+
*
|
|
4
|
+
* Implements the MCP Streamable HTTP transport so any agent SDK
|
|
5
|
+
* (Claude, Codex, OpenCode) can connect to it as an MCP server.
|
|
6
|
+
*
|
|
7
|
+
* Tool calls are forwarded to the renderer whose session originated the
|
|
8
|
+
* connection via the `AgentTransport` for execution against the live
|
|
9
|
+
* workflow graph. The session identity is encoded in the URL path as
|
|
10
|
+
* `/mcp/<sessionId>` so simultaneous sessions from different renderers
|
|
11
|
+
* can't clobber each other's tool routing.
|
|
12
|
+
*/
|
|
13
|
+
import { createServer, } from "node:http";
|
|
14
|
+
import { createLogger } from "@nodetool-ai/config";
|
|
15
|
+
import { uiToolSchemas } from "@nodetool-ai/protocol";
|
|
16
|
+
import { z, toJSONSchema } from "zod";
|
|
17
|
+
const log = createLogger("nodetool.websocket.agent.mcp");
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Tool schema conversion
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
function zodShapeToJsonSchema(zodShape) {
|
|
22
|
+
const schema = z.object(zodShape);
|
|
23
|
+
const jsonSchema = toJSONSchema(schema);
|
|
24
|
+
// Remove $schema key — MCP inputSchema doesn't need it
|
|
25
|
+
delete jsonSchema.$schema;
|
|
26
|
+
return {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: (jsonSchema.properties ?? {}),
|
|
29
|
+
required: jsonSchema.required,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function buildToolDefinitions() {
|
|
33
|
+
return Object.entries(uiToolSchemas).map(([name, schema]) => ({
|
|
34
|
+
name,
|
|
35
|
+
description: schema.description,
|
|
36
|
+
inputSchema: zodShapeToJsonSchema(schema.parameters),
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// MCP HTTP Server (singleton)
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
let serverInstance = null;
|
|
43
|
+
let serverPort = null;
|
|
44
|
+
/**
|
|
45
|
+
* Session ID → transport mapping. Each agent session has its own entry so
|
|
46
|
+
* tool calls are routed to the renderer that originated that session. This
|
|
47
|
+
* replaces the earlier "active transport" global, which was unsafe when
|
|
48
|
+
* multiple renderers were connected simultaneously.
|
|
49
|
+
*/
|
|
50
|
+
const sessionTransports = new Map();
|
|
51
|
+
/** Default CORS origins allowed to hit the MCP HTTP server. */
|
|
52
|
+
const DEFAULT_ALLOWED_ORIGIN_PATTERNS = [
|
|
53
|
+
/^https?:\/\/localhost(?::\d+)?$/,
|
|
54
|
+
/^https?:\/\/127\.0\.0\.1(?::\d+)?$/,
|
|
55
|
+
// Electron renderer
|
|
56
|
+
/^file:\/\//,
|
|
57
|
+
];
|
|
58
|
+
function isOriginAllowed(origin) {
|
|
59
|
+
// MCP SDK clients (Claude/Codex) typically don't send an Origin header.
|
|
60
|
+
// In that case there's no browser enforcing CORS, so allow the request.
|
|
61
|
+
if (!origin)
|
|
62
|
+
return true;
|
|
63
|
+
return DEFAULT_ALLOWED_ORIGIN_PATTERNS.some((re) => re.test(origin));
|
|
64
|
+
}
|
|
65
|
+
function readBody(req) {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const chunks = [];
|
|
68
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
69
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
70
|
+
req.on("error", reject);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function sendJson(res, status, body) {
|
|
74
|
+
const payload = JSON.stringify(body);
|
|
75
|
+
res.writeHead(status, {
|
|
76
|
+
"Content-Type": "application/json",
|
|
77
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
78
|
+
});
|
|
79
|
+
res.end(payload);
|
|
80
|
+
}
|
|
81
|
+
function extractSessionIdFromPath(path) {
|
|
82
|
+
if (!path)
|
|
83
|
+
return null;
|
|
84
|
+
// URL is `/mcp/<sessionId>` (optionally with a trailing slash or query).
|
|
85
|
+
const match = /^\/mcp\/([^/?#]+)\/?/.exec(path);
|
|
86
|
+
return match ? decodeURIComponent(match[1]) : null;
|
|
87
|
+
}
|
|
88
|
+
async function handleMcpRequest(req, res) {
|
|
89
|
+
const origin = req.headers.origin;
|
|
90
|
+
if (isOriginAllowed(origin)) {
|
|
91
|
+
res.setHeader("Access-Control-Allow-Origin", origin ?? "*");
|
|
92
|
+
}
|
|
93
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
94
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
95
|
+
res.setHeader("Vary", "Origin");
|
|
96
|
+
if (req.method === "OPTIONS") {
|
|
97
|
+
res.writeHead(204);
|
|
98
|
+
res.end();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (req.method !== "POST") {
|
|
102
|
+
sendJson(res, 405, { error: "Method not allowed" });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const sessionId = extractSessionIdFromPath(req.url);
|
|
106
|
+
if (!sessionId) {
|
|
107
|
+
sendJson(res, 404, {
|
|
108
|
+
jsonrpc: "2.0",
|
|
109
|
+
id: null,
|
|
110
|
+
error: { code: -32601, message: "Session ID missing from MCP URL path" },
|
|
111
|
+
});
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
let rpcRequest;
|
|
115
|
+
try {
|
|
116
|
+
const body = await readBody(req);
|
|
117
|
+
rpcRequest = JSON.parse(body);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
sendJson(res, 400, {
|
|
121
|
+
jsonrpc: "2.0",
|
|
122
|
+
id: null,
|
|
123
|
+
error: { code: -32700, message: "Parse error" },
|
|
124
|
+
});
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const response = await handleRpcMethod(rpcRequest, sessionId);
|
|
128
|
+
sendJson(res, 200, response);
|
|
129
|
+
}
|
|
130
|
+
async function handleRpcMethod(request, sessionId) {
|
|
131
|
+
const { id, method, params } = request;
|
|
132
|
+
switch (method) {
|
|
133
|
+
case "initialize":
|
|
134
|
+
return {
|
|
135
|
+
jsonrpc: "2.0",
|
|
136
|
+
id,
|
|
137
|
+
result: {
|
|
138
|
+
protocolVersion: "2024-11-05",
|
|
139
|
+
capabilities: { tools: {} },
|
|
140
|
+
serverInfo: { name: "nodetool-ui", version: "1.0.0" },
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
case "tools/list":
|
|
144
|
+
return {
|
|
145
|
+
jsonrpc: "2.0",
|
|
146
|
+
id,
|
|
147
|
+
result: { tools: buildToolDefinitions() },
|
|
148
|
+
};
|
|
149
|
+
case "tools/call": {
|
|
150
|
+
const toolName = params?.name;
|
|
151
|
+
const toolArgs = params?.arguments;
|
|
152
|
+
if (!toolName || !uiToolSchemas[toolName]) {
|
|
153
|
+
return {
|
|
154
|
+
jsonrpc: "2.0",
|
|
155
|
+
id,
|
|
156
|
+
error: { code: -32602, message: `Unknown tool: ${toolName}` },
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const transport = sessionTransports.get(sessionId);
|
|
160
|
+
if (!transport || !transport.isAlive) {
|
|
161
|
+
return {
|
|
162
|
+
jsonrpc: "2.0",
|
|
163
|
+
id,
|
|
164
|
+
result: {
|
|
165
|
+
content: [
|
|
166
|
+
{
|
|
167
|
+
type: "text",
|
|
168
|
+
text: "Error: No active renderer for this session — open a workflow in NodeTool first.",
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
isError: true,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const result = await transport.executeTool(sessionId, `mcp-${id}`, toolName, toolArgs ?? {});
|
|
177
|
+
const text = typeof result === "string"
|
|
178
|
+
? result
|
|
179
|
+
: JSON.stringify(result ?? null);
|
|
180
|
+
return {
|
|
181
|
+
jsonrpc: "2.0",
|
|
182
|
+
id,
|
|
183
|
+
result: {
|
|
184
|
+
content: [{ type: "text", text }],
|
|
185
|
+
isError: false,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
191
|
+
return {
|
|
192
|
+
jsonrpc: "2.0",
|
|
193
|
+
id,
|
|
194
|
+
result: {
|
|
195
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
196
|
+
isError: true,
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
case "notifications/initialized":
|
|
202
|
+
case "ping":
|
|
203
|
+
return { jsonrpc: "2.0", id, result: {} };
|
|
204
|
+
default:
|
|
205
|
+
return {
|
|
206
|
+
jsonrpc: "2.0",
|
|
207
|
+
id,
|
|
208
|
+
error: { code: -32601, message: `Method not found: ${method}` },
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Public API
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
async function ensureServerStarted() {
|
|
216
|
+
if (serverInstance && serverPort)
|
|
217
|
+
return;
|
|
218
|
+
await new Promise((resolve, reject) => {
|
|
219
|
+
const server = createServer((req, res) => {
|
|
220
|
+
handleMcpRequest(req, res).catch((err) => {
|
|
221
|
+
log.error("MCP server error", err instanceof Error ? err : new Error(String(err)));
|
|
222
|
+
sendJson(res, 500, {
|
|
223
|
+
jsonrpc: "2.0",
|
|
224
|
+
id: null,
|
|
225
|
+
error: { code: -32603, message: "Internal error" },
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
server.listen(0, "127.0.0.1", () => {
|
|
230
|
+
const addr = server.address();
|
|
231
|
+
if (!addr || typeof addr === "string") {
|
|
232
|
+
reject(new Error("Failed to get MCP server address"));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
serverPort = addr.port;
|
|
236
|
+
serverInstance = server;
|
|
237
|
+
log.info(`MCP tool server listening at http://127.0.0.1:${serverPort}`);
|
|
238
|
+
resolve();
|
|
239
|
+
});
|
|
240
|
+
server.on("error", (err) => {
|
|
241
|
+
log.error("MCP tool server error", err);
|
|
242
|
+
reject(err);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Start (if needed) the MCP HTTP server and register `transport` as the
|
|
248
|
+
* executor for `sessionId`. Returns the session-scoped MCP URL to hand to
|
|
249
|
+
* the agent SDK.
|
|
250
|
+
*/
|
|
251
|
+
export async function startMcpToolServer(transport, sessionId) {
|
|
252
|
+
await ensureServerStarted();
|
|
253
|
+
sessionTransports.set(sessionId, transport);
|
|
254
|
+
return `http://127.0.0.1:${serverPort}/mcp/${encodeURIComponent(sessionId)}`;
|
|
255
|
+
}
|
|
256
|
+
/** Update the transport associated with an existing session. */
|
|
257
|
+
export function setMcpToolServerTransport(transport, sessionId) {
|
|
258
|
+
sessionTransports.set(sessionId, transport);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Remove all session→transport mappings pointing at the given transport.
|
|
262
|
+
* Called when a renderer disconnects so we don't keep dead references.
|
|
263
|
+
*/
|
|
264
|
+
export function clearMcpToolServerTransport(transport) {
|
|
265
|
+
for (const [sessionId, t] of sessionTransports.entries()) {
|
|
266
|
+
if (t === transport) {
|
|
267
|
+
sessionTransports.delete(sessionId);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/** Drop a single session's transport mapping. */
|
|
272
|
+
export function clearMcpToolServerSession(sessionId) {
|
|
273
|
+
sessionTransports.delete(sessionId);
|
|
274
|
+
}
|
|
275
|
+
/** Get the MCP server URL for an already-registered session, or null. */
|
|
276
|
+
export function getMcpToolServerUrl(sessionId) {
|
|
277
|
+
if (serverInstance && serverPort && sessionTransports.has(sessionId)) {
|
|
278
|
+
return `http://127.0.0.1:${serverPort}/mcp/${encodeURIComponent(sessionId)}`;
|
|
279
|
+
}
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
/** Stop the MCP tool server. */
|
|
283
|
+
export function stopMcpToolServer() {
|
|
284
|
+
if (serverInstance) {
|
|
285
|
+
serverInstance.close();
|
|
286
|
+
serverInstance = null;
|
|
287
|
+
serverPort = null;
|
|
288
|
+
sessionTransports.clear();
|
|
289
|
+
log.info("MCP tool server stopped");
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=mcp-tool-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-tool-server.js","sourceRoot":"","sources":["../../src/agent/mcp-tool-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode SDK agent integration.
|
|
3
|
+
*
|
|
4
|
+
* Uses @opencode-ai/sdk to manage sessions and send prompts. OpenCode runs
|
|
5
|
+
* as a local server; we spawn it and connect via the SDK client.
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentTransport } from "./transport.js";
|
|
8
|
+
import type { AgentGetSessionMessagesRequest, AgentListSessionsRequest, AgentMessage, AgentModelDescriptor, AgentSessionInfoEntry, AgentTranscriptMessage, FrontendToolManifest } from "./types.js";
|
|
9
|
+
export declare function closeOpenCodeServer(): void;
|
|
10
|
+
export declare function listOpenCodeModels(workspacePath?: string): Promise<AgentModelDescriptor[]>;
|
|
11
|
+
export declare function listOpenCodeSessions(options: AgentListSessionsRequest): Promise<AgentSessionInfoEntry[]>;
|
|
12
|
+
export declare function getOpenCodeSessionMessages(options: AgentGetSessionMessagesRequest): Promise<AgentTranscriptMessage[]>;
|
|
13
|
+
export declare class OpenCodeQuerySession {
|
|
14
|
+
private closed;
|
|
15
|
+
private readonly providerID;
|
|
16
|
+
private readonly modelID;
|
|
17
|
+
private readonly workspacePath;
|
|
18
|
+
private readonly systemPrompt;
|
|
19
|
+
private sessionId;
|
|
20
|
+
private inFlight;
|
|
21
|
+
constructor(options: {
|
|
22
|
+
model: string;
|
|
23
|
+
workspacePath: string;
|
|
24
|
+
resumeSessionId?: string;
|
|
25
|
+
systemPrompt?: string;
|
|
26
|
+
});
|
|
27
|
+
private ensureSession;
|
|
28
|
+
send(message: string, _transport: AgentTransport | null, sessionId: string, _manifest: FrontendToolManifest[], onMessage?: (message: AgentMessage) => void, _mcpServerUrl?: string | null): Promise<AgentMessage[]>;
|
|
29
|
+
interrupt(): Promise<void>;
|
|
30
|
+
close(): void;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=opencode-agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode-agent.d.ts","sourceRoot":"","sources":["../../src/agent/opencode-agent.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EACV,8BAA8B,EAC9B,wBAAwB,EACxB,YAAY,EACZ,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAkHpB,wBAAgB,mBAAmB,IAAI,IAAI,CAM1C;AAED,wBAAsB,kBAAkB,CACtC,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,oBAAoB,EAAE,CAAC,CA6FjC;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAyBlC;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,8BAA8B,GACtC,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAyCnC;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,QAAQ,CAAS;gBAEb,OAAO,EAAE;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;QACtB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB;YAca,aAAa;IA8BrB,IAAI,CACR,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,cAAc,GAAG,IAAI,EACjC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,oBAAoB,EAAE,EACjC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,EAC3C,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,GAC5B,OAAO,CAAC,YAAY,EAAE,CAAC;IAiGpB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAehC,KAAK,IAAI,IAAI;CAId"}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode SDK agent integration.
|
|
3
|
+
*
|
|
4
|
+
* Uses @opencode-ai/sdk to manage sessions and send prompts. OpenCode runs
|
|
5
|
+
* as a local server; we spawn it and connect via the SDK client.
|
|
6
|
+
*/
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
import { createLogger } from "@nodetool-ai/config";
|
|
9
|
+
const log = createLogger("nodetool.websocket.agent.opencode");
|
|
10
|
+
let clientInstance = null;
|
|
11
|
+
let serverClose = null;
|
|
12
|
+
async function getClient() {
|
|
13
|
+
if (clientInstance) {
|
|
14
|
+
return clientInstance;
|
|
15
|
+
}
|
|
16
|
+
const sdk = (await import("@opencode-ai/sdk"));
|
|
17
|
+
const { client, server } = await sdk.createOpencode();
|
|
18
|
+
clientInstance = client;
|
|
19
|
+
serverClose = server.close;
|
|
20
|
+
log.info(`OpenCode server started at ${server.url}`);
|
|
21
|
+
return clientInstance;
|
|
22
|
+
}
|
|
23
|
+
function getClientIfRunning() {
|
|
24
|
+
return clientInstance;
|
|
25
|
+
}
|
|
26
|
+
export function closeOpenCodeServer() {
|
|
27
|
+
if (serverClose) {
|
|
28
|
+
serverClose();
|
|
29
|
+
serverClose = null;
|
|
30
|
+
clientInstance = null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export async function listOpenCodeModels(workspacePath) {
|
|
34
|
+
const models = [];
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
const query = workspacePath ? { query: { directory: workspacePath } } : {};
|
|
37
|
+
let client;
|
|
38
|
+
try {
|
|
39
|
+
client = await getClient();
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
log.error("Failed to start OpenCode server for model listing", error instanceof Error ? error : new Error(String(error)));
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
// Primary source: providers the user has actually configured (env vars,
|
|
46
|
+
// opencode.json, oauth). These are ready to use without further setup.
|
|
47
|
+
try {
|
|
48
|
+
const configured = await client.config.providers(query);
|
|
49
|
+
if (configured.data?.providers) {
|
|
50
|
+
const defaultModelIds = new Set(Object.values(configured.data.default ?? {}));
|
|
51
|
+
for (const provider of configured.data.providers) {
|
|
52
|
+
for (const [modelId, modelInfo] of Object.entries(provider.models)) {
|
|
53
|
+
if (!modelInfo.capabilities?.toolcall)
|
|
54
|
+
continue;
|
|
55
|
+
const compositeId = `${provider.id}/${modelId}`;
|
|
56
|
+
if (seen.has(compositeId))
|
|
57
|
+
continue;
|
|
58
|
+
seen.add(compositeId);
|
|
59
|
+
models.push({
|
|
60
|
+
id: compositeId,
|
|
61
|
+
label: `${modelInfo.name} (${provider.name})`,
|
|
62
|
+
provider: "opencode",
|
|
63
|
+
isDefault: defaultModelIds.has(modelId),
|
|
64
|
+
supportsMaxTurns: false,
|
|
65
|
+
supportsReasoningEffort: modelInfo.capabilities?.reasoning ?? false,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
log.warn(`config.providers failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
73
|
+
}
|
|
74
|
+
// Supplemental source: the full catalog from provider.list. Appends models
|
|
75
|
+
// that weren't in the configured set (labelled "not connected") so the
|
|
76
|
+
// user can discover and configure them. Also used as the sole source if
|
|
77
|
+
// config.providers returned nothing usable.
|
|
78
|
+
try {
|
|
79
|
+
const all = await client.provider.list(query);
|
|
80
|
+
if (all.data) {
|
|
81
|
+
const connectedSet = new Set(all.data.connected ?? []);
|
|
82
|
+
const defaultModelIds = new Set(Object.values(all.data.default ?? {}));
|
|
83
|
+
for (const provider of all.data.all ?? []) {
|
|
84
|
+
const isConnected = connectedSet.has(provider.id);
|
|
85
|
+
for (const [modelId, modelInfo] of Object.entries(provider.models)) {
|
|
86
|
+
if (modelInfo.status === "deprecated")
|
|
87
|
+
continue;
|
|
88
|
+
if (!modelInfo.tool_call)
|
|
89
|
+
continue;
|
|
90
|
+
const compositeId = `${provider.id}/${modelId}`;
|
|
91
|
+
if (seen.has(compositeId))
|
|
92
|
+
continue;
|
|
93
|
+
seen.add(compositeId);
|
|
94
|
+
const label = isConnected
|
|
95
|
+
? `${modelInfo.name} (${provider.name})`
|
|
96
|
+
: `${modelInfo.name} (${provider.name}, not connected)`;
|
|
97
|
+
models.push({
|
|
98
|
+
id: compositeId,
|
|
99
|
+
label,
|
|
100
|
+
provider: "opencode",
|
|
101
|
+
isDefault: isConnected && defaultModelIds.has(modelId),
|
|
102
|
+
supportsMaxTurns: false,
|
|
103
|
+
supportsReasoningEffort: modelInfo.reasoning,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
log.error("provider.list failed", error instanceof Error ? error : new Error(String(error)));
|
|
111
|
+
}
|
|
112
|
+
if (models.length > 0 && !models.some((m) => m.isDefault)) {
|
|
113
|
+
models[0].isDefault = true;
|
|
114
|
+
}
|
|
115
|
+
log.info(`Listed ${models.length} OpenCode model(s)`);
|
|
116
|
+
return models;
|
|
117
|
+
}
|
|
118
|
+
export async function listOpenCodeSessions(options) {
|
|
119
|
+
try {
|
|
120
|
+
const client = getClientIfRunning();
|
|
121
|
+
if (!client)
|
|
122
|
+
return [];
|
|
123
|
+
const result = await client.session.list({
|
|
124
|
+
query: { directory: options.dir },
|
|
125
|
+
});
|
|
126
|
+
if (!result.data)
|
|
127
|
+
return [];
|
|
128
|
+
return result.data.map((s) => ({
|
|
129
|
+
sessionId: s.id,
|
|
130
|
+
summary: s.title,
|
|
131
|
+
lastModified: s.time.updated,
|
|
132
|
+
cwd: s.directory,
|
|
133
|
+
createdAt: s.time.created,
|
|
134
|
+
provider: "opencode",
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
log.error("Failed to list OpenCode sessions", error instanceof Error ? error : new Error(String(error)));
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export async function getOpenCodeSessionMessages(options) {
|
|
143
|
+
try {
|
|
144
|
+
const client = getClientIfRunning();
|
|
145
|
+
if (!client)
|
|
146
|
+
return [];
|
|
147
|
+
const result = await client.session.messages({
|
|
148
|
+
path: { id: options.sessionId },
|
|
149
|
+
});
|
|
150
|
+
if (!result.data)
|
|
151
|
+
return [];
|
|
152
|
+
return result.data
|
|
153
|
+
.map((entry) => {
|
|
154
|
+
const role = entry.info.role === "user" ? "user" : "assistant";
|
|
155
|
+
const textParts = entry.parts
|
|
156
|
+
.filter((p) => p.type === "text" && "text" in p)
|
|
157
|
+
.map((p) => p.text);
|
|
158
|
+
return {
|
|
159
|
+
type: role,
|
|
160
|
+
uuid: entry.info.id,
|
|
161
|
+
session_id: entry.info.sessionID,
|
|
162
|
+
text: textParts.join("\n"),
|
|
163
|
+
};
|
|
164
|
+
})
|
|
165
|
+
.filter((m) => m.text.length > 0);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
log.error("Failed to get OpenCode session messages", error instanceof Error ? error : new Error(String(error)));
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
export class OpenCodeQuerySession {
|
|
173
|
+
closed = false;
|
|
174
|
+
providerID;
|
|
175
|
+
modelID;
|
|
176
|
+
workspacePath;
|
|
177
|
+
systemPrompt;
|
|
178
|
+
sessionId;
|
|
179
|
+
inFlight = false;
|
|
180
|
+
constructor(options) {
|
|
181
|
+
const parts = options.model.split("/");
|
|
182
|
+
if (parts.length >= 2) {
|
|
183
|
+
this.providerID = parts[0];
|
|
184
|
+
this.modelID = parts.slice(1).join("/");
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
this.providerID = "anthropic";
|
|
188
|
+
this.modelID = options.model;
|
|
189
|
+
}
|
|
190
|
+
this.workspacePath = options.workspacePath;
|
|
191
|
+
this.systemPrompt = options.systemPrompt ?? "";
|
|
192
|
+
this.sessionId = options.resumeSessionId ?? null;
|
|
193
|
+
}
|
|
194
|
+
async ensureSession() {
|
|
195
|
+
if (this.sessionId)
|
|
196
|
+
return this.sessionId;
|
|
197
|
+
const client = await getClient();
|
|
198
|
+
const result = await client.session.create({
|
|
199
|
+
body: { title: "NodeTool Agent Session" },
|
|
200
|
+
query: { directory: this.workspacePath },
|
|
201
|
+
});
|
|
202
|
+
if (!result.data) {
|
|
203
|
+
throw new Error("Failed to create OpenCode session");
|
|
204
|
+
}
|
|
205
|
+
this.sessionId = result.data.id;
|
|
206
|
+
log.info(`OpenCode session created: ${this.sessionId}`);
|
|
207
|
+
if (this.systemPrompt.trim().length > 0) {
|
|
208
|
+
await client.session.prompt({
|
|
209
|
+
path: { id: this.sessionId },
|
|
210
|
+
body: {
|
|
211
|
+
noReply: true,
|
|
212
|
+
parts: [{ type: "text", text: this.systemPrompt }],
|
|
213
|
+
model: { providerID: this.providerID, modelID: this.modelID },
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return this.sessionId;
|
|
218
|
+
}
|
|
219
|
+
async send(message, _transport, sessionId, _manifest, onMessage, _mcpServerUrl) {
|
|
220
|
+
if (this.closed) {
|
|
221
|
+
throw new Error("Cannot send to a closed session");
|
|
222
|
+
}
|
|
223
|
+
if (this.inFlight) {
|
|
224
|
+
throw new Error("A request is already in progress for this session");
|
|
225
|
+
}
|
|
226
|
+
this.inFlight = true;
|
|
227
|
+
try {
|
|
228
|
+
const ocSessionId = await this.ensureSession();
|
|
229
|
+
const client = await getClient();
|
|
230
|
+
const result = await client.session.prompt({
|
|
231
|
+
path: { id: ocSessionId },
|
|
232
|
+
body: {
|
|
233
|
+
parts: [{ type: "text", text: message }],
|
|
234
|
+
model: { providerID: this.providerID, modelID: this.modelID },
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
if (!result.data) {
|
|
238
|
+
throw new Error("OpenCode prompt returned no data");
|
|
239
|
+
}
|
|
240
|
+
const resolvedSessionId = this.sessionId ?? sessionId;
|
|
241
|
+
const outputMessages = [];
|
|
242
|
+
for (const part of result.data.parts) {
|
|
243
|
+
if (part.type === "text" && part.text && part.text.trim().length > 0) {
|
|
244
|
+
const msg = {
|
|
245
|
+
type: "assistant",
|
|
246
|
+
uuid: part.id ?? randomUUID(),
|
|
247
|
+
session_id: resolvedSessionId,
|
|
248
|
+
content: [{ type: "text", text: part.text }],
|
|
249
|
+
};
|
|
250
|
+
outputMessages.push(msg);
|
|
251
|
+
if (onMessage)
|
|
252
|
+
onMessage(msg);
|
|
253
|
+
}
|
|
254
|
+
else if (part.type === "tool" && "tool" in part) {
|
|
255
|
+
const toolInput = "state" in part &&
|
|
256
|
+
part.state &&
|
|
257
|
+
typeof part.state === "object" &&
|
|
258
|
+
"input" in part.state
|
|
259
|
+
? part.state.input
|
|
260
|
+
: {};
|
|
261
|
+
const msg = {
|
|
262
|
+
type: "assistant",
|
|
263
|
+
uuid: part.id ?? randomUUID(),
|
|
264
|
+
session_id: resolvedSessionId,
|
|
265
|
+
content: [],
|
|
266
|
+
tool_calls: [
|
|
267
|
+
{
|
|
268
|
+
id: part.id ?? randomUUID(),
|
|
269
|
+
type: "function",
|
|
270
|
+
function: {
|
|
271
|
+
name: part.tool ?? "unknown",
|
|
272
|
+
arguments: JSON.stringify(toolInput ?? {}),
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
};
|
|
277
|
+
outputMessages.push(msg);
|
|
278
|
+
if (onMessage)
|
|
279
|
+
onMessage(msg);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (outputMessages.length === 0) {
|
|
283
|
+
const msg = {
|
|
284
|
+
type: "result",
|
|
285
|
+
uuid: randomUUID(),
|
|
286
|
+
session_id: resolvedSessionId,
|
|
287
|
+
subtype: "success",
|
|
288
|
+
text: "Completed",
|
|
289
|
+
};
|
|
290
|
+
outputMessages.push(msg);
|
|
291
|
+
if (onMessage)
|
|
292
|
+
onMessage(msg);
|
|
293
|
+
}
|
|
294
|
+
return outputMessages;
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
const errorMsg = {
|
|
298
|
+
type: "result",
|
|
299
|
+
uuid: randomUUID(),
|
|
300
|
+
session_id: this.sessionId ?? sessionId,
|
|
301
|
+
subtype: "error",
|
|
302
|
+
is_error: true,
|
|
303
|
+
errors: [error instanceof Error ? error.message : String(error)],
|
|
304
|
+
};
|
|
305
|
+
if (onMessage)
|
|
306
|
+
onMessage(errorMsg);
|
|
307
|
+
return [errorMsg];
|
|
308
|
+
}
|
|
309
|
+
finally {
|
|
310
|
+
this.inFlight = false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
async interrupt() {
|
|
314
|
+
if (this.sessionId) {
|
|
315
|
+
try {
|
|
316
|
+
const client = await getClient();
|
|
317
|
+
await client.session.abort({
|
|
318
|
+
path: { id: this.sessionId },
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
log.warn(`Failed to abort OpenCode session: ${error instanceof Error ? error.message : String(error)}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
close() {
|
|
327
|
+
if (this.closed)
|
|
328
|
+
return;
|
|
329
|
+
this.closed = true;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
//# sourceMappingURL=opencode-agent.js.map
|