cloudflare-openhands-sdk 0.1.0

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.
@@ -0,0 +1,302 @@
1
+ // src/utils/agent-server.ts
2
+ var DEFAULT_AGENT_SERVER_PATH = "/container-server/software-agent-sdk/.venv/bin/agent-server";
3
+ var DEFAULT_AGENT_SERVER_DIR = "/container-server/software-agent-sdk";
4
+ function getDefaultAgentServerPath() {
5
+ return DEFAULT_AGENT_SERVER_PATH;
6
+ }
7
+ function getDefaultAgentServerDir() {
8
+ return DEFAULT_AGENT_SERVER_DIR;
9
+ }
10
+ async function checkAgentServerExists(sandbox, path = DEFAULT_AGENT_SERVER_PATH) {
11
+ try {
12
+ const result = await sandbox.exec(`test -f ${path} && echo "EXISTS" || echo "NOT_FOUND"`);
13
+ return result.stdout.trim() === "EXISTS";
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+ async function findAgentServerPath(sandbox) {
19
+ const locations = [
20
+ DEFAULT_AGENT_SERVER_PATH,
21
+ "/software-agent-sdk/.venv/bin/agent-server",
22
+ "/root/software-agent-sdk/.venv/bin/agent-server"
23
+ ];
24
+ for (const location of locations) {
25
+ if (await checkAgentServerExists(sandbox, location)) {
26
+ return location;
27
+ }
28
+ }
29
+ try {
30
+ const result = await sandbox.exec("which agent-server");
31
+ const path = result.stdout.trim();
32
+ if (path && !path.includes("not found")) {
33
+ return path;
34
+ }
35
+ } catch {}
36
+ return null;
37
+ }
38
+ function buildAgentServerCommand(port, directory) {
39
+ const command = `${DEFAULT_AGENT_SERVER_PATH} --host 0.0.0.0 --port ${port}`;
40
+ return directory && directory !== DEFAULT_AGENT_SERVER_DIR ? `cd ${directory} && ${command}` : command;
41
+ }
42
+
43
+ // src/openhands/types.ts
44
+ class OpenhandsStartupError extends Error {
45
+ code = "OPENHANDS_STARTUP_FAILED";
46
+ context;
47
+ constructor(message, context, options) {
48
+ super(message, options);
49
+ this.name = "OpenhandsStartupError";
50
+ this.context = context;
51
+ }
52
+ }
53
+ // src/openhands/openhands.ts
54
+ var DEFAULT_PORT = 8001;
55
+ async function findExistingAgentServer(sandbox, port) {
56
+ const processes = await sandbox.listProcesses();
57
+ const commandPattern = DEFAULT_AGENT_SERVER_PATH;
58
+ for (const proc of processes) {
59
+ if (proc.command.includes(commandPattern)) {
60
+ if (proc.command.includes(`--port ${port}`) || proc.command.includes(`--port=${port}`)) {
61
+ if (proc.status === "starting" || proc.status === "running") {
62
+ return proc;
63
+ }
64
+ }
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+ async function startAgentServer(sandbox, port, options) {
70
+ const directory = options.directory || DEFAULT_AGENT_SERVER_DIR;
71
+ const command = buildAgentServerCommand(port, directory);
72
+ const process = await sandbox.startProcess(command, {
73
+ cwd: directory,
74
+ env: options.env
75
+ });
76
+ try {
77
+ await process.waitForPort(port, {
78
+ mode: "http",
79
+ path: "/",
80
+ timeout: 60000
81
+ });
82
+ } catch (e) {
83
+ const logs = await process.getLogs();
84
+ throw new OpenhandsStartupError(`agent-server failed to start on port ${port}. Stderr: ${logs.stderr || "(empty)"}`, {
85
+ port,
86
+ stderr: logs.stderr,
87
+ command: process.command,
88
+ processId: process.id
89
+ }, { cause: e });
90
+ }
91
+ return process;
92
+ }
93
+ async function ensureAgentServer(sandbox, port, options) {
94
+ const existingProcess = await findExistingAgentServer(sandbox, port);
95
+ if (existingProcess) {
96
+ if (existingProcess.status === "starting") {
97
+ try {
98
+ await existingProcess.waitForPort(port, {
99
+ mode: "http",
100
+ path: "/",
101
+ timeout: 60000
102
+ });
103
+ } catch (e) {
104
+ const logs = await existingProcess.getLogs();
105
+ throw new OpenhandsStartupError(`agent-server failed to start. Stderr: ${logs.stderr || "(empty)"}`, {
106
+ port,
107
+ stderr: logs.stderr,
108
+ command: existingProcess.command,
109
+ processId: existingProcess.id
110
+ }, { cause: e });
111
+ }
112
+ }
113
+ return existingProcess;
114
+ }
115
+ try {
116
+ return await startAgentServer(sandbox, port, options);
117
+ } catch (startupError) {
118
+ const retryProcess = await findExistingAgentServer(sandbox, port);
119
+ if (retryProcess) {
120
+ if (retryProcess.status === "starting") {
121
+ try {
122
+ await retryProcess.waitForPort(port, {
123
+ mode: "http",
124
+ path: "/",
125
+ timeout: 60000
126
+ });
127
+ } catch (e) {
128
+ const logs = await retryProcess.getLogs();
129
+ throw new OpenhandsStartupError(`agent-server failed to start. Stderr: ${logs.stderr || "(empty)"}`, {
130
+ port,
131
+ stderr: logs.stderr,
132
+ command: retryProcess.command,
133
+ processId: retryProcess.id
134
+ }, { cause: e });
135
+ }
136
+ }
137
+ return retryProcess;
138
+ }
139
+ throw startupError;
140
+ }
141
+ }
142
+ async function createOpenhandsServer(sandbox, options = {}) {
143
+ const port = options.port ?? DEFAULT_PORT;
144
+ const process = await ensureAgentServer(sandbox, port, options);
145
+ let previewUrl;
146
+ if (options.exposePort) {
147
+ if (!options.hostname) {
148
+ throw new Error("hostname is required when exposePort is true. Provide hostname in options or extract from request URL.");
149
+ }
150
+ try {
151
+ const exposed = await sandbox.exposePort(port, {
152
+ hostname: options.hostname
153
+ });
154
+ previewUrl = typeof exposed === "string" ? exposed : exposed.url;
155
+ } catch (error) {
156
+ console.warn("Failed to expose port:", error);
157
+ }
158
+ }
159
+ return {
160
+ port,
161
+ url: `http://localhost:${port}`,
162
+ previewUrl,
163
+ processId: process.id,
164
+ async close() {
165
+ await process.kill("SIGTERM");
166
+ }
167
+ };
168
+ }
169
+ function proxyToOpenhands(request, sandbox, server) {
170
+ return sandbox.containerFetch(request, server.port);
171
+ }
172
+ // src/routes/handler.ts
173
+ import { getSandbox, proxyToSandbox } from "@cloudflare/sandbox";
174
+ async function findOpenhandsServer(sandbox, port) {
175
+ const processes = await sandbox.listProcesses();
176
+ for (const process of processes) {
177
+ if (process.command.includes(DEFAULT_AGENT_SERVER_PATH) && (process.command.includes(`--port ${port}`) || process.command.includes(`--port=${port}`)) && (process.status === "running" || process.status === "starting")) {
178
+ let previewUrl;
179
+ try {
180
+ const exposedPorts = await sandbox.getExposedPorts("");
181
+ const exposed = exposedPorts.find((p) => p.port === port);
182
+ if (exposed) {
183
+ previewUrl = exposed.url;
184
+ }
185
+ } catch {}
186
+ return {
187
+ port,
188
+ url: `http://localhost:${port}`,
189
+ previewUrl,
190
+ processId: process.id,
191
+ async close() {
192
+ await process.kill("SIGTERM");
193
+ }
194
+ };
195
+ }
196
+ }
197
+ return null;
198
+ }
199
+ function createOpenhandsHandler(options = {}) {
200
+ const basePath = options.basePath || "";
201
+ const getSandboxName = options.getSandboxName || ((req) => {
202
+ const url = new URL(req.url);
203
+ return url.searchParams.get("sandbox");
204
+ });
205
+ return async (request, env) => {
206
+ const url = new URL(request.url);
207
+ const pathname = url.pathname;
208
+ const sandboxName = getSandboxName(request) || options.sandboxName || "my-sandbox";
209
+ const sandbox = getSandbox(env.Sandbox, sandboxName);
210
+ const proxyResponse = await proxyToSandbox(request, env);
211
+ if (proxyResponse) {
212
+ return proxyResponse;
213
+ }
214
+ if (pathname === `${basePath}/start-openhands`) {
215
+ try {
216
+ const hostname = options.hostname || url.hostname;
217
+ const server = await createOpenhandsServer(sandbox, {
218
+ ...options,
219
+ hostname: options.exposePort ? hostname : undefined
220
+ });
221
+ return Response.json({
222
+ process: {
223
+ id: server.processId,
224
+ port: server.port,
225
+ status: "running"
226
+ },
227
+ previewUrl: server.previewUrl ? { url: server.previewUrl, port: server.port } : undefined,
228
+ success: true
229
+ });
230
+ } catch (error) {
231
+ return Response.json({
232
+ error: error instanceof Error ? error.message : String(error),
233
+ errorStack: error instanceof Error ? error.stack : undefined,
234
+ success: false
235
+ }, { status: 500 });
236
+ }
237
+ }
238
+ if (pathname === `${basePath}/stop-openhands`) {
239
+ try {
240
+ const port = options.port ?? 8001;
241
+ const server = await findOpenhandsServer(sandbox, port);
242
+ if (server) {
243
+ await server.close();
244
+ return Response.json({ success: true, message: "Server stopped" });
245
+ }
246
+ return Response.json({ success: false, message: "No server running" }, { status: 404 });
247
+ } catch (error) {
248
+ return Response.json({
249
+ error: error instanceof Error ? error.message : String(error),
250
+ success: false
251
+ }, { status: 500 });
252
+ }
253
+ }
254
+ if (pathname === `${basePath}/openhands-status`) {
255
+ try {
256
+ const port = options.port ?? 8001;
257
+ const server = await findOpenhandsServer(sandbox, port);
258
+ if (!server) {
259
+ return Response.json({
260
+ running: false,
261
+ success: true
262
+ });
263
+ }
264
+ const processes = await sandbox.listProcesses();
265
+ const process = processes.find((p) => p.id === server.processId);
266
+ return Response.json({
267
+ running: process?.status === "running",
268
+ port: server.port,
269
+ previewUrl: server.previewUrl,
270
+ process: process ? {
271
+ id: process.id,
272
+ status: process.status,
273
+ command: process.command
274
+ } : null,
275
+ success: true
276
+ });
277
+ } catch (error) {
278
+ return Response.json({
279
+ error: error instanceof Error ? error.message : String(error),
280
+ success: false
281
+ }, { status: 500 });
282
+ }
283
+ }
284
+ return null;
285
+ };
286
+ }
287
+ function attachOpenhandsRoutes(fetchHandler, options = {}) {
288
+ const openhandsHandler = createOpenhandsHandler(options);
289
+ return async (request, env) => {
290
+ const openhandsResponse = await openhandsHandler(request, env);
291
+ if (openhandsResponse !== null) {
292
+ return openhandsResponse;
293
+ }
294
+ return fetchHandler(request, env);
295
+ };
296
+ }
297
+ export {
298
+ createOpenhandsHandler,
299
+ attachOpenhandsRoutes
300
+ };
301
+
302
+ //# debugId=90B6CB75CEE3F17464756E2164756E21
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/utils/agent-server.ts", "../src/openhands/types.ts", "../src/openhands/openhands.ts", "../src/routes/handler.ts"],
4
+ "sourcesContent": [
5
+ "import type { Sandbox } from '@cloudflare/sandbox';\n\n/**\n * Default path to agent-server executable\n */\nexport const DEFAULT_AGENT_SERVER_PATH =\n '/container-server/software-agent-sdk/.venv/bin/agent-server';\n\n/**\n * Default working directory for agent-server\n */\nexport const DEFAULT_AGENT_SERVER_DIR =\n '/container-server/software-agent-sdk';\n\n/**\n * Get the default agent-server executable path\n */\nexport function getDefaultAgentServerPath(): string {\n return DEFAULT_AGENT_SERVER_PATH;\n}\n\n/**\n * Get the default agent-server working directory\n */\nexport function getDefaultAgentServerDir(): string {\n return DEFAULT_AGENT_SERVER_DIR;\n}\n\n/**\n * Check if agent-server exists at the given path\n */\nexport async function checkAgentServerExists(\n sandbox: Sandbox<unknown>,\n path: string = DEFAULT_AGENT_SERVER_PATH\n): Promise<boolean> {\n try {\n const result = await sandbox.exec(\n `test -f ${path} && echo \"EXISTS\" || echo \"NOT_FOUND\"`\n );\n return result.stdout.trim() === 'EXISTS';\n } catch {\n return false;\n }\n}\n\n/**\n * Find agent-server executable by checking common locations\n */\nexport async function findAgentServerPath(\n sandbox: Sandbox<unknown>\n): Promise<string | null> {\n const locations = [\n DEFAULT_AGENT_SERVER_PATH,\n '/software-agent-sdk/.venv/bin/agent-server',\n '/root/software-agent-sdk/.venv/bin/agent-server',\n ];\n\n for (const location of locations) {\n if (await checkAgentServerExists(sandbox, location)) {\n return location;\n }\n }\n\n // Try which as fallback\n try {\n const result = await sandbox.exec('which agent-server');\n const path = result.stdout.trim();\n if (path && !path.includes('not found')) {\n return path;\n }\n } catch {\n // Ignore errors\n }\n\n return null;\n}\n\n/**\n * Build the agent-server command\n */\nexport function buildAgentServerCommand(\n port: number,\n directory?: string\n): string {\n const command = `${DEFAULT_AGENT_SERVER_PATH} --host 0.0.0.0 --port ${port}`;\n return directory && directory !== DEFAULT_AGENT_SERVER_DIR\n ? `cd ${directory} && ${command}`\n : command;\n}\n\n",
6
+ "import type { Process } from '@cloudflare/sandbox';\n\n/**\n * Configuration options for starting OpenHands agent-server\n */\nexport interface OpenhandsOptions {\n /** Port for agent-server (default: 8001) */\n port?: number;\n /** Working directory for agent-server (default: /container-server/software-agent-sdk) */\n directory?: string;\n /** Hostname for preview URL exposure (required if exposePort is true) */\n hostname?: string;\n /** Enable preview URL exposure (default: false) */\n exposePort?: boolean;\n /** Environment variables for agent-server */\n env?: Record<string, string>;\n /** Sandbox name/session ID (default: 'my-sandbox') */\n sandboxName?: string;\n}\n\n/**\n * Server lifecycle management\n */\nexport interface OpenhandsServer {\n /** Port the server is running on */\n port: number;\n /** Base URL for server (http://localhost:{port}) */\n url: string;\n /** Preview URL if port was exposed (optional) */\n previewUrl?: string;\n /** Process ID */\n processId: string;\n /** Close the server gracefully */\n close(): Promise<void>;\n}\n\n/**\n * Context information for startup errors\n */\nexport interface OpenhandsStartupContext {\n port: number;\n stderr?: string;\n command?: string;\n processId?: string;\n}\n\n/**\n * Error thrown when agent-server fails to start\n */\nexport class OpenhandsStartupError extends Error {\n readonly code = 'OPENHANDS_STARTUP_FAILED' as const;\n readonly context: OpenhandsStartupContext;\n\n constructor(\n message: string,\n context: OpenhandsStartupContext,\n options?: ErrorOptions\n ) {\n super(message, options);\n this.name = 'OpenhandsStartupError';\n this.context = context;\n }\n}\n\n",
7
+ "import type { Sandbox, Process } from '@cloudflare/sandbox';\nimport type {\n OpenhandsOptions,\n OpenhandsServer,\n OpenhandsStartupContext,\n} from './types';\nimport { OpenhandsStartupError } from './types';\nimport {\n buildAgentServerCommand,\n DEFAULT_AGENT_SERVER_DIR,\n DEFAULT_AGENT_SERVER_PATH,\n} from '../utils/agent-server';\n\nconst DEFAULT_PORT = 8001;\n\n/**\n * Find an existing agent-server process running on the specified port.\n * Returns the process if found and still active, null otherwise.\n */\nasync function findExistingAgentServer(\n sandbox: Sandbox<unknown>,\n port: number\n): Promise<Process | null> {\n const processes = await sandbox.listProcesses();\n const commandPattern = DEFAULT_AGENT_SERVER_PATH;\n\n for (const proc of processes) {\n // Match commands that contain the agent-server path\n if (proc.command.includes(commandPattern)) {\n // Check if the command includes the port\n if (\n proc.command.includes(`--port ${port}`) ||\n proc.command.includes(`--port=${port}`)\n ) {\n if (proc.status === 'starting' || proc.status === 'running') {\n return proc;\n }\n }\n }\n }\n\n return null;\n}\n\n/**\n * Start a new agent-server process\n */\nasync function startAgentServer(\n sandbox: Sandbox<unknown>,\n port: number,\n options: OpenhandsOptions\n): Promise<Process> {\n const directory = options.directory || DEFAULT_AGENT_SERVER_DIR;\n const command = buildAgentServerCommand(port, directory);\n\n const process = await sandbox.startProcess(command, {\n cwd: directory,\n env: options.env,\n });\n\n // Wait for the server to be ready\n try {\n await process.waitForPort(port, {\n mode: 'http',\n path: '/',\n timeout: 60_000, // 60 seconds\n });\n } catch (e) {\n const logs = await process.getLogs();\n throw new OpenhandsStartupError(\n `agent-server failed to start on port ${port}. Stderr: ${logs.stderr || '(empty)'}`,\n {\n port,\n stderr: logs.stderr,\n command: process.command,\n processId: process.id,\n },\n { cause: e }\n );\n }\n\n return process;\n}\n\n/**\n * Ensures agent-server is running in the container.\n * Reuses existing process if one is already running on the specified port.\n * Handles concurrent startup attempts gracefully by retrying on failure.\n */\nasync function ensureAgentServer(\n sandbox: Sandbox<unknown>,\n port: number,\n options: OpenhandsOptions\n): Promise<Process> {\n // Check if agent-server is already running on this port\n const existingProcess = await findExistingAgentServer(sandbox, port);\n if (existingProcess) {\n // Reuse existing process - wait for it to be ready if still starting\n if (existingProcess.status === 'starting') {\n try {\n await existingProcess.waitForPort(port, {\n mode: 'http',\n path: '/',\n timeout: 60_000,\n });\n } catch (e) {\n const logs = await existingProcess.getLogs();\n throw new OpenhandsStartupError(\n `agent-server failed to start. Stderr: ${logs.stderr || '(empty)'}`,\n {\n port,\n stderr: logs.stderr,\n command: existingProcess.command,\n processId: existingProcess.id,\n },\n { cause: e }\n );\n }\n }\n return existingProcess;\n }\n\n // Try to start a new agent-server\n try {\n return await startAgentServer(sandbox, port, options);\n } catch (startupError) {\n // Startup failed - check if another concurrent request started the server\n // This handles the race condition where multiple requests try to start simultaneously\n const retryProcess = await findExistingAgentServer(sandbox, port);\n if (retryProcess) {\n // Wait for the concurrent server to be ready\n if (retryProcess.status === 'starting') {\n try {\n await retryProcess.waitForPort(port, {\n mode: 'http',\n path: '/',\n timeout: 60_000,\n });\n } catch (e) {\n const logs = await retryProcess.getLogs();\n throw new OpenhandsStartupError(\n `agent-server failed to start. Stderr: ${logs.stderr || '(empty)'}`,\n {\n port,\n stderr: logs.stderr,\n command: retryProcess.command,\n processId: retryProcess.id,\n },\n { cause: e }\n );\n }\n }\n return retryProcess;\n }\n\n // No concurrent process found, rethrow the original error\n throw startupError;\n }\n}\n\n/**\n * Starts an agent-server inside a Sandbox container.\n *\n * This function manages the server lifecycle only. If an agent-server is already\n * running on the specified port, this function will reuse it instead of starting\n * a new one.\n *\n * @param sandbox - The Sandbox instance to run agent-server in\n * @param options - Configuration options\n * @returns Promise resolving to server handle { port, url, previewUrl?, close() }\n *\n * @example\n * ```typescript\n * import { getSandbox } from '@cloudflare/sandbox'\n * import { createOpenhandsServer } from 'openhands-sdk/openhands'\n *\n * const sandbox = getSandbox(env.Sandbox, 'my-sandbox')\n * const server = await createOpenhandsServer(sandbox, {\n * port: 8001,\n * exposePort: true,\n * hostname: 'yourdomain.com'\n * })\n *\n * // Proxy requests to the server\n * return sandbox.containerFetch(request, server.port)\n *\n * // When done\n * await server.close()\n * ```\n */\nexport async function createOpenhandsServer(\n sandbox: Sandbox<unknown>,\n options: OpenhandsOptions = {}\n): Promise<OpenhandsServer> {\n const port = options.port ?? DEFAULT_PORT;\n const process = await ensureAgentServer(sandbox, port, options);\n\n let previewUrl: string | undefined;\n\n // Optionally expose port for preview URL\n if (options.exposePort) {\n if (!options.hostname) {\n throw new Error(\n 'hostname is required when exposePort is true. Provide hostname in options or extract from request URL.'\n );\n }\n\n try {\n const exposed = await sandbox.exposePort(port, {\n hostname: options.hostname,\n });\n previewUrl = typeof exposed === 'string' ? exposed : exposed.url;\n } catch (error) {\n // Log but don't fail - server is still running\n console.warn('Failed to expose port:', error);\n }\n }\n\n return {\n port,\n url: `http://localhost:${port}`,\n previewUrl,\n processId: process.id,\n async close() {\n await process.kill('SIGTERM');\n },\n };\n}\n\n/**\n * Proxy a request to the agent-server.\n *\n * This function handles proxying only - you must start the server separately\n * using `createOpenhandsServer()`.\n *\n * @param request - The incoming HTTP request\n * @param sandbox - The Sandbox instance running agent-server\n * @param server - The agent-server handle from createOpenhandsServer()\n * @returns Response from agent-server\n *\n * @example\n * ```typescript\n * import { getSandbox } from '@cloudflare/sandbox'\n * import { createOpenhandsServer, proxyToOpenhands } from 'openhands-sdk/openhands'\n *\n * export default {\n * async fetch(request: Request, env: Env) {\n * const sandbox = getSandbox(env.Sandbox, 'my-sandbox')\n * const server = await createOpenhandsServer(sandbox, {\n * port: 8001,\n * exposePort: true,\n * hostname: 'yourdomain.com'\n * })\n * return proxyToOpenhands(request, sandbox, server)\n * }\n * }\n * ```\n */\nexport function proxyToOpenhands(\n request: Request,\n sandbox: Sandbox<unknown>,\n server: OpenhandsServer\n): Response | Promise<Response> {\n return sandbox.containerFetch(request, server.port);\n}\n\n",
8
+ "import { getSandbox, proxyToSandbox } from '@cloudflare/sandbox';\nimport type { Sandbox } from '@cloudflare/sandbox';\nimport {\n createOpenhandsServer,\n proxyToOpenhands,\n type OpenhandsOptions,\n type OpenhandsServer,\n} from '../openhands';\nimport { DEFAULT_AGENT_SERVER_PATH } from '../utils/agent-server';\n\n// Cloudflare Workers environment type constraint\n// Accepts any Env type that has a Sandbox property compatible with DurableObjectNamespace\n// This is a minimal structural type that matches what getSandbox and proxyToSandbox need\n// Using a more permissive type that accepts any object with the required structure\nexport type OpenhandsEnv = {\n Sandbox: {\n get(id: { toString(): string }): {\n fetch(input: string | Request, init?: RequestInit): Promise<Response>;\n };\n };\n};\n\n/**\n * Options for OpenHands route handler\n */\nexport interface OpenhandsHandlerOptions extends OpenhandsOptions {\n /** Base path for OpenHands routes (default: '') */\n basePath?: string;\n /** Custom sandbox name resolver */\n getSandboxName?: (request: Request) => string | null;\n}\n\n/**\n * Find existing OpenHands server by port\n */\nasync function findOpenhandsServer(\n sandbox: Sandbox<unknown>,\n port: number\n): Promise<OpenhandsServer | null> {\n const processes = await sandbox.listProcesses();\n\n for (const process of processes) {\n // Match agent-server processes on the specified port\n if (\n process.command.includes(DEFAULT_AGENT_SERVER_PATH) &&\n (process.command.includes(`--port ${port}`) ||\n process.command.includes(`--port=${port}`)) &&\n (process.status === 'running' || process.status === 'starting')\n ) {\n // Try to get exposed ports to find preview URL\n let previewUrl: string | undefined;\n try {\n // Try to get exposed ports - hostname is optional\n const exposedPorts = await sandbox.getExposedPorts('');\n const exposed = exposedPorts.find((p) => p.port === port);\n if (exposed) {\n previewUrl = exposed.url;\n }\n } catch {\n // Ignore errors getting exposed ports\n }\n\n return {\n port,\n url: `http://localhost:${port}`,\n previewUrl,\n processId: process.id,\n async close() {\n await process.kill('SIGTERM');\n },\n };\n }\n }\n\n return null;\n}\n\n/**\n * Creates a middleware function that handles OpenHands routes.\n * Returns null if the request doesn't match OpenHands routes (allows chaining).\n *\n * @param options - Configuration options\n * @returns Middleware function\n */\nexport function createOpenhandsHandler<Env extends OpenhandsEnv = OpenhandsEnv>(\n options: OpenhandsHandlerOptions = {}\n) {\n const basePath = options.basePath || '';\n const getSandboxName = options.getSandboxName || ((req) => {\n const url = new URL(req.url);\n return url.searchParams.get('sandbox');\n });\n\n return async (\n request: Request,\n env: Env\n ): Promise<Response | null> => {\n const url = new URL(request.url);\n const pathname = url.pathname;\n\n // Extract sandbox name\n const sandboxName = getSandboxName(request) || options.sandboxName || 'my-sandbox';\n const sandbox = getSandbox(env.Sandbox, sandboxName);\n\n // Handle preview URL proxying (must be first)\n const proxyResponse = await proxyToSandbox(request, env);\n if (proxyResponse) {\n return proxyResponse;\n }\n\n // Handle OpenHands routes\n if (pathname === `${basePath}/start-openhands`) {\n try {\n // Extract hostname from request if not provided\n const hostname = options.hostname || url.hostname;\n\n const server = await createOpenhandsServer(sandbox, {\n ...options,\n hostname: options.exposePort ? hostname : undefined,\n });\n\n return Response.json({\n process: {\n id: server.processId,\n port: server.port,\n status: 'running',\n },\n previewUrl: server.previewUrl\n ? { url: server.previewUrl, port: server.port }\n : undefined,\n success: true,\n });\n } catch (error) {\n return Response.json(\n {\n error: error instanceof Error ? error.message : String(error),\n errorStack: error instanceof Error ? error.stack : undefined,\n success: false,\n },\n { status: 500 }\n );\n }\n }\n\n if (pathname === `${basePath}/stop-openhands`) {\n try {\n const port = options.port ?? 8001;\n const server = await findOpenhandsServer(sandbox, port);\n if (server) {\n await server.close();\n return Response.json({ success: true, message: 'Server stopped' });\n }\n return Response.json(\n { success: false, message: 'No server running' },\n { status: 404 }\n );\n } catch (error) {\n return Response.json(\n {\n error: error instanceof Error ? error.message : String(error),\n success: false,\n },\n { status: 500 }\n );\n }\n }\n\n if (pathname === `${basePath}/openhands-status`) {\n try {\n const port = options.port ?? 8001;\n const server = await findOpenhandsServer(sandbox, port);\n if (!server) {\n return Response.json({\n running: false,\n success: true,\n });\n }\n\n // Verify process is still running\n const processes = await sandbox.listProcesses();\n const process = processes.find((p) => p.id === server.processId);\n\n return Response.json({\n running: process?.status === 'running',\n port: server.port,\n previewUrl: server.previewUrl,\n process: process\n ? {\n id: process.id,\n status: process.status,\n command: process.command,\n }\n : null,\n success: true,\n });\n } catch (error) {\n return Response.json(\n {\n error: error instanceof Error ? error.message : String(error),\n success: false,\n },\n { status: 500 }\n );\n }\n }\n\n // Request doesn't match OpenHands routes\n return null;\n };\n}\n\n/**\n * Attaches OpenHands routes to an existing fetch handler.\n * This wraps your handler with OpenHands route handling and preview URL proxying.\n *\n * @param fetchHandler - Your existing fetch handler\n * @param options - Configuration options\n * @returns Wrapped fetch handler\n *\n * @example\n * ```typescript\n * import { attachOpenhandsRoutes } from 'openhands-sdk/routes';\n *\n * export default attachOpenhandsRoutes(async (request, env) => {\n * // Your custom routes here\n * return new Response('Not found', { status: 404 });\n * }, {\n * port: 8001,\n * exposePort: true,\n * hostname: 'yourdomain.com'\n * });\n * ```\n */\nexport function attachOpenhandsRoutes<Env extends OpenhandsEnv = OpenhandsEnv>(\n fetchHandler: (\n request: Request,\n env: Env\n ) => Response | Promise<Response>,\n options: OpenhandsHandlerOptions = {}\n): (\n request: Request,\n env: Env\n) => Promise<Response> {\n const openhandsHandler = createOpenhandsHandler<Env>(options);\n\n return async (\n request: Request,\n env: Env\n ): Promise<Response> => {\n // First, try OpenHands handler\n const openhandsResponse = await openhandsHandler(request, env);\n if (openhandsResponse !== null) {\n return openhandsResponse;\n }\n\n // Fall through to user's handler\n return fetchHandler(request, env);\n };\n}\n\n"
9
+ ],
10
+ "mappings": "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",
11
+ "debugId": "90B6CB75CEE3F17464756E2164756E21",
12
+ "names": []
13
+ }
@@ -0,0 +1,30 @@
1
+ import type { Sandbox } from '@cloudflare/sandbox';
2
+ /**
3
+ * Default path to agent-server executable
4
+ */
5
+ export declare const DEFAULT_AGENT_SERVER_PATH = "/container-server/software-agent-sdk/.venv/bin/agent-server";
6
+ /**
7
+ * Default working directory for agent-server
8
+ */
9
+ export declare const DEFAULT_AGENT_SERVER_DIR = "/container-server/software-agent-sdk";
10
+ /**
11
+ * Get the default agent-server executable path
12
+ */
13
+ export declare function getDefaultAgentServerPath(): string;
14
+ /**
15
+ * Get the default agent-server working directory
16
+ */
17
+ export declare function getDefaultAgentServerDir(): string;
18
+ /**
19
+ * Check if agent-server exists at the given path
20
+ */
21
+ export declare function checkAgentServerExists(sandbox: Sandbox<unknown>, path?: string): Promise<boolean>;
22
+ /**
23
+ * Find agent-server executable by checking common locations
24
+ */
25
+ export declare function findAgentServerPath(sandbox: Sandbox<unknown>): Promise<string | null>;
26
+ /**
27
+ * Build the agent-server command
28
+ */
29
+ export declare function buildAgentServerCommand(port: number, directory?: string): string;
30
+ //# sourceMappingURL=agent-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-server.d.ts","sourceRoot":"","sources":["../../src/utils/agent-server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD;;GAEG;AACH,eAAO,MAAM,yBAAyB,gEACyB,CAAC;AAEhE;;GAEG;AACH,eAAO,MAAM,wBAAwB,yCACG,CAAC;AAEzC;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EACzB,IAAI,GAAE,MAAkC,GACvC,OAAO,CAAC,OAAO,CAAC,CASlB;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GACxB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyBxB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAKR"}
@@ -0,0 +1,52 @@
1
+ // src/utils/agent-server.ts
2
+ var DEFAULT_AGENT_SERVER_PATH = "/container-server/software-agent-sdk/.venv/bin/agent-server";
3
+ var DEFAULT_AGENT_SERVER_DIR = "/container-server/software-agent-sdk";
4
+ function getDefaultAgentServerPath() {
5
+ return DEFAULT_AGENT_SERVER_PATH;
6
+ }
7
+ function getDefaultAgentServerDir() {
8
+ return DEFAULT_AGENT_SERVER_DIR;
9
+ }
10
+ async function checkAgentServerExists(sandbox, path = DEFAULT_AGENT_SERVER_PATH) {
11
+ try {
12
+ const result = await sandbox.exec(`test -f ${path} && echo "EXISTS" || echo "NOT_FOUND"`);
13
+ return result.stdout.trim() === "EXISTS";
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+ async function findAgentServerPath(sandbox) {
19
+ const locations = [
20
+ DEFAULT_AGENT_SERVER_PATH,
21
+ "/software-agent-sdk/.venv/bin/agent-server",
22
+ "/root/software-agent-sdk/.venv/bin/agent-server"
23
+ ];
24
+ for (const location of locations) {
25
+ if (await checkAgentServerExists(sandbox, location)) {
26
+ return location;
27
+ }
28
+ }
29
+ try {
30
+ const result = await sandbox.exec("which agent-server");
31
+ const path = result.stdout.trim();
32
+ if (path && !path.includes("not found")) {
33
+ return path;
34
+ }
35
+ } catch {}
36
+ return null;
37
+ }
38
+ function buildAgentServerCommand(port, directory) {
39
+ const command = `${DEFAULT_AGENT_SERVER_PATH} --host 0.0.0.0 --port ${port}`;
40
+ return directory && directory !== DEFAULT_AGENT_SERVER_DIR ? `cd ${directory} && ${command}` : command;
41
+ }
42
+ export {
43
+ getDefaultAgentServerPath,
44
+ getDefaultAgentServerDir,
45
+ findAgentServerPath,
46
+ checkAgentServerExists,
47
+ buildAgentServerCommand,
48
+ DEFAULT_AGENT_SERVER_PATH,
49
+ DEFAULT_AGENT_SERVER_DIR
50
+ };
51
+
52
+ //# debugId=3D6BDF72373DD85F64756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/utils/agent-server.ts"],
4
+ "sourcesContent": [
5
+ "import type { Sandbox } from '@cloudflare/sandbox';\n\n/**\n * Default path to agent-server executable\n */\nexport const DEFAULT_AGENT_SERVER_PATH =\n '/container-server/software-agent-sdk/.venv/bin/agent-server';\n\n/**\n * Default working directory for agent-server\n */\nexport const DEFAULT_AGENT_SERVER_DIR =\n '/container-server/software-agent-sdk';\n\n/**\n * Get the default agent-server executable path\n */\nexport function getDefaultAgentServerPath(): string {\n return DEFAULT_AGENT_SERVER_PATH;\n}\n\n/**\n * Get the default agent-server working directory\n */\nexport function getDefaultAgentServerDir(): string {\n return DEFAULT_AGENT_SERVER_DIR;\n}\n\n/**\n * Check if agent-server exists at the given path\n */\nexport async function checkAgentServerExists(\n sandbox: Sandbox<unknown>,\n path: string = DEFAULT_AGENT_SERVER_PATH\n): Promise<boolean> {\n try {\n const result = await sandbox.exec(\n `test -f ${path} && echo \"EXISTS\" || echo \"NOT_FOUND\"`\n );\n return result.stdout.trim() === 'EXISTS';\n } catch {\n return false;\n }\n}\n\n/**\n * Find agent-server executable by checking common locations\n */\nexport async function findAgentServerPath(\n sandbox: Sandbox<unknown>\n): Promise<string | null> {\n const locations = [\n DEFAULT_AGENT_SERVER_PATH,\n '/software-agent-sdk/.venv/bin/agent-server',\n '/root/software-agent-sdk/.venv/bin/agent-server',\n ];\n\n for (const location of locations) {\n if (await checkAgentServerExists(sandbox, location)) {\n return location;\n }\n }\n\n // Try which as fallback\n try {\n const result = await sandbox.exec('which agent-server');\n const path = result.stdout.trim();\n if (path && !path.includes('not found')) {\n return path;\n }\n } catch {\n // Ignore errors\n }\n\n return null;\n}\n\n/**\n * Build the agent-server command\n */\nexport function buildAgentServerCommand(\n port: number,\n directory?: string\n): string {\n const command = `${DEFAULT_AGENT_SERVER_PATH} --host 0.0.0.0 --port ${port}`;\n return directory && directory !== DEFAULT_AGENT_SERVER_DIR\n ? `cd ${directory} && ${command}`\n : command;\n}\n\n"
6
+ ],
7
+ "mappings": ";AAKO,IAAM,4BACX;AAKK,IAAM,2BACX;AAKK,SAAS,yBAAyB,GAAW;AAAA,EAClD,OAAO;AAAA;AAMF,SAAS,wBAAwB,GAAW;AAAA,EACjD,OAAO;AAAA;AAMT,eAAsB,sBAAsB,CAC1C,SACA,OAAe,2BACG;AAAA,EAClB,IAAI;AAAA,IACF,MAAM,SAAS,MAAM,QAAQ,KAC3B,WAAW,2CACb;AAAA,IACA,OAAO,OAAO,OAAO,KAAK,MAAM;AAAA,IAChC,MAAM;AAAA,IACN,OAAO;AAAA;AAAA;AAOX,eAAsB,mBAAmB,CACvC,SACwB;AAAA,EACxB,MAAM,YAAY;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EAEA,WAAW,YAAY,WAAW;AAAA,IAChC,IAAI,MAAM,uBAAuB,SAAS,QAAQ,GAAG;AAAA,MACnD,OAAO;AAAA,IACT;AAAA,EACF;AAAA,EAGA,IAAI;AAAA,IACF,MAAM,SAAS,MAAM,QAAQ,KAAK,oBAAoB;AAAA,IACtD,MAAM,OAAO,OAAO,OAAO,KAAK;AAAA,IAChC,IAAI,QAAQ,CAAC,KAAK,SAAS,WAAW,GAAG;AAAA,MACvC,OAAO;AAAA,IACT;AAAA,IACA,MAAM;AAAA,EAIR,OAAO;AAAA;AAMF,SAAS,uBAAuB,CACrC,MACA,WACQ;AAAA,EACR,MAAM,UAAU,GAAG,mDAAmD;AAAA,EACtE,OAAO,aAAa,cAAc,2BAC9B,MAAM,gBAAgB,YACtB;AAAA;",
8
+ "debugId": "3D6BDF72373DD85F64756E2164756E21",
9
+ "names": []
10
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "cloudflare-openhands-sdk",
3
+ "version": "0.1.0",
4
+ "description": "SDK for integrating OpenHands agent-server with Cloudflare Workers Sandbox",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./openhands": {
15
+ "types": "./dist/openhands/index.d.ts",
16
+ "import": "./dist/openhands/index.js"
17
+ },
18
+ "./routes": {
19
+ "types": "./dist/routes/handler.d.ts",
20
+ "import": "./dist/routes/handler.js"
21
+ },
22
+ "./utils": {
23
+ "types": "./dist/utils/agent-server.d.ts",
24
+ "import": "./dist/utils/agent-server.js"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "Dockerfile",
30
+ "README.md"
31
+ ],
32
+ "scripts": {
33
+ "build": "bun run build.ts",
34
+ "prepublishOnly": "bun run build"
35
+ },
36
+ "devDependencies": {
37
+ "@types/bun": "latest",
38
+ "typescript": "^5"
39
+ },
40
+ "peerDependencies": {
41
+ "@cloudflare/sandbox": "^0.6.7"
42
+ },
43
+ "keywords": [
44
+ "openhands",
45
+ "cloudflare",
46
+ "workers",
47
+ "sandbox",
48
+ "agent-server"
49
+ ],
50
+ "license": "MIT",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/jagzmz/cloudflare-openhands-sdk.git"
54
+ }
55
+ }