@cloudflare/sandbox 0.13.0-next.651.1 → 0.13.0-next.709.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/Dockerfile +12 -17
- package/README.md +48 -2
- package/dist/bridge/index.d.ts.map +1 -1
- package/dist/bridge/index.js +1365 -1054
- package/dist/bridge/index.js.map +1 -1
- package/dist/{contexts-BS0Bs6IU.d.ts → contexts-1EsLHByO.d.ts} +240 -15
- package/dist/contexts-1EsLHByO.d.ts.map +1 -0
- package/dist/{dist-DF8sudAg.js → dist-Duor5GbS.js} +38 -147
- package/dist/dist-Duor5GbS.js.map +1 -0
- package/dist/errors/index.d.ts +4 -0
- package/dist/errors/index.js +4 -0
- package/dist/{errors-BG6NZiPD.js → errors-CXR0xBpw.js} +82 -13
- package/dist/errors-CXR0xBpw.js.map +1 -0
- package/dist/errors-QYlSkVGz.js +893 -0
- package/dist/errors-QYlSkVGz.js.map +1 -0
- package/dist/extensions/index.d.ts +4 -74
- package/dist/extensions/index.js +5 -152
- package/dist/extensions-CFB2xHqY.js +1023 -0
- package/dist/extensions-CFB2xHqY.js.map +1 -0
- package/dist/filesystem-BWAZCZER.d.ts +732 -0
- package/dist/filesystem-BWAZCZER.d.ts.map +1 -0
- package/dist/git/index.d.ts +63 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/index.js +338 -0
- package/dist/git/index.js.map +1 -0
- package/dist/index-Bs4bqXDR.d.ts +438 -0
- package/dist/index-Bs4bqXDR.d.ts.map +1 -0
- package/dist/index-HNYBk-az.d.ts +444 -0
- package/dist/index-HNYBk-az.d.ts.map +1 -0
- package/dist/index.d.ts +487 -181
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -43
- package/dist/index.js.map +1 -1
- package/dist/interpreter/index.d.ts +311 -0
- package/dist/interpreter/index.d.ts.map +1 -0
- package/dist/interpreter/index.js +292 -0
- package/dist/interpreter/index.js.map +1 -0
- package/dist/openai/index.d.ts +5 -4
- package/dist/openai/index.d.ts.map +1 -1
- package/dist/openai/index.js +11 -6
- package/dist/openai/index.js.map +1 -1
- package/dist/opencode/index.d.ts +133 -161
- package/dist/opencode/index.d.ts.map +1 -1
- package/dist/opencode/index.js +284 -203
- package/dist/opencode/index.js.map +1 -1
- package/dist/process-types-GStiZ8f8.d.ts +73 -0
- package/dist/process-types-GStiZ8f8.d.ts.map +1 -0
- package/dist/sandbox-Auuwfnur.js +10010 -0
- package/dist/sandbox-Auuwfnur.js.map +1 -0
- package/dist/sandbox-BbAabq93.d.ts +42 -0
- package/dist/sandbox-BbAabq93.d.ts.map +1 -0
- package/dist/xterm/index.d.ts +11 -7
- package/dist/xterm/index.d.ts.map +1 -1
- package/dist/xterm/index.js +61 -18
- package/dist/xterm/index.js.map +1 -1
- package/package.json +28 -4
- package/dist/contexts-BS0Bs6IU.d.ts.map +0 -1
- package/dist/dist-DF8sudAg.js.map +0 -1
- package/dist/errors-BG6NZiPD.js.map +0 -1
- package/dist/extensions/index.d.ts.map +0 -1
- package/dist/extensions/index.js.map +0 -1
- package/dist/rpc-types-PBUY-xXM.d.ts +0 -1679
- package/dist/rpc-types-PBUY-xXM.d.ts.map +0 -1
- package/dist/sandbox-BgwMBQ7S.js +0 -8273
- package/dist/sandbox-BgwMBQ7S.js.map +0 -1
- package/dist/sandbox-D_MMqExx.d.ts +0 -1077
- package/dist/sandbox-D_MMqExx.d.ts.map +0 -1
package/dist/opencode/index.js
CHANGED
|
@@ -1,60 +1,103 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { i as ErrorCode } from "../errors-CXR0xBpw.js";
|
|
2
|
+
import "../errors-QYlSkVGz.js";
|
|
3
|
+
import { r as createLogger } from "../dist-Duor5GbS.js";
|
|
4
|
+
import { n as createExtensionProcessSandbox, t as SandboxExtension } from "../extensions-CFB2xHqY.js";
|
|
3
5
|
|
|
4
|
-
//#region
|
|
6
|
+
//#region ../../extensions/opencode/src/client.ts
|
|
7
|
+
let createSDKClient;
|
|
8
|
+
async function loadSDK() {
|
|
9
|
+
if (createSDKClient) return createSDKClient;
|
|
10
|
+
try {
|
|
11
|
+
createSDKClient = (await import("@opencode-ai/sdk/v2/client")).createOpencodeClient;
|
|
12
|
+
return createSDKClient;
|
|
13
|
+
} catch {
|
|
14
|
+
throw new Error("@opencode-ai/sdk is required for OpenCode integration. Install it with: npm install @opencode-ai/sdk");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Build a typed OpenCode SDK client from a lifecycle handle.
|
|
19
|
+
*
|
|
20
|
+
* Works against either the Worker stub (`sandbox.opencode`) or the in-DO object
|
|
21
|
+
* (`this.opencode`): it ensures the server through the handle, reads the stored
|
|
22
|
+
* config, and routes every request through `handle.fetch`. Because the handle
|
|
23
|
+
* owns `containerFetch`, this helper never touches sandbox transport directly.
|
|
24
|
+
*
|
|
25
|
+
* The SDK is imported lazily so the peer dependency is only required when an
|
|
26
|
+
* OpenCode client is actually built.
|
|
27
|
+
*/
|
|
28
|
+
async function createOpenCodeClient(handle, options) {
|
|
29
|
+
const server = await handle.start(options);
|
|
30
|
+
const config = await handle.config();
|
|
31
|
+
const directory = options?.directory ?? config.directory;
|
|
32
|
+
return (await loadSDK())({
|
|
33
|
+
baseUrl: server.url,
|
|
34
|
+
fetch: (input, init) => handle.fetch(new Request(input, init)),
|
|
35
|
+
directory
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region ../../extensions/opencode/src/types.ts
|
|
5
41
|
/**
|
|
6
42
|
* Error thrown when OpenCode server fails to start
|
|
7
43
|
*/
|
|
8
|
-
var
|
|
44
|
+
var OpenCodeStartupError = class extends Error {
|
|
9
45
|
code = ErrorCode.OPENCODE_STARTUP_FAILED;
|
|
10
46
|
context;
|
|
11
47
|
constructor(message, context, options) {
|
|
12
48
|
super(message, options);
|
|
13
|
-
this.name = "
|
|
49
|
+
this.name = "OpenCodeStartupError";
|
|
14
50
|
this.context = context;
|
|
15
51
|
}
|
|
16
52
|
};
|
|
17
53
|
|
|
18
54
|
//#endregion
|
|
19
|
-
//#region
|
|
55
|
+
//#region ../../extensions/opencode/src/opencode.ts
|
|
20
56
|
function getLogger() {
|
|
21
57
|
return createLogger({
|
|
22
58
|
component: "sandbox-do",
|
|
23
59
|
operation: "opencode"
|
|
24
60
|
});
|
|
25
61
|
}
|
|
26
|
-
const DEFAULT_PORT = 4096;
|
|
62
|
+
const DEFAULT_PORT$1 = 4096;
|
|
27
63
|
const OPENCODE_STARTUP_TIMEOUT_MS = 18e4;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
64
|
+
function openCodeCommand(port) {
|
|
65
|
+
return [
|
|
66
|
+
"opencode",
|
|
67
|
+
"serve",
|
|
68
|
+
"--port",
|
|
69
|
+
String(port),
|
|
70
|
+
"--hostname",
|
|
71
|
+
"0.0.0.0"
|
|
72
|
+
];
|
|
36
73
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (createOpencodeClient) return;
|
|
40
|
-
try {
|
|
41
|
-
createOpencodeClient = (await import("@opencode-ai/sdk/v2/client")).createOpencodeClient;
|
|
42
|
-
} catch {
|
|
43
|
-
throw new Error("@opencode-ai/sdk is required for OpenCode integration. Install it with: npm install @opencode-ai/sdk");
|
|
44
|
-
}
|
|
74
|
+
function commandsEqual(a, b) {
|
|
75
|
+
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
45
76
|
}
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
77
|
+
/** Recover a running OpenCode process from runtime-local process status. */
|
|
78
|
+
async function findExistingOpenCodeProcess(sandbox, port, directory) {
|
|
79
|
+
const command = openCodeCommand(port);
|
|
80
|
+
const processes = createExtensionProcessSandbox(sandbox);
|
|
81
|
+
const status = (await processes.listProcesses()).find((candidate) => candidate.state === "running" && candidate.cwd === directory && commandsEqual(candidate.command, command));
|
|
82
|
+
return status ? processes.getProcess(status.id) : null;
|
|
83
|
+
}
|
|
84
|
+
async function collectStderr(process) {
|
|
85
|
+
const reader = (await process.logs({
|
|
86
|
+
replay: true,
|
|
87
|
+
follow: false
|
|
88
|
+
})).getReader();
|
|
89
|
+
const decoder = new TextDecoder();
|
|
90
|
+
let stderr = "";
|
|
91
|
+
try {
|
|
92
|
+
while (true) {
|
|
93
|
+
const { done, value } = await reader.read();
|
|
94
|
+
if (done) break;
|
|
95
|
+
if (value.type === "stderr") stderr += decoder.decode(value.data, { stream: true });
|
|
96
|
+
}
|
|
97
|
+
return stderr + decoder.decode();
|
|
98
|
+
} finally {
|
|
99
|
+
reader.releaseLock();
|
|
56
100
|
}
|
|
57
|
-
return null;
|
|
58
101
|
}
|
|
59
102
|
/**
|
|
60
103
|
* Ensures OpenCode server is running in the container.
|
|
@@ -62,30 +105,9 @@ async function findExistingOpencodeProcess(sandbox, port) {
|
|
|
62
105
|
* Handles concurrent startup attempts gracefully by retrying on failure.
|
|
63
106
|
* Returns the process handle.
|
|
64
107
|
*/
|
|
65
|
-
async function
|
|
66
|
-
const existingProcess = await
|
|
108
|
+
async function ensureOpenCodeServer(sandbox, port, directory, config, customEnv) {
|
|
109
|
+
const existingProcess = await findExistingOpenCodeProcess(sandbox, port, directory);
|
|
67
110
|
if (existingProcess) {
|
|
68
|
-
if (existingProcess.status === "starting") {
|
|
69
|
-
getLogger().debug("Found starting OpenCode process, waiting for ready", {
|
|
70
|
-
port,
|
|
71
|
-
processId: existingProcess.id
|
|
72
|
-
});
|
|
73
|
-
try {
|
|
74
|
-
await existingProcess.waitForPort(port, {
|
|
75
|
-
mode: "http",
|
|
76
|
-
path: "/path",
|
|
77
|
-
status: 200,
|
|
78
|
-
timeout: OPENCODE_STARTUP_TIMEOUT_MS
|
|
79
|
-
});
|
|
80
|
-
} catch (e) {
|
|
81
|
-
const logs = await existingProcess.getLogs();
|
|
82
|
-
throw new OpencodeStartupError(`OpenCode server failed to start. Stderr: ${logs.stderr || "(empty)"}`, {
|
|
83
|
-
port,
|
|
84
|
-
stderr: logs.stderr,
|
|
85
|
-
command: existingProcess.command
|
|
86
|
-
}, { cause: e });
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
111
|
getLogger().debug("Reusing existing OpenCode process", {
|
|
90
112
|
port,
|
|
91
113
|
processId: existingProcess.id
|
|
@@ -93,29 +115,14 @@ async function ensureOpencodeServer(sandbox, port, directory, config, customEnv)
|
|
|
93
115
|
return existingProcess;
|
|
94
116
|
}
|
|
95
117
|
try {
|
|
96
|
-
return await
|
|
118
|
+
return await startOpenCodeServer(sandbox, port, directory, config, customEnv);
|
|
97
119
|
} catch (startupError) {
|
|
98
|
-
const retryProcess = await
|
|
120
|
+
const retryProcess = await findExistingOpenCodeProcess(sandbox, port, directory);
|
|
99
121
|
if (retryProcess) {
|
|
100
122
|
getLogger().debug("Startup failed but found concurrent process, reusing", {
|
|
101
123
|
port,
|
|
102
124
|
processId: retryProcess.id
|
|
103
125
|
});
|
|
104
|
-
if (retryProcess.status === "starting") try {
|
|
105
|
-
await retryProcess.waitForPort(port, {
|
|
106
|
-
mode: "http",
|
|
107
|
-
path: "/path",
|
|
108
|
-
status: 200,
|
|
109
|
-
timeout: OPENCODE_STARTUP_TIMEOUT_MS
|
|
110
|
-
});
|
|
111
|
-
} catch (e) {
|
|
112
|
-
const logs = await retryProcess.getLogs();
|
|
113
|
-
throw new OpencodeStartupError(`OpenCode server failed to start. Stderr: ${logs.stderr || "(empty)"}`, {
|
|
114
|
-
port,
|
|
115
|
-
stderr: logs.stderr,
|
|
116
|
-
command: retryProcess.command
|
|
117
|
-
}, { cause: e });
|
|
118
|
-
}
|
|
119
126
|
return retryProcess;
|
|
120
127
|
}
|
|
121
128
|
throw startupError;
|
|
@@ -124,7 +131,7 @@ async function ensureOpencodeServer(sandbox, port, directory, config, customEnv)
|
|
|
124
131
|
/**
|
|
125
132
|
* Internal function to start a new OpenCode server process.
|
|
126
133
|
*/
|
|
127
|
-
async function
|
|
134
|
+
async function startOpenCodeServer(sandbox, port, directory, config, customEnv) {
|
|
128
135
|
getLogger().info("Starting OpenCode server", {
|
|
129
136
|
port,
|
|
130
137
|
directory
|
|
@@ -152,8 +159,11 @@ async function startOpencodeServer(sandbox, port, directory, config, customEnv)
|
|
|
152
159
|
}
|
|
153
160
|
}
|
|
154
161
|
if (customEnv) Object.assign(env, customEnv);
|
|
155
|
-
const command =
|
|
156
|
-
const process = await sandbox.
|
|
162
|
+
const command = openCodeCommand(port);
|
|
163
|
+
const process = await createExtensionProcessSandbox(sandbox).exec(command, {
|
|
164
|
+
cwd: directory,
|
|
165
|
+
env: Object.keys(env).length > 0 ? env : void 0
|
|
166
|
+
});
|
|
157
167
|
try {
|
|
158
168
|
await process.waitForPort(port, {
|
|
159
169
|
mode: "http",
|
|
@@ -166,16 +176,16 @@ async function startOpencodeServer(sandbox, port, directory, config, customEnv)
|
|
|
166
176
|
processId: process.id
|
|
167
177
|
});
|
|
168
178
|
} catch (e) {
|
|
169
|
-
const
|
|
179
|
+
const stderr = await collectStderr(process);
|
|
170
180
|
const error = e instanceof Error ? e : void 0;
|
|
171
181
|
getLogger().error("OpenCode server failed to start", error, {
|
|
172
182
|
port,
|
|
173
|
-
stderr
|
|
183
|
+
stderr
|
|
174
184
|
});
|
|
175
|
-
throw new
|
|
185
|
+
throw new OpenCodeStartupError(`OpenCode server failed to start. Stderr: ${stderr || "(empty)"}`, {
|
|
176
186
|
port,
|
|
177
|
-
stderr
|
|
178
|
-
command
|
|
187
|
+
stderr,
|
|
188
|
+
command: command.join(" ")
|
|
179
189
|
}, { cause: e });
|
|
180
190
|
}
|
|
181
191
|
return process;
|
|
@@ -183,7 +193,7 @@ async function startOpencodeServer(sandbox, port, directory, config, customEnv)
|
|
|
183
193
|
/**
|
|
184
194
|
* Starts an OpenCode server inside a Sandbox container.
|
|
185
195
|
*
|
|
186
|
-
* This function manages the server lifecycle only - use `
|
|
196
|
+
* This function manages the server lifecycle only - use `createOpenCodeClient()` if you
|
|
187
197
|
* also need a typed SDK client for programmatic access.
|
|
188
198
|
*
|
|
189
199
|
* If an OpenCode server is already running on the specified port, this function
|
|
@@ -196,10 +206,10 @@ async function startOpencodeServer(sandbox, port, directory, config, customEnv)
|
|
|
196
206
|
* @example
|
|
197
207
|
* ```typescript
|
|
198
208
|
* import { getSandbox } from '@cloudflare/sandbox'
|
|
199
|
-
* import {
|
|
209
|
+
* import { createOpenCodeServer } from '@cloudflare/sandbox/opencode'
|
|
200
210
|
*
|
|
201
211
|
* const sandbox = getSandbox(env.Sandbox, 'my-agent')
|
|
202
|
-
* const server = await
|
|
212
|
+
* const server = await createOpenCodeServer(sandbox, {
|
|
203
213
|
* directory: '/home/user/my-project',
|
|
204
214
|
* config: {
|
|
205
215
|
* provider: {
|
|
@@ -226,148 +236,219 @@ async function startOpencodeServer(sandbox, port, directory, config, customEnv)
|
|
|
226
236
|
* await server.close()
|
|
227
237
|
* ```
|
|
228
238
|
*/
|
|
229
|
-
async function
|
|
230
|
-
const port = options?.port ?? DEFAULT_PORT;
|
|
231
|
-
const process = await
|
|
239
|
+
async function createOpenCodeServer(sandbox, options) {
|
|
240
|
+
const port = options?.port ?? DEFAULT_PORT$1;
|
|
241
|
+
const process = await ensureOpenCodeServer(sandbox, port, options?.directory, options?.config, options?.env);
|
|
232
242
|
return {
|
|
233
243
|
port,
|
|
234
244
|
url: `http://localhost:${port}`,
|
|
235
|
-
close: () =>
|
|
245
|
+
close: async () => {
|
|
246
|
+
await process.kill(15);
|
|
247
|
+
await process.waitForExit().catch(() => {});
|
|
248
|
+
}
|
|
236
249
|
};
|
|
237
250
|
}
|
|
251
|
+
|
|
252
|
+
//#endregion
|
|
253
|
+
//#region ../../extensions/opencode/src/lifecycle.ts
|
|
254
|
+
const DEFAULT_PORT = 4096;
|
|
255
|
+
const STATE_KEY_PREFIX = "opencode:desired-state:";
|
|
256
|
+
/** Recognise the retryable container-unavailable error across RPC. */
|
|
257
|
+
function isContainerUnavailable(error) {
|
|
258
|
+
if (typeof error !== "object" || error === null) return false;
|
|
259
|
+
return error.name === "ContainerUnavailableError";
|
|
260
|
+
}
|
|
238
261
|
/**
|
|
239
|
-
*
|
|
262
|
+
* DO-resident lifecycle handle for an OpenCode server. Owns the durable
|
|
263
|
+
* `opencode serve` process: start/reuse, stop, status, and request proxying.
|
|
240
264
|
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
265
|
+
* It extends {@link SandboxExtension}, so the DO stub exposes it as
|
|
266
|
+
* `sandbox.opencode` and method calls dispatch through `callExtension` — the
|
|
267
|
+
* same path the interpreter extension uses. `createOpenCodeClient` consumes the
|
|
268
|
+
* same handle from either the Worker stub or the in-DO object.
|
|
244
269
|
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
270
|
+
* The server starts lazily: nothing runs until `start()` (or `fetch()`, which
|
|
271
|
+
* calls it) is invoked. To start optimistically, call `opencode.start()` from
|
|
272
|
+
* your Sandbox subclass's `onStart`.
|
|
273
|
+
*/
|
|
274
|
+
var OpenCodeHandle = class extends SandboxExtension {
|
|
275
|
+
#sandbox;
|
|
276
|
+
#defaults;
|
|
277
|
+
#storage;
|
|
278
|
+
#stateKey;
|
|
279
|
+
#server;
|
|
280
|
+
#lastOptions;
|
|
281
|
+
constructor(sandbox, defaults = {}, storage, stateIndex = 0) {
|
|
282
|
+
super(sandbox);
|
|
283
|
+
this.#sandbox = sandbox;
|
|
284
|
+
this.#defaults = defaults;
|
|
285
|
+
this.#storage = storage;
|
|
286
|
+
this.#stateKey = `${STATE_KEY_PREFIX}${stateIndex}`;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Start or reuse the OpenCode server, returning RPC-safe metadata. With no
|
|
290
|
+
* options it reuses the last-used config, recovering persisted desired-state
|
|
291
|
+
* after a cold start. Retries once on a transient `CONTAINER_UNAVAILABLE`
|
|
292
|
+
* (e.g. a rollout in flight).
|
|
293
|
+
*/
|
|
294
|
+
async start(options) {
|
|
295
|
+
const recovered = options ?? this.#lastOptions ?? await this.#loadPersisted();
|
|
296
|
+
const resolved = {
|
|
297
|
+
...this.#defaults,
|
|
298
|
+
...recovered
|
|
299
|
+
};
|
|
300
|
+
this.#lastOptions = resolved;
|
|
301
|
+
await this.#persist(resolved);
|
|
302
|
+
let server;
|
|
303
|
+
try {
|
|
304
|
+
server = await createOpenCodeServer(this.#sandbox, resolved);
|
|
305
|
+
} catch (error) {
|
|
306
|
+
if (!isContainerUnavailable(error)) throw error;
|
|
307
|
+
server = await createOpenCodeServer(this.#sandbox, resolved);
|
|
308
|
+
}
|
|
309
|
+
this.#server = server;
|
|
310
|
+
return {
|
|
311
|
+
port: server.port,
|
|
312
|
+
url: server.url
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/** Stop the running server, if one was started through this handle. */
|
|
316
|
+
async stop() {
|
|
317
|
+
if (!this.#server) return;
|
|
318
|
+
await this.#server.close();
|
|
319
|
+
this.#server = void 0;
|
|
320
|
+
}
|
|
321
|
+
/** Report whether the named OpenCode server is currently running. */
|
|
322
|
+
async status() {
|
|
323
|
+
const resolved = {
|
|
324
|
+
...this.#defaults,
|
|
325
|
+
...this.#lastOptions
|
|
326
|
+
};
|
|
327
|
+
const port = resolved.port ?? DEFAULT_PORT;
|
|
328
|
+
return {
|
|
329
|
+
running: await findExistingOpenCodeProcess(this.#sandbox, port, resolved.directory) !== null,
|
|
330
|
+
port,
|
|
331
|
+
url: `http://localhost:${port}`
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/** Snapshot of the resolved configuration the client builder reads. */
|
|
335
|
+
async config() {
|
|
336
|
+
const resolved = {
|
|
337
|
+
...this.#defaults,
|
|
338
|
+
...this.#lastOptions
|
|
339
|
+
};
|
|
340
|
+
return {
|
|
341
|
+
port: resolved.port ?? DEFAULT_PORT,
|
|
342
|
+
directory: resolved.directory
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Start the server then route a request into the container. This is the
|
|
347
|
+
* transport the SDK client's `fetch` adapter and the Worker proxy use, so it
|
|
348
|
+
* works identically from the Worker stub (one RPC hop) or in-DO (local).
|
|
349
|
+
*/
|
|
350
|
+
async fetch(request) {
|
|
351
|
+
const server = await this.start();
|
|
352
|
+
return this.#sandbox.containerFetch(request, server.port);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Persist resolved desired-state so a cold DO can recover it. Best-effort.
|
|
356
|
+
*
|
|
357
|
+
* Secret-bearing fields (`config`, `env`) are never written to storage. They
|
|
358
|
+
* are sourced fresh from the handle's defaults — which the Sandbox rebuilds
|
|
359
|
+
* from the environment on every construction — so a cold start always uses
|
|
360
|
+
* current credentials rather than a durable copy.
|
|
361
|
+
*/
|
|
362
|
+
async #persist(options) {
|
|
363
|
+
if (!this.#storage) return;
|
|
364
|
+
const { config: _config, env: _env, ...safe } = options;
|
|
365
|
+
await this.#storage.put(this.#stateKey, safe);
|
|
366
|
+
}
|
|
367
|
+
/** Read persisted desired-state, if any, after a DO eviction. */
|
|
368
|
+
async #loadPersisted() {
|
|
369
|
+
if (!this.#storage) return void 0;
|
|
370
|
+
return this.#storage.get(this.#stateKey);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
/**
|
|
374
|
+
* Assigns each handle a stable per-sandbox index so its persisted state key is
|
|
375
|
+
* deterministic across DO reconstruction (field initializers run in the same
|
|
376
|
+
* order each time). Keyed weakly so it is collected with the sandbox.
|
|
377
|
+
*/
|
|
378
|
+
const stateIndexCounter = /* @__PURE__ */ new WeakMap();
|
|
379
|
+
/**
|
|
380
|
+
* Factory — attach as a field on a Sandbox subclass:
|
|
381
|
+
* `opencode = withOpenCode(this, { directory, config, storage: this.ctx.storage })`.
|
|
285
382
|
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
* ```
|
|
383
|
+
* Pass `storage` to persist desired-state so the server is recovered after a DO
|
|
384
|
+
* eviction (cold start). The server starts lazily on first use.
|
|
289
385
|
*/
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
return {
|
|
296
|
-
client: clientFactory({
|
|
297
|
-
baseUrl: server.url,
|
|
298
|
-
fetch: (input, init) => sandbox.containerFetch(new Request(input, init), server.port)
|
|
299
|
-
}),
|
|
300
|
-
server
|
|
301
|
-
};
|
|
386
|
+
function withOpenCode(sandbox, options = {}) {
|
|
387
|
+
const { storage, ...defaults } = options;
|
|
388
|
+
const index = stateIndexCounter.get(sandbox) ?? 0;
|
|
389
|
+
stateIndexCounter.set(sandbox, index + 1);
|
|
390
|
+
return new OpenCodeHandle(sandbox, defaults, storage, index);
|
|
302
391
|
}
|
|
392
|
+
|
|
393
|
+
//#endregion
|
|
394
|
+
//#region ../../extensions/opencode/src/proxy.ts
|
|
303
395
|
/**
|
|
304
|
-
* Proxy a request
|
|
396
|
+
* Proxy a request to the OpenCode web UI through a lifecycle handle.
|
|
305
397
|
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
398
|
+
* For an initial HTML page load that still lacks the `?url=` parameter, returns
|
|
399
|
+
* a redirect that adds it (so the OpenCode frontend calls back through the
|
|
400
|
+
* proxy instead of `127.0.0.1:4096`). Every other request is routed through
|
|
401
|
+
* `handle.fetch`, which ensures the server is running before forwarding into the
|
|
402
|
+
* container.
|
|
309
403
|
*/
|
|
310
|
-
function
|
|
311
|
-
|
|
404
|
+
function proxyToOpenCodeUI(request, handle, options) {
|
|
405
|
+
const url = new URL(request.url);
|
|
406
|
+
if (!url.searchParams.has("url") && request.method === "GET") {
|
|
407
|
+
if ((request.headers.get("accept") || "").includes("text/html") || url.pathname === "/") {
|
|
408
|
+
url.searchParams.set("url", options?.callbackOrigin ?? url.origin);
|
|
409
|
+
return Response.redirect(url.toString(), 302);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return handle.fetch(request);
|
|
312
413
|
}
|
|
313
414
|
/**
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
* This function handles the redirect and proxying only - you must start the
|
|
317
|
-
* server separately using `createOpencodeServer()`.
|
|
415
|
+
* Curried Worker fetch wrapper for OpenCode.
|
|
318
416
|
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
* 2. Proxying the request to the container
|
|
417
|
+
* `createOpenCodeProxy(resolve, options?)` captures a lazy per-request resolver
|
|
418
|
+
* for the OpenCode lifecycle handle (`sandbox.opencode`) and returns a function
|
|
419
|
+
* that wraps the user's worker entrypoint.
|
|
323
420
|
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
421
|
+
* The wrapped handler runs first. If it returns a 404 (or has no `fetch`), the
|
|
422
|
+
* request falls through to the OpenCode web-UI proxy — the redirect handshake
|
|
423
|
+
* for HTML loads and an ensure-then-forward into the container for everything
|
|
424
|
+
* else. A 404 from the user handler is the "not mine, proxy it" signal, so the
|
|
425
|
+
* handler only needs to own its own routes and `return new Response('Not
|
|
426
|
+
* found', { status: 404 })` for the rest.
|
|
328
427
|
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
* const sandbox = getSandbox(env.Sandbox, 'opencode')
|
|
337
|
-
* const server = await createOpencodeServer(sandbox, {
|
|
338
|
-
* directory: '/home/user/project',
|
|
339
|
-
* config: {
|
|
340
|
-
* provider: {
|
|
341
|
-
* anthropic: {
|
|
342
|
-
* options: { apiKey: env.ANTHROPIC_KEY }
|
|
343
|
-
* },
|
|
344
|
-
* // Optional: Route all providers through Cloudflare AI Gateway
|
|
345
|
-
* 'cloudflare-ai-gateway': {
|
|
346
|
-
* options: {
|
|
347
|
-
* accountId: env.CF_ACCOUNT_ID,
|
|
348
|
-
* gatewayId: env.CF_GATEWAY_ID,
|
|
349
|
-
* apiToken: env.CF_API_TOKEN
|
|
350
|
-
* }
|
|
351
|
-
* }
|
|
352
|
-
* }
|
|
353
|
-
* }
|
|
354
|
-
* })
|
|
355
|
-
* return proxyToOpencode(request, sandbox, server)
|
|
428
|
+
* ```ts
|
|
429
|
+
* export default createOpenCodeProxy(
|
|
430
|
+
* (env) => getSandbox(env.Sandbox, 'my-sandbox').opencode
|
|
431
|
+
* )({
|
|
432
|
+
* async fetch(request, env) {
|
|
433
|
+
* // handle your own routes, else:
|
|
434
|
+
* return new Response('Not found', { status: 404 });
|
|
356
435
|
* }
|
|
357
|
-
* }
|
|
436
|
+
* });
|
|
358
437
|
* ```
|
|
359
438
|
*/
|
|
360
|
-
function
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
439
|
+
function createOpenCodeProxy(resolve, options) {
|
|
440
|
+
return (handler) => ({
|
|
441
|
+
...handler,
|
|
442
|
+
async fetch(request, env, ctx) {
|
|
443
|
+
if (handler.fetch) {
|
|
444
|
+
const response = await handler.fetch(request, env, ctx);
|
|
445
|
+
if (response.status !== 404) return response;
|
|
446
|
+
}
|
|
447
|
+
return proxyToOpenCodeUI(request, resolve(env), options);
|
|
366
448
|
}
|
|
367
|
-
}
|
|
368
|
-
return proxyToOpencodeServer(request, sandbox, server);
|
|
449
|
+
});
|
|
369
450
|
}
|
|
370
451
|
|
|
371
452
|
//#endregion
|
|
372
|
-
export {
|
|
453
|
+
export { OpenCodeHandle, OpenCodeStartupError, createOpenCodeClient, createOpenCodeProxy, proxyToOpenCodeUI, withOpenCode };
|
|
373
454
|
//# sourceMappingURL=index.js.map
|