@cloudflare/sandbox 0.13.0-next.681.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 +6 -6
- package/README.md +49 -3
- package/dist/bridge/index.d.ts.map +1 -1
- package/dist/bridge/index.js +1364 -1072
- package/dist/bridge/index.js.map +1 -1
- package/dist/{contexts-DY1LHU1v.d.ts → contexts-1EsLHByO.d.ts} +156 -20
- package/dist/contexts-1EsLHByO.d.ts.map +1 -0
- package/dist/{dist-BStBkGIC.js → dist-Duor5GbS.js} +7 -56
- package/dist/dist-Duor5GbS.js.map +1 -0
- package/dist/errors/index.d.ts +4 -4
- package/dist/errors/index.js +3 -3
- package/dist/{errors-k3B8orjH.js → errors-CXR0xBpw.js} +42 -10
- package/dist/errors-CXR0xBpw.js.map +1 -0
- package/dist/{errors-ewgSNicb.js → errors-QYlSkVGz.js} +402 -250
- package/dist/errors-QYlSkVGz.js.map +1 -0
- package/dist/extensions/index.d.ts +4 -2
- package/dist/extensions/index.js +5 -3
- 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 +8 -19
- package/dist/git/index.d.ts.map +1 -1
- package/dist/git/index.js +16 -23
- package/dist/git/index.js.map +1 -1
- package/dist/index-Bs4bqXDR.d.ts +438 -0
- package/dist/index-Bs4bqXDR.d.ts.map +1 -0
- package/dist/{index-Dy6u9r60.d.ts → index-HNYBk-az.d.ts} +233 -179
- package/dist/index-HNYBk-az.d.ts.map +1 -0
- package/dist/index.d.ts +480 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/interpreter/index.d.ts +3 -1
- package/dist/interpreter/index.d.ts.map +1 -1
- package/dist/interpreter/index.js +46 -18
- package/dist/interpreter/index.js.map +1 -1
- package/dist/openai/index.d.ts +5 -5
- 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 +7 -11
- package/dist/opencode/index.d.ts.map +1 -1
- package/dist/opencode/index.js +56 -85
- 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-boKWPIcd.js → sandbox-Auuwfnur.js} +4152 -3359
- 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 +5 -1
- package/dist/xterm/index.d.ts.map +1 -1
- package/dist/xterm/index.js +55 -12
- package/dist/xterm/index.js.map +1 -1
- package/package.json +3 -2
- package/dist/codes-y-U76vnQ.d.ts +0 -79
- package/dist/codes-y-U76vnQ.d.ts.map +0 -1
- package/dist/contexts-DY1LHU1v.d.ts.map +0 -1
- package/dist/dist-BStBkGIC.js.map +0 -1
- package/dist/errors-ewgSNicb.js.map +0 -1
- package/dist/errors-k3B8orjH.js.map +0 -1
- package/dist/extensions-CepYdzro.js +0 -191
- package/dist/extensions-CepYdzro.js.map +0 -1
- package/dist/index-B7QgIs0N.d.ts +0 -1841
- package/dist/index-B7QgIs0N.d.ts.map +0 -1
- package/dist/index-Dy6u9r60.d.ts.map +0 -1
- package/dist/sandbox-DHNO89IF.d.ts +0 -828
- package/dist/sandbox-DHNO89IF.d.ts.map +0 -1
- package/dist/sandbox-boKWPIcd.js.map +0 -1
package/dist/openai/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["sandbox: Sandbox","output: ShellResult['output']","exitCode: number | null","outcome: ShellOutputResult['outcome']","error: unknown","root: string","result: FileOperationResult","original: string","stack: string[]"],"sources":["../../src/openai/index.ts"],"sourcesContent":["/**\n * OpenAI Agents adapters for executing shell commands and file operations\n * inside a Cloudflare Sandbox.\n */\nimport {\n type ApplyPatchOperation,\n type ApplyPatchResult,\n applyDiff,\n type Editor as OpenAIEeditor,\n type Shell as OpenAIShell,\n type ShellAction,\n type ShellOutputResult,\n type ShellResult\n} from '@openai/agents';\n\n// Command result for API responses\nexport interface CommandResult {\n command: string;\n stdout: string;\n stderr: string;\n exitCode: number | null;\n timestamp: number;\n}\n\n// File operation result for API responses\nexport interface FileOperationResult {\n operation: 'create' | 'update' | 'delete';\n path: string;\n status: 'completed' | 'failed';\n output: string;\n error?: string;\n timestamp: number;\n}\n\nimport { createLogger, type Logger } from '@repo/shared';\nimport type { Sandbox } from '../sandbox';\n\n// Helper functions for error handling\nfunction isErrorWithProperties(error: unknown): error is {\n message?: string;\n exitCode?: number;\n stdout?: string;\n stderr?: string;\n status?: number;\n stack?: string;\n} {\n return typeof error === 'object' && error !== null;\n}\n\nfunction getErrorMessage(error: unknown): string {\n if (isErrorWithProperties(error) && typeof error.message === 'string') {\n return error.message;\n }\n return String(error);\n}\n\n/**\n * Convert unknown values to Error instances when possible so downstream\n * loggers can include stack traces without losing type safety.\n */\nfunction toError(error: unknown): Error | undefined {\n return error instanceof Error ? error : undefined;\n}\n\n/**\n * Shell implementation that adapts Cloudflare Sandbox exec calls to the\n * OpenAI Agents `Shell` contract, including structured result collection.\n */\nexport class Shell implements OpenAIShell {\n private cwd: string = '/workspace';\n public results: CommandResult[] = [];\n private readonly logger: Logger;\n\n constructor(private readonly sandbox: Sandbox) {\n this.logger = createLogger({\n component: 'sandbox-do',\n operation: 'openai-shell'\n });\n }\n\n async run(action: ShellAction): Promise<ShellResult> {\n this.logger.debug('SandboxShell.run called', {\n commands: action.commands,\n timeout: action.timeoutMs\n });\n const output: ShellResult['output'] = [];\n\n for (const command of action.commands) {\n this.logger.debug('Executing command', { command, cwd: this.cwd });\n let stdout = '';\n let stderr = '';\n let exitCode: number | null = 0;\n let outcome: ShellOutputResult['outcome'] = {\n type: 'exit',\n exitCode: 0\n };\n try {\n const result = await this.sandbox\n .exec(command, {\n timeout: action.timeoutMs,\n cwd: this.cwd\n })\n .output({ encoding: 'utf8' });\n stdout = result.stdout;\n stderr = result.stderr;\n exitCode = result.exitCode;\n // exec returns a result even for failed commands, so check success field\n // Timeout would be indicated by a specific error or exit code\n outcome = { type: 'exit', exitCode };\n\n this.logger.debug('Command executed successfully', {\n command,\n exitCode,\n stdoutLength: stdout.length,\n stderrLength: stderr.length\n });\n\n // Log warnings for non-zero exit codes or stderr output\n if (exitCode !== 0) {\n this.logger.warn(`Command failed with exit code ${exitCode}`, {\n command,\n stderr\n });\n } else if (stderr) {\n this.logger.warn(`Command produced stderr output`, {\n command,\n stderr\n });\n } else {\n this.logger.info(`Command completed successfully`, { command });\n }\n } catch (error: unknown) {\n // Handle network/HTTP errors or timeout errors\n const errorObj = isErrorWithProperties(error) ? error : {};\n exitCode =\n typeof errorObj.exitCode === 'number' ? errorObj.exitCode : null;\n stdout = typeof errorObj.stdout === 'string' ? errorObj.stdout : '';\n stderr = typeof errorObj.stderr === 'string' ? errorObj.stderr : '';\n\n // Check if it's a timeout error\n const errorMessage = getErrorMessage(error);\n if (\n errorMessage.includes('timeout') ||\n errorMessage.includes('Timeout') ||\n errorMessage.includes('timed out')\n ) {\n this.logger.error(`Command timed out`, undefined, {\n command,\n timeout: action.timeoutMs\n });\n outcome = { type: 'timeout' };\n } else {\n this.logger.error(`Error executing command`, toError(error), {\n command,\n error: errorMessage || error,\n exitCode\n });\n outcome = { type: 'exit', exitCode: exitCode ?? 1 };\n }\n }\n output.push({\n command,\n stdout,\n stderr,\n outcome\n });\n\n // Collect results for API responses\n const collectedExitCode =\n outcome.type === 'exit' ? outcome.exitCode : null;\n const timestamp = Date.now();\n this.results.push({\n command: String(command),\n stdout: String(stdout),\n stderr: String(stderr),\n exitCode: collectedExitCode,\n timestamp\n });\n this.logger.debug('Result collected', {\n command,\n exitCode: collectedExitCode,\n timestamp\n });\n\n if (outcome.type === 'timeout') {\n this.logger.warn('Breaking command loop due to timeout');\n break;\n }\n }\n\n this.logger.debug('SandboxShell.run completed', {\n totalCommands: action.commands.length,\n resultsCount: this.results.length\n });\n return {\n output,\n providerData: {\n working_directory: this.cwd\n }\n };\n }\n}\n\n/**\n * Editor implementation that projects applyPatch operations from Agents\n * into calls against the sandbox filesystem APIs.\n */\nexport class Editor implements OpenAIEeditor {\n public results: FileOperationResult[] = [];\n private readonly logger: Logger;\n\n constructor(\n private readonly sandbox: Sandbox,\n private readonly root: string = '/workspace'\n ) {\n this.logger = createLogger({\n component: 'sandbox-do',\n operation: 'openai-editor'\n });\n }\n\n /**\n * Create a new file inside the sandbox by applying the provided diff.\n */\n async createFile(\n operation: Extract<ApplyPatchOperation, { type: 'create_file' }>\n ): Promise<ApplyPatchResult | undefined> {\n const targetPath = this.resolve(operation.path);\n this.logger.debug('WorkspaceEditor.createFile called', {\n path: operation.path,\n targetPath\n });\n\n try {\n // Create parent directory if needed\n const dirPath = this.getDirname(targetPath);\n if (dirPath !== this.root && dirPath !== '/') {\n this.logger.debug('Creating parent directory', { dirPath });\n await this.sandbox.mkdir(dirPath, { recursive: true });\n }\n\n const content = applyDiff('', operation.diff, 'create');\n this.logger.debug('Writing file content', {\n path: targetPath,\n contentLength: content.length\n });\n await this.sandbox.writeFile(targetPath, content, { encoding: 'utf-8' });\n const timestamp = Date.now();\n const result: FileOperationResult = {\n operation: 'create',\n path: operation.path,\n status: 'completed',\n output: `Created ${operation.path}`,\n timestamp\n };\n this.results.push(result);\n this.logger.info('File created successfully', {\n path: operation.path,\n timestamp\n });\n return { status: 'completed', output: `Created ${operation.path}` };\n } catch (error: unknown) {\n const timestamp = Date.now();\n const errorMessage = getErrorMessage(error);\n const result: FileOperationResult = {\n operation: 'create',\n path: operation.path,\n status: 'failed',\n output: `Failed to create ${operation.path}`,\n error: errorMessage,\n timestamp\n };\n this.results.push(result);\n this.logger.error('Failed to create file', toError(error), {\n path: operation.path,\n error: errorMessage\n });\n throw error;\n }\n }\n\n /**\n * Update an existing file by reading its content, applying a diff, and\n * writing the patched output back to the sandbox.\n */\n async updateFile(\n operation: Extract<ApplyPatchOperation, { type: 'update_file' }>\n ): Promise<ApplyPatchResult | undefined> {\n const targetPath = this.resolve(operation.path);\n this.logger.debug('WorkspaceEditor.updateFile called', {\n path: operation.path,\n targetPath\n });\n\n try {\n let original: string;\n try {\n this.logger.debug('Reading original file', { path: targetPath });\n const fileInfo = await this.sandbox.readFile(targetPath, {\n encoding: 'utf-8'\n });\n original = fileInfo.content;\n this.logger.debug('Original file read', {\n path: targetPath,\n originalLength: original.length\n });\n } catch (error: unknown) {\n // Sandbox API may throw errors for missing files\n const errorObj = isErrorWithProperties(error) ? error : {};\n const errorMessage = getErrorMessage(error);\n if (\n errorMessage.includes('not found') ||\n errorMessage.includes('ENOENT') ||\n errorObj.status === 404\n ) {\n this.logger.error('Cannot update missing file', undefined, {\n path: operation.path\n });\n throw new Error(`Cannot update missing file: ${operation.path}`);\n }\n this.logger.error('Error reading file', toError(error), {\n path: operation.path,\n error: errorMessage\n });\n throw error;\n }\n\n const patched = applyDiff(original, operation.diff);\n this.logger.debug('Applied diff', {\n path: targetPath,\n originalLength: original.length,\n patchedLength: patched.length\n });\n await this.sandbox.writeFile(targetPath, patched, { encoding: 'utf-8' });\n const timestamp = Date.now();\n const result: FileOperationResult = {\n operation: 'update',\n path: operation.path,\n status: 'completed',\n output: `Updated ${operation.path}`,\n timestamp\n };\n this.results.push(result);\n this.logger.info('File updated successfully', {\n path: operation.path,\n timestamp\n });\n return { status: 'completed', output: `Updated ${operation.path}` };\n } catch (error: unknown) {\n const timestamp = Date.now();\n const errorMessage = getErrorMessage(error);\n const result: FileOperationResult = {\n operation: 'update',\n path: operation.path,\n status: 'failed',\n output: `Failed to update ${operation.path}`,\n error: errorMessage,\n timestamp\n };\n this.results.push(result);\n this.logger.error('Failed to update file', toError(error), {\n path: operation.path,\n error: errorMessage\n });\n throw error;\n }\n }\n\n /**\n * Delete a file that was previously created through applyPatch calls.\n */\n async deleteFile(\n operation: Extract<ApplyPatchOperation, { type: 'delete_file' }>\n ): Promise<ApplyPatchResult | undefined> {\n const targetPath = this.resolve(operation.path);\n this.logger.debug('WorkspaceEditor.deleteFile called', {\n path: operation.path,\n targetPath\n });\n\n try {\n await this.sandbox.deleteFile(targetPath);\n const timestamp = Date.now();\n const result: FileOperationResult = {\n operation: 'delete',\n path: operation.path,\n status: 'completed',\n output: `Deleted ${operation.path}`,\n timestamp\n };\n this.results.push(result);\n this.logger.info('File deleted successfully', {\n path: operation.path,\n timestamp\n });\n return { status: 'completed', output: `Deleted ${operation.path}` };\n } catch (error: unknown) {\n const timestamp = Date.now();\n const errorMessage = getErrorMessage(error);\n const result: FileOperationResult = {\n operation: 'delete',\n path: operation.path,\n status: 'failed',\n output: `Failed to delete ${operation.path}`,\n error: errorMessage,\n timestamp\n };\n this.results.push(result);\n this.logger.error('Failed to delete file', toError(error), {\n path: operation.path,\n error: errorMessage\n });\n throw error;\n }\n }\n\n private resolve(relativePath: string): string {\n // If the path already starts with the root, strip it to get the relative part\n let pathToProcess = relativePath;\n if (relativePath.startsWith(this.root)) {\n pathToProcess = relativePath.slice(this.root.length);\n // Remove leading slash if present after stripping root\n pathToProcess = pathToProcess.replace(/^\\//, '');\n }\n\n // Remove leading ./ or / if present, then join with root\n const normalized = pathToProcess.replace(/^\\.\\//, '').replace(/^\\//, '');\n const resolved = normalized ? `${this.root}/${normalized}` : this.root;\n\n // Normalize path separators first\n const pathWithNormalizedSeparators = resolved.replace(/\\/+/g, '/');\n\n // Normalize .. segments by processing path segments\n const segments = pathWithNormalizedSeparators\n .split('/')\n .filter((s) => s && s !== '.');\n const stack: string[] = [];\n\n for (const segment of segments) {\n if (segment === '..') {\n if (stack.length === 0) {\n throw new Error(`Operation outside workspace: ${relativePath}`);\n }\n stack.pop();\n } else {\n stack.push(segment);\n }\n }\n\n const normalizedPath = `/${stack.join('/')}`;\n\n // Ensure the resolved path is within the workspace\n if (!normalizedPath.startsWith(this.root)) {\n throw new Error(`Operation outside workspace: ${relativePath}`);\n }\n\n return normalizedPath;\n }\n\n private getDirname(filePath: string): string {\n const lastSlash = filePath.lastIndexOf('/');\n if (lastSlash === -1) {\n return '/';\n }\n return filePath.substring(0, lastSlash) || '/';\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["sandbox: ISandbox","output: ShellResult['output']","exitCode: number | null","outcome: ShellOutputResult['outcome']","error: unknown","root: string","result: FileOperationResult","original: string","stack: string[]"],"sources":["../../src/openai/index.ts"],"sourcesContent":["/**\n * OpenAI Agents adapters for executing shell commands and file operations\n * inside a Cloudflare Sandbox.\n */\nimport {\n type ApplyPatchOperation,\n type ApplyPatchResult,\n applyDiff,\n type Editor as OpenAIEeditor,\n type Shell as OpenAIShell,\n type ShellAction,\n type ShellOutputResult,\n type ShellResult\n} from '@openai/agents';\n\n// Command result for API responses\nexport interface CommandResult {\n command: string;\n stdout: string;\n stderr: string;\n exitCode: number | null;\n timestamp: number;\n}\n\n// File operation result for API responses\nexport interface FileOperationResult {\n operation: 'create' | 'update' | 'delete';\n path: string;\n status: 'completed' | 'failed';\n output: string;\n error?: string;\n timestamp: number;\n}\n\nimport { createLogger, type ISandbox, type Logger } from '@repo/shared';\n\n// Helper functions for error handling\nfunction isErrorWithProperties(error: unknown): error is {\n message?: string;\n exitCode?: number;\n stdout?: string;\n stderr?: string;\n status?: number;\n stack?: string;\n} {\n return typeof error === 'object' && error !== null;\n}\n\nfunction getErrorMessage(error: unknown): string {\n if (isErrorWithProperties(error) && typeof error.message === 'string') {\n return error.message;\n }\n return String(error);\n}\n\n/**\n * Convert unknown values to Error instances when possible so downstream\n * loggers can include stack traces without losing type safety.\n */\nfunction toError(error: unknown): Error | undefined {\n return error instanceof Error ? error : undefined;\n}\n\n/**\n * Shell implementation that adapts Cloudflare Sandbox exec calls to the\n * OpenAI Agents `Shell` contract, including structured result collection.\n */\nexport class Shell implements OpenAIShell {\n private cwd: string = '/workspace';\n public results: CommandResult[] = [];\n private readonly logger: Logger;\n\n constructor(private readonly sandbox: ISandbox) {\n this.logger = createLogger({\n component: 'sandbox-do',\n operation: 'openai-shell'\n });\n }\n\n async run(action: ShellAction): Promise<ShellResult> {\n this.logger.debug('SandboxShell.run called', {\n commands: action.commands,\n timeout: action.timeoutMs\n });\n const output: ShellResult['output'] = [];\n\n for (const command of action.commands) {\n this.logger.debug('Executing command', { command, cwd: this.cwd });\n let stdout = '';\n let stderr = '';\n let exitCode: number | null = 0;\n let outcome: ShellOutputResult['outcome'] = {\n type: 'exit',\n exitCode: 0\n };\n try {\n const proc = await this.sandbox.exec(['/bin/bash', '-lc', command], {\n timeout: action.timeoutMs,\n cwd: this.cwd\n });\n const out = await proc.output();\n stdout = new TextDecoder().decode(out.stdout);\n stderr = new TextDecoder().decode(out.stderr);\n exitCode = out.exitCode;\n // Timeout would be indicated by a specific error or exit code\n outcome = { type: 'exit', exitCode };\n\n this.logger.debug('Command executed successfully', {\n command,\n exitCode,\n stdoutLength: stdout.length,\n stderrLength: stderr.length\n });\n\n // Log warnings for non-zero exit codes or stderr output\n if (exitCode !== 0) {\n this.logger.warn(`Command failed with exit code ${exitCode}`, {\n command,\n stderr\n });\n } else if (stderr) {\n this.logger.warn(`Command produced stderr output`, {\n command,\n stderr\n });\n } else {\n this.logger.info(`Command completed successfully`, { command });\n }\n } catch (error: unknown) {\n // Handle network/HTTP errors or timeout errors\n const errorObj = isErrorWithProperties(error) ? error : {};\n exitCode =\n typeof errorObj.exitCode === 'number' ? errorObj.exitCode : null;\n stdout = typeof errorObj.stdout === 'string' ? errorObj.stdout : '';\n stderr = typeof errorObj.stderr === 'string' ? errorObj.stderr : '';\n\n // Check if it's a timeout error\n const errorMessage = getErrorMessage(error);\n if (\n errorMessage.includes('timeout') ||\n errorMessage.includes('Timeout') ||\n errorMessage.includes('timed out')\n ) {\n this.logger.error(`Command timed out`, undefined, {\n command,\n timeout: action.timeoutMs\n });\n outcome = { type: 'timeout' };\n } else {\n this.logger.error(`Error executing command`, toError(error), {\n command,\n error: errorMessage || error,\n exitCode\n });\n outcome = { type: 'exit', exitCode: exitCode ?? 1 };\n }\n }\n output.push({\n command,\n stdout,\n stderr,\n outcome\n });\n\n // Collect results for API responses\n const collectedExitCode =\n outcome.type === 'exit' ? outcome.exitCode : null;\n const timestamp = Date.now();\n this.results.push({\n command: String(command),\n stdout: String(stdout),\n stderr: String(stderr),\n exitCode: collectedExitCode,\n timestamp\n });\n this.logger.debug('Result collected', {\n command,\n exitCode: collectedExitCode,\n timestamp\n });\n\n if (outcome.type === 'timeout') {\n this.logger.warn('Breaking command loop due to timeout');\n break;\n }\n }\n\n this.logger.debug('SandboxShell.run completed', {\n totalCommands: action.commands.length,\n resultsCount: this.results.length\n });\n return {\n output,\n providerData: {\n working_directory: this.cwd\n }\n };\n }\n}\n\n/**\n * Editor implementation that projects applyPatch operations from Agents\n * into calls against the sandbox filesystem APIs.\n */\nexport class Editor implements OpenAIEeditor {\n public results: FileOperationResult[] = [];\n private readonly logger: Logger;\n\n constructor(\n private readonly sandbox: ISandbox,\n private readonly root: string = '/workspace'\n ) {\n this.logger = createLogger({\n component: 'sandbox-do',\n operation: 'openai-editor'\n });\n }\n\n /**\n * Create a new file inside the sandbox by applying the provided diff.\n */\n async createFile(\n operation: Extract<ApplyPatchOperation, { type: 'create_file' }>\n ): Promise<ApplyPatchResult | undefined> {\n const targetPath = this.resolve(operation.path);\n this.logger.debug('WorkspaceEditor.createFile called', {\n path: operation.path,\n targetPath\n });\n\n try {\n // Create parent directory if needed\n const dirPath = this.getDirname(targetPath);\n if (dirPath !== this.root && dirPath !== '/') {\n this.logger.debug('Creating parent directory', { dirPath });\n await this.sandbox.mkdir(dirPath, { recursive: true });\n }\n\n const content = applyDiff('', operation.diff, 'create');\n this.logger.debug('Writing file content', {\n path: targetPath,\n contentLength: content.length\n });\n await this.sandbox.writeFile(targetPath, content, { encoding: 'utf-8' });\n const timestamp = Date.now();\n const result: FileOperationResult = {\n operation: 'create',\n path: operation.path,\n status: 'completed',\n output: `Created ${operation.path}`,\n timestamp\n };\n this.results.push(result);\n this.logger.info('File created successfully', {\n path: operation.path,\n timestamp\n });\n return { status: 'completed', output: `Created ${operation.path}` };\n } catch (error: unknown) {\n const timestamp = Date.now();\n const errorMessage = getErrorMessage(error);\n const result: FileOperationResult = {\n operation: 'create',\n path: operation.path,\n status: 'failed',\n output: `Failed to create ${operation.path}`,\n error: errorMessage,\n timestamp\n };\n this.results.push(result);\n this.logger.error('Failed to create file', toError(error), {\n path: operation.path,\n error: errorMessage\n });\n throw error;\n }\n }\n\n /**\n * Update an existing file by reading its content, applying a diff, and\n * writing the patched output back to the sandbox.\n */\n async updateFile(\n operation: Extract<ApplyPatchOperation, { type: 'update_file' }>\n ): Promise<ApplyPatchResult | undefined> {\n const targetPath = this.resolve(operation.path);\n this.logger.debug('WorkspaceEditor.updateFile called', {\n path: operation.path,\n targetPath\n });\n\n try {\n let original: string;\n try {\n this.logger.debug('Reading original file', { path: targetPath });\n const fileInfo = await this.sandbox.readFile(targetPath, {\n encoding: 'utf-8'\n });\n original = fileInfo.content;\n this.logger.debug('Original file read', {\n path: targetPath,\n originalLength: original.length\n });\n } catch (error: unknown) {\n // Sandbox API may throw errors for missing files\n const errorObj = isErrorWithProperties(error) ? error : {};\n const errorMessage = getErrorMessage(error);\n if (\n errorMessage.includes('not found') ||\n errorMessage.includes('ENOENT') ||\n errorObj.status === 404\n ) {\n this.logger.error('Cannot update missing file', undefined, {\n path: operation.path\n });\n throw new Error(`Cannot update missing file: ${operation.path}`);\n }\n this.logger.error('Error reading file', toError(error), {\n path: operation.path,\n error: errorMessage\n });\n throw error;\n }\n\n const patched = applyDiff(original, operation.diff);\n this.logger.debug('Applied diff', {\n path: targetPath,\n originalLength: original.length,\n patchedLength: patched.length\n });\n await this.sandbox.writeFile(targetPath, patched, { encoding: 'utf-8' });\n const timestamp = Date.now();\n const result: FileOperationResult = {\n operation: 'update',\n path: operation.path,\n status: 'completed',\n output: `Updated ${operation.path}`,\n timestamp\n };\n this.results.push(result);\n this.logger.info('File updated successfully', {\n path: operation.path,\n timestamp\n });\n return { status: 'completed', output: `Updated ${operation.path}` };\n } catch (error: unknown) {\n const timestamp = Date.now();\n const errorMessage = getErrorMessage(error);\n const result: FileOperationResult = {\n operation: 'update',\n path: operation.path,\n status: 'failed',\n output: `Failed to update ${operation.path}`,\n error: errorMessage,\n timestamp\n };\n this.results.push(result);\n this.logger.error('Failed to update file', toError(error), {\n path: operation.path,\n error: errorMessage\n });\n throw error;\n }\n }\n\n /**\n * Delete a file that was previously created through applyPatch calls.\n */\n async deleteFile(\n operation: Extract<ApplyPatchOperation, { type: 'delete_file' }>\n ): Promise<ApplyPatchResult | undefined> {\n const targetPath = this.resolve(operation.path);\n this.logger.debug('WorkspaceEditor.deleteFile called', {\n path: operation.path,\n targetPath\n });\n\n try {\n await this.sandbox.deleteFile(targetPath);\n const timestamp = Date.now();\n const result: FileOperationResult = {\n operation: 'delete',\n path: operation.path,\n status: 'completed',\n output: `Deleted ${operation.path}`,\n timestamp\n };\n this.results.push(result);\n this.logger.info('File deleted successfully', {\n path: operation.path,\n timestamp\n });\n return { status: 'completed', output: `Deleted ${operation.path}` };\n } catch (error: unknown) {\n const timestamp = Date.now();\n const errorMessage = getErrorMessage(error);\n const result: FileOperationResult = {\n operation: 'delete',\n path: operation.path,\n status: 'failed',\n output: `Failed to delete ${operation.path}`,\n error: errorMessage,\n timestamp\n };\n this.results.push(result);\n this.logger.error('Failed to delete file', toError(error), {\n path: operation.path,\n error: errorMessage\n });\n throw error;\n }\n }\n\n private resolve(relativePath: string): string {\n // If the path already starts with the root, strip it to get the relative part\n let pathToProcess = relativePath;\n if (relativePath.startsWith(this.root)) {\n pathToProcess = relativePath.slice(this.root.length);\n // Remove leading slash if present after stripping root\n pathToProcess = pathToProcess.replace(/^\\//, '');\n }\n\n // Remove leading ./ or / if present, then join with root\n const normalized = pathToProcess.replace(/^\\.\\//, '').replace(/^\\//, '');\n const resolved = normalized ? `${this.root}/${normalized}` : this.root;\n\n // Normalize path separators first\n const pathWithNormalizedSeparators = resolved.replace(/\\/+/g, '/');\n\n // Normalize .. segments by processing path segments\n const segments = pathWithNormalizedSeparators\n .split('/')\n .filter((s) => s && s !== '.');\n const stack: string[] = [];\n\n for (const segment of segments) {\n if (segment === '..') {\n if (stack.length === 0) {\n throw new Error(`Operation outside workspace: ${relativePath}`);\n }\n stack.pop();\n } else {\n stack.push(segment);\n }\n }\n\n const normalizedPath = `/${stack.join('/')}`;\n\n // Ensure the resolved path is within the workspace\n if (!normalizedPath.startsWith(this.root)) {\n throw new Error(`Operation outside workspace: ${relativePath}`);\n }\n\n return normalizedPath;\n }\n\n private getDirname(filePath: string): string {\n const lastSlash = filePath.lastIndexOf('/');\n if (lastSlash === -1) {\n return '/';\n }\n return filePath.substring(0, lastSlash) || '/';\n }\n}\n"],"mappings":";;;;;;;;;AAqCA,SAAS,sBAAsB,OAO7B;AACA,QAAO,OAAO,UAAU,YAAY,UAAU;;AAGhD,SAAS,gBAAgB,OAAwB;AAC/C,KAAI,sBAAsB,MAAM,IAAI,OAAO,MAAM,YAAY,SAC3D,QAAO,MAAM;AAEf,QAAO,OAAO,MAAM;;;;;;AAOtB,SAAS,QAAQ,OAAmC;AAClD,QAAO,iBAAiB,QAAQ,QAAQ;;;;;;AAO1C,IAAa,QAAb,MAA0C;CACxC,AAAQ,MAAc;CACtB,AAAO,UAA2B,EAAE;CACpC,AAAiB;CAEjB,YAAY,AAAiBA,SAAmB;EAAnB;AAC3B,OAAK,SAAS,aAAa;GACzB,WAAW;GACX,WAAW;GACZ,CAAC;;CAGJ,MAAM,IAAI,QAA2C;AACnD,OAAK,OAAO,MAAM,2BAA2B;GAC3C,UAAU,OAAO;GACjB,SAAS,OAAO;GACjB,CAAC;EACF,MAAMC,SAAgC,EAAE;AAExC,OAAK,MAAM,WAAW,OAAO,UAAU;AACrC,QAAK,OAAO,MAAM,qBAAqB;IAAE;IAAS,KAAK,KAAK;IAAK,CAAC;GAClE,IAAI,SAAS;GACb,IAAI,SAAS;GACb,IAAIC,WAA0B;GAC9B,IAAIC,UAAwC;IAC1C,MAAM;IACN,UAAU;IACX;AACD,OAAI;IAKF,MAAM,MAAM,OAJC,MAAM,KAAK,QAAQ,KAAK;KAAC;KAAa;KAAO;KAAQ,EAAE;KAClE,SAAS,OAAO;KAChB,KAAK,KAAK;KACX,CAAC,EACqB,QAAQ;AAC/B,aAAS,IAAI,aAAa,CAAC,OAAO,IAAI,OAAO;AAC7C,aAAS,IAAI,aAAa,CAAC,OAAO,IAAI,OAAO;AAC7C,eAAW,IAAI;AAEf,cAAU;KAAE,MAAM;KAAQ;KAAU;AAEpC,SAAK,OAAO,MAAM,iCAAiC;KACjD;KACA;KACA,cAAc,OAAO;KACrB,cAAc,OAAO;KACtB,CAAC;AAGF,QAAI,aAAa,EACf,MAAK,OAAO,KAAK,iCAAiC,YAAY;KAC5D;KACA;KACD,CAAC;aACO,OACT,MAAK,OAAO,KAAK,kCAAkC;KACjD;KACA;KACD,CAAC;QAEF,MAAK,OAAO,KAAK,kCAAkC,EAAE,SAAS,CAAC;YAE1DC,OAAgB;IAEvB,MAAM,WAAW,sBAAsB,MAAM,GAAG,QAAQ,EAAE;AAC1D,eACE,OAAO,SAAS,aAAa,WAAW,SAAS,WAAW;AAC9D,aAAS,OAAO,SAAS,WAAW,WAAW,SAAS,SAAS;AACjE,aAAS,OAAO,SAAS,WAAW,WAAW,SAAS,SAAS;IAGjE,MAAM,eAAe,gBAAgB,MAAM;AAC3C,QACE,aAAa,SAAS,UAAU,IAChC,aAAa,SAAS,UAAU,IAChC,aAAa,SAAS,YAAY,EAClC;AACA,UAAK,OAAO,MAAM,qBAAqB,QAAW;MAChD;MACA,SAAS,OAAO;MACjB,CAAC;AACF,eAAU,EAAE,MAAM,WAAW;WACxB;AACL,UAAK,OAAO,MAAM,2BAA2B,QAAQ,MAAM,EAAE;MAC3D;MACA,OAAO,gBAAgB;MACvB;MACD,CAAC;AACF,eAAU;MAAE,MAAM;MAAQ,UAAU,YAAY;MAAG;;;AAGvD,UAAO,KAAK;IACV;IACA;IACA;IACA;IACD,CAAC;GAGF,MAAM,oBACJ,QAAQ,SAAS,SAAS,QAAQ,WAAW;GAC/C,MAAM,YAAY,KAAK,KAAK;AAC5B,QAAK,QAAQ,KAAK;IAChB,SAAS,OAAO,QAAQ;IACxB,QAAQ,OAAO,OAAO;IACtB,QAAQ,OAAO,OAAO;IACtB,UAAU;IACV;IACD,CAAC;AACF,QAAK,OAAO,MAAM,oBAAoB;IACpC;IACA,UAAU;IACV;IACD,CAAC;AAEF,OAAI,QAAQ,SAAS,WAAW;AAC9B,SAAK,OAAO,KAAK,uCAAuC;AACxD;;;AAIJ,OAAK,OAAO,MAAM,8BAA8B;GAC9C,eAAe,OAAO,SAAS;GAC/B,cAAc,KAAK,QAAQ;GAC5B,CAAC;AACF,SAAO;GACL;GACA,cAAc,EACZ,mBAAmB,KAAK,KACzB;GACF;;;;;;;AAQL,IAAa,SAAb,MAA6C;CAC3C,AAAO,UAAiC,EAAE;CAC1C,AAAiB;CAEjB,YACE,AAAiBJ,SACjB,AAAiBK,OAAe,cAChC;EAFiB;EACA;AAEjB,OAAK,SAAS,aAAa;GACzB,WAAW;GACX,WAAW;GACZ,CAAC;;;;;CAMJ,MAAM,WACJ,WACuC;EACvC,MAAM,aAAa,KAAK,QAAQ,UAAU,KAAK;AAC/C,OAAK,OAAO,MAAM,qCAAqC;GACrD,MAAM,UAAU;GAChB;GACD,CAAC;AAEF,MAAI;GAEF,MAAM,UAAU,KAAK,WAAW,WAAW;AAC3C,OAAI,YAAY,KAAK,QAAQ,YAAY,KAAK;AAC5C,SAAK,OAAO,MAAM,6BAA6B,EAAE,SAAS,CAAC;AAC3D,UAAM,KAAK,QAAQ,MAAM,SAAS,EAAE,WAAW,MAAM,CAAC;;GAGxD,MAAM,UAAU,UAAU,IAAI,UAAU,MAAM,SAAS;AACvD,QAAK,OAAO,MAAM,wBAAwB;IACxC,MAAM;IACN,eAAe,QAAQ;IACxB,CAAC;AACF,SAAM,KAAK,QAAQ,UAAU,YAAY,SAAS,EAAE,UAAU,SAAS,CAAC;GACxE,MAAM,YAAY,KAAK,KAAK;GAC5B,MAAMC,SAA8B;IAClC,WAAW;IACX,MAAM,UAAU;IAChB,QAAQ;IACR,QAAQ,WAAW,UAAU;IAC7B;IACD;AACD,QAAK,QAAQ,KAAK,OAAO;AACzB,QAAK,OAAO,KAAK,6BAA6B;IAC5C,MAAM,UAAU;IAChB;IACD,CAAC;AACF,UAAO;IAAE,QAAQ;IAAa,QAAQ,WAAW,UAAU;IAAQ;WAC5DF,OAAgB;GACvB,MAAM,YAAY,KAAK,KAAK;GAC5B,MAAM,eAAe,gBAAgB,MAAM;GAC3C,MAAME,SAA8B;IAClC,WAAW;IACX,MAAM,UAAU;IAChB,QAAQ;IACR,QAAQ,oBAAoB,UAAU;IACtC,OAAO;IACP;IACD;AACD,QAAK,QAAQ,KAAK,OAAO;AACzB,QAAK,OAAO,MAAM,yBAAyB,QAAQ,MAAM,EAAE;IACzD,MAAM,UAAU;IAChB,OAAO;IACR,CAAC;AACF,SAAM;;;;;;;CAQV,MAAM,WACJ,WACuC;EACvC,MAAM,aAAa,KAAK,QAAQ,UAAU,KAAK;AAC/C,OAAK,OAAO,MAAM,qCAAqC;GACrD,MAAM,UAAU;GAChB;GACD,CAAC;AAEF,MAAI;GACF,IAAIC;AACJ,OAAI;AACF,SAAK,OAAO,MAAM,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAIhE,gBAHiB,MAAM,KAAK,QAAQ,SAAS,YAAY,EACvD,UAAU,SACX,CAAC,EACkB;AACpB,SAAK,OAAO,MAAM,sBAAsB;KACtC,MAAM;KACN,gBAAgB,SAAS;KAC1B,CAAC;YACKH,OAAgB;IAEvB,MAAM,WAAW,sBAAsB,MAAM,GAAG,QAAQ,EAAE;IAC1D,MAAM,eAAe,gBAAgB,MAAM;AAC3C,QACE,aAAa,SAAS,YAAY,IAClC,aAAa,SAAS,SAAS,IAC/B,SAAS,WAAW,KACpB;AACA,UAAK,OAAO,MAAM,8BAA8B,QAAW,EACzD,MAAM,UAAU,MACjB,CAAC;AACF,WAAM,IAAI,MAAM,+BAA+B,UAAU,OAAO;;AAElE,SAAK,OAAO,MAAM,sBAAsB,QAAQ,MAAM,EAAE;KACtD,MAAM,UAAU;KAChB,OAAO;KACR,CAAC;AACF,UAAM;;GAGR,MAAM,UAAU,UAAU,UAAU,UAAU,KAAK;AACnD,QAAK,OAAO,MAAM,gBAAgB;IAChC,MAAM;IACN,gBAAgB,SAAS;IACzB,eAAe,QAAQ;IACxB,CAAC;AACF,SAAM,KAAK,QAAQ,UAAU,YAAY,SAAS,EAAE,UAAU,SAAS,CAAC;GACxE,MAAM,YAAY,KAAK,KAAK;GAC5B,MAAME,SAA8B;IAClC,WAAW;IACX,MAAM,UAAU;IAChB,QAAQ;IACR,QAAQ,WAAW,UAAU;IAC7B;IACD;AACD,QAAK,QAAQ,KAAK,OAAO;AACzB,QAAK,OAAO,KAAK,6BAA6B;IAC5C,MAAM,UAAU;IAChB;IACD,CAAC;AACF,UAAO;IAAE,QAAQ;IAAa,QAAQ,WAAW,UAAU;IAAQ;WAC5DF,OAAgB;GACvB,MAAM,YAAY,KAAK,KAAK;GAC5B,MAAM,eAAe,gBAAgB,MAAM;GAC3C,MAAME,SAA8B;IAClC,WAAW;IACX,MAAM,UAAU;IAChB,QAAQ;IACR,QAAQ,oBAAoB,UAAU;IACtC,OAAO;IACP;IACD;AACD,QAAK,QAAQ,KAAK,OAAO;AACzB,QAAK,OAAO,MAAM,yBAAyB,QAAQ,MAAM,EAAE;IACzD,MAAM,UAAU;IAChB,OAAO;IACR,CAAC;AACF,SAAM;;;;;;CAOV,MAAM,WACJ,WACuC;EACvC,MAAM,aAAa,KAAK,QAAQ,UAAU,KAAK;AAC/C,OAAK,OAAO,MAAM,qCAAqC;GACrD,MAAM,UAAU;GAChB;GACD,CAAC;AAEF,MAAI;AACF,SAAM,KAAK,QAAQ,WAAW,WAAW;GACzC,MAAM,YAAY,KAAK,KAAK;GAC5B,MAAMA,SAA8B;IAClC,WAAW;IACX,MAAM,UAAU;IAChB,QAAQ;IACR,QAAQ,WAAW,UAAU;IAC7B;IACD;AACD,QAAK,QAAQ,KAAK,OAAO;AACzB,QAAK,OAAO,KAAK,6BAA6B;IAC5C,MAAM,UAAU;IAChB;IACD,CAAC;AACF,UAAO;IAAE,QAAQ;IAAa,QAAQ,WAAW,UAAU;IAAQ;WAC5DF,OAAgB;GACvB,MAAM,YAAY,KAAK,KAAK;GAC5B,MAAM,eAAe,gBAAgB,MAAM;GAC3C,MAAME,SAA8B;IAClC,WAAW;IACX,MAAM,UAAU;IAChB,QAAQ;IACR,QAAQ,oBAAoB,UAAU;IACtC,OAAO;IACP;IACD;AACD,QAAK,QAAQ,KAAK,OAAO;AACzB,QAAK,OAAO,MAAM,yBAAyB,QAAQ,MAAM,EAAE;IACzD,MAAM,UAAU;IAChB,OAAO;IACR,CAAC;AACF,SAAM;;;CAIV,AAAQ,QAAQ,cAA8B;EAE5C,IAAI,gBAAgB;AACpB,MAAI,aAAa,WAAW,KAAK,KAAK,EAAE;AACtC,mBAAgB,aAAa,MAAM,KAAK,KAAK,OAAO;AAEpD,mBAAgB,cAAc,QAAQ,OAAO,GAAG;;EAIlD,MAAM,aAAa,cAAc,QAAQ,SAAS,GAAG,CAAC,QAAQ,OAAO,GAAG;EAOxE,MAAM,YANW,aAAa,GAAG,KAAK,KAAK,GAAG,eAAe,KAAK,MAGpB,QAAQ,QAAQ,IAAI,CAI/D,MAAM,IAAI,CACV,QAAQ,MAAM,KAAK,MAAM,IAAI;EAChC,MAAME,QAAkB,EAAE;AAE1B,OAAK,MAAM,WAAW,SACpB,KAAI,YAAY,MAAM;AACpB,OAAI,MAAM,WAAW,EACnB,OAAM,IAAI,MAAM,gCAAgC,eAAe;AAEjE,SAAM,KAAK;QAEX,OAAM,KAAK,QAAQ;EAIvB,MAAM,iBAAiB,IAAI,MAAM,KAAK,IAAI;AAG1C,MAAI,CAAC,eAAe,WAAW,KAAK,KAAK,CACvC,OAAM,IAAI,MAAM,gCAAgC,eAAe;AAGjE,SAAO;;CAGT,AAAQ,WAAW,UAA0B;EAC3C,MAAM,YAAY,SAAS,YAAY,IAAI;AAC3C,MAAI,cAAc,GAChB,QAAO;AAET,SAAO,SAAS,UAAU,GAAG,UAAU,IAAI"}
|
package/dist/opencode/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "../
|
|
3
|
-
import
|
|
1
|
+
import { D as OpenCodeStartupContext } from "../contexts-1EsLHByO.js";
|
|
2
|
+
import "../process-types-GStiZ8f8.js";
|
|
3
|
+
import "../filesystem-BWAZCZER.js";
|
|
4
|
+
import { c as SandboxExtension, l as SandboxLike } from "../index-Bs4bqXDR.js";
|
|
4
5
|
import { OpencodeClient } from "@opencode-ai/sdk/v2/client";
|
|
5
6
|
import { Config } from "@opencode-ai/sdk/v2";
|
|
6
7
|
|
|
@@ -17,11 +18,6 @@ interface OpenCodeOptions {
|
|
|
17
18
|
config?: Config;
|
|
18
19
|
/** Additional environment variables to pass to the OpenCode process */
|
|
19
20
|
env?: Record<string, string>;
|
|
20
|
-
/**
|
|
21
|
-
* Stable process id for the OpenCode server so it can be looked up directly
|
|
22
|
-
* instead of scanning all processes. Defaults to `opencode-{port}`.
|
|
23
|
-
*/
|
|
24
|
-
processId?: string;
|
|
25
21
|
}
|
|
26
22
|
/**
|
|
27
23
|
* Server lifecycle management
|
|
@@ -45,9 +41,9 @@ declare class OpenCodeStartupError extends Error {
|
|
|
45
41
|
//#endregion
|
|
46
42
|
//#region ../../extensions/opencode/src/opencode.d.ts
|
|
47
43
|
interface OpenCodeSandboxLike extends SandboxLike {
|
|
48
|
-
exec
|
|
49
|
-
getProcess
|
|
50
|
-
listProcesses
|
|
44
|
+
exec: NonNullable<SandboxLike['exec']>;
|
|
45
|
+
getProcess: NonNullable<SandboxLike['getProcess']>;
|
|
46
|
+
listProcesses: NonNullable<SandboxLike['listProcesses']>;
|
|
51
47
|
containerFetch(request: Request, port: number): Promise<Response>;
|
|
52
48
|
}
|
|
53
49
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../../extensions/opencode/src/types.ts","../../../../extensions/opencode/src/opencode.ts","../../../../extensions/opencode/src/lifecycle.ts","../../../../extensions/opencode/src/client.ts","../../../../extensions/opencode/src/proxy.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../../extensions/opencode/src/types.ts","../../../../extensions/opencode/src/opencode.ts","../../../../extensions/opencode/src/lifecycle.ts","../../../../extensions/opencode/src/client.ts","../../../../extensions/opencode/src/proxy.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/opencode/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
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";
|
|
4
5
|
|
|
5
6
|
//#region ../../extensions/opencode/src/client.ts
|
|
6
7
|
let createSDKClient;
|
|
@@ -60,30 +61,43 @@ function getLogger() {
|
|
|
60
61
|
}
|
|
61
62
|
const DEFAULT_PORT$1 = 4096;
|
|
62
63
|
const OPENCODE_STARTUP_TIMEOUT_MS = 18e4;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
function openCodeCommand(port) {
|
|
65
|
+
return [
|
|
66
|
+
"opencode",
|
|
67
|
+
"serve",
|
|
68
|
+
"--port",
|
|
69
|
+
String(port),
|
|
70
|
+
"--hostname",
|
|
71
|
+
"0.0.0.0"
|
|
72
|
+
];
|
|
71
73
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return `opencode-${port}`;
|
|
74
|
+
function commandsEqual(a, b) {
|
|
75
|
+
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
75
76
|
}
|
|
76
|
-
/**
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
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();
|
|
100
|
+
}
|
|
87
101
|
}
|
|
88
102
|
/**
|
|
89
103
|
* Ensures OpenCode server is running in the container.
|
|
@@ -91,30 +105,9 @@ async function findExistingOpenCodeProcess(sandbox, processId) {
|
|
|
91
105
|
* Handles concurrent startup attempts gracefully by retrying on failure.
|
|
92
106
|
* Returns the process handle.
|
|
93
107
|
*/
|
|
94
|
-
async function ensureOpenCodeServer(sandbox, port,
|
|
95
|
-
const existingProcess = await findExistingOpenCodeProcess(sandbox,
|
|
108
|
+
async function ensureOpenCodeServer(sandbox, port, directory, config, customEnv) {
|
|
109
|
+
const existingProcess = await findExistingOpenCodeProcess(sandbox, port, directory);
|
|
96
110
|
if (existingProcess) {
|
|
97
|
-
if (await existingProcess.status() === "starting") {
|
|
98
|
-
getLogger().debug("Found starting OpenCode process, waiting for ready", {
|
|
99
|
-
port,
|
|
100
|
-
processId: existingProcess.id
|
|
101
|
-
});
|
|
102
|
-
try {
|
|
103
|
-
await existingProcess.waitForPort(port, {
|
|
104
|
-
mode: "http",
|
|
105
|
-
path: "/path",
|
|
106
|
-
status: 200,
|
|
107
|
-
timeout: OPENCODE_STARTUP_TIMEOUT_MS
|
|
108
|
-
});
|
|
109
|
-
} catch (e) {
|
|
110
|
-
const logs = await existingProcess.getLogs();
|
|
111
|
-
throw new OpenCodeStartupError(`OpenCode server failed to start. Stderr: ${logs.stderr || "(empty)"}`, {
|
|
112
|
-
port,
|
|
113
|
-
stderr: logs.stderr,
|
|
114
|
-
command: existingProcess.command
|
|
115
|
-
}, { cause: e });
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
111
|
getLogger().debug("Reusing existing OpenCode process", {
|
|
119
112
|
port,
|
|
120
113
|
processId: existingProcess.id
|
|
@@ -122,29 +115,14 @@ async function ensureOpenCodeServer(sandbox, port, processId, directory, config,
|
|
|
122
115
|
return existingProcess;
|
|
123
116
|
}
|
|
124
117
|
try {
|
|
125
|
-
return await startOpenCodeServer(sandbox, port,
|
|
118
|
+
return await startOpenCodeServer(sandbox, port, directory, config, customEnv);
|
|
126
119
|
} catch (startupError) {
|
|
127
|
-
const retryProcess = await findExistingOpenCodeProcess(sandbox,
|
|
120
|
+
const retryProcess = await findExistingOpenCodeProcess(sandbox, port, directory);
|
|
128
121
|
if (retryProcess) {
|
|
129
122
|
getLogger().debug("Startup failed but found concurrent process, reusing", {
|
|
130
123
|
port,
|
|
131
124
|
processId: retryProcess.id
|
|
132
125
|
});
|
|
133
|
-
if (await retryProcess.status() === "starting") try {
|
|
134
|
-
await retryProcess.waitForPort(port, {
|
|
135
|
-
mode: "http",
|
|
136
|
-
path: "/path",
|
|
137
|
-
status: 200,
|
|
138
|
-
timeout: OPENCODE_STARTUP_TIMEOUT_MS
|
|
139
|
-
});
|
|
140
|
-
} catch (e) {
|
|
141
|
-
const logs = await retryProcess.getLogs();
|
|
142
|
-
throw new OpenCodeStartupError(`OpenCode server failed to start. Stderr: ${logs.stderr || "(empty)"}`, {
|
|
143
|
-
port,
|
|
144
|
-
stderr: logs.stderr,
|
|
145
|
-
command: retryProcess.command
|
|
146
|
-
}, { cause: e });
|
|
147
|
-
}
|
|
148
126
|
return retryProcess;
|
|
149
127
|
}
|
|
150
128
|
throw startupError;
|
|
@@ -153,7 +131,7 @@ async function ensureOpenCodeServer(sandbox, port, processId, directory, config,
|
|
|
153
131
|
/**
|
|
154
132
|
* Internal function to start a new OpenCode server process.
|
|
155
133
|
*/
|
|
156
|
-
async function startOpenCodeServer(sandbox, port,
|
|
134
|
+
async function startOpenCodeServer(sandbox, port, directory, config, customEnv) {
|
|
157
135
|
getLogger().info("Starting OpenCode server", {
|
|
158
136
|
port,
|
|
159
137
|
directory
|
|
@@ -181,9 +159,9 @@ async function startOpenCodeServer(sandbox, port, processId, directory, config,
|
|
|
181
159
|
}
|
|
182
160
|
}
|
|
183
161
|
if (customEnv) Object.assign(env, customEnv);
|
|
184
|
-
const command =
|
|
185
|
-
const process = await sandbox.exec(command, {
|
|
186
|
-
|
|
162
|
+
const command = openCodeCommand(port);
|
|
163
|
+
const process = await createExtensionProcessSandbox(sandbox).exec(command, {
|
|
164
|
+
cwd: directory,
|
|
187
165
|
env: Object.keys(env).length > 0 ? env : void 0
|
|
188
166
|
});
|
|
189
167
|
try {
|
|
@@ -198,16 +176,16 @@ async function startOpenCodeServer(sandbox, port, processId, directory, config,
|
|
|
198
176
|
processId: process.id
|
|
199
177
|
});
|
|
200
178
|
} catch (e) {
|
|
201
|
-
const
|
|
179
|
+
const stderr = await collectStderr(process);
|
|
202
180
|
const error = e instanceof Error ? e : void 0;
|
|
203
181
|
getLogger().error("OpenCode server failed to start", error, {
|
|
204
182
|
port,
|
|
205
|
-
stderr
|
|
183
|
+
stderr
|
|
206
184
|
});
|
|
207
|
-
throw new OpenCodeStartupError(`OpenCode server failed to start. Stderr: ${
|
|
185
|
+
throw new OpenCodeStartupError(`OpenCode server failed to start. Stderr: ${stderr || "(empty)"}`, {
|
|
208
186
|
port,
|
|
209
|
-
stderr
|
|
210
|
-
command
|
|
187
|
+
stderr,
|
|
188
|
+
command: command.join(" ")
|
|
211
189
|
}, { cause: e });
|
|
212
190
|
}
|
|
213
191
|
return process;
|
|
@@ -260,13 +238,13 @@ async function startOpenCodeServer(sandbox, port, processId, directory, config,
|
|
|
260
238
|
*/
|
|
261
239
|
async function createOpenCodeServer(sandbox, options) {
|
|
262
240
|
const port = options?.port ?? DEFAULT_PORT$1;
|
|
263
|
-
const process = await ensureOpenCodeServer(sandbox, port, options?.
|
|
241
|
+
const process = await ensureOpenCodeServer(sandbox, port, options?.directory, options?.config, options?.env);
|
|
264
242
|
return {
|
|
265
243
|
port,
|
|
266
244
|
url: `http://localhost:${port}`,
|
|
267
245
|
close: async () => {
|
|
268
|
-
process.kill(
|
|
269
|
-
await process.
|
|
246
|
+
await process.kill(15);
|
|
247
|
+
await process.waitForExit().catch(() => {});
|
|
270
248
|
}
|
|
271
249
|
};
|
|
272
250
|
}
|
|
@@ -280,10 +258,6 @@ function isContainerUnavailable(error) {
|
|
|
280
258
|
if (typeof error !== "object" || error === null) return false;
|
|
281
259
|
return error.name === "ContainerUnavailableError";
|
|
282
260
|
}
|
|
283
|
-
/** Stable process id for the OpenCode server on a given port. */
|
|
284
|
-
function defaultProcessId(port) {
|
|
285
|
-
return `opencode-${port}`;
|
|
286
|
-
}
|
|
287
261
|
/**
|
|
288
262
|
* DO-resident lifecycle handle for an OpenCode server. Owns the durable
|
|
289
263
|
* `opencode serve` process: start/reuse, stop, status, and request proxying.
|
|
@@ -351,11 +325,8 @@ var OpenCodeHandle = class extends SandboxExtension {
|
|
|
351
325
|
...this.#lastOptions
|
|
352
326
|
};
|
|
353
327
|
const port = resolved.port ?? DEFAULT_PORT;
|
|
354
|
-
const processId = resolved.processId ?? defaultProcessId(port);
|
|
355
|
-
const proc = await this.#sandbox.getProcess(processId);
|
|
356
|
-
const status = proc ? await proc.status() : null;
|
|
357
328
|
return {
|
|
358
|
-
running:
|
|
329
|
+
running: await findExistingOpenCodeProcess(this.#sandbox, port, resolved.directory) !== null,
|
|
359
330
|
port,
|
|
360
331
|
url: `http://localhost:${port}`
|
|
361
332
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["createSDKClient: OpenCodeClientFactory | undefined","DEFAULT_PORT","defaultProcessId","env: Record<string, string>","#sandbox","#defaults","#storage","#stateKey","#lastOptions","#loadPersisted","#persist","server: OpenCodeServer","#server"],"sources":["../../../../extensions/opencode/src/client.ts","../../../../extensions/opencode/src/types.ts","../../../../extensions/opencode/src/opencode.ts","../../../../extensions/opencode/src/lifecycle.ts","../../../../extensions/opencode/src/proxy.ts"],"sourcesContent":["import type { OpencodeClient } from '@opencode-ai/sdk/v2/client';\nimport type { OpenCodeHandle } from './lifecycle';\nimport type { OpenCodeOptions } from './types';\n\ntype OpenCodeClientFactory = (options: {\n baseUrl: string;\n fetch: typeof fetch;\n directory?: string;\n}) => OpencodeClient;\n\nlet createSDKClient: OpenCodeClientFactory | undefined;\n\nasync function loadSDK(): Promise<OpenCodeClientFactory> {\n if (createSDKClient) return createSDKClient;\n try {\n const sdk = await import('@opencode-ai/sdk/v2/client');\n createSDKClient = sdk.createOpencodeClient as OpenCodeClientFactory;\n return createSDKClient;\n } catch {\n throw new Error(\n '@opencode-ai/sdk is required for OpenCode integration. ' +\n 'Install it with: npm install @opencode-ai/sdk'\n );\n }\n}\n\n/**\n * Build a typed OpenCode SDK client from a lifecycle handle.\n *\n * Works against either the Worker stub (`sandbox.opencode`) or the in-DO object\n * (`this.opencode`): it ensures the server through the handle, reads the stored\n * config, and routes every request through `handle.fetch`. Because the handle\n * owns `containerFetch`, this helper never touches sandbox transport directly.\n *\n * The SDK is imported lazily so the peer dependency is only required when an\n * OpenCode client is actually built.\n */\nexport async function createOpenCodeClient<TClient = OpencodeClient>(\n handle: OpenCodeHandle,\n options?: OpenCodeOptions\n): Promise<TClient> {\n const server = await handle.start(options);\n const config = await handle.config();\n const directory = options?.directory ?? config.directory;\n\n const factory = await loadSDK();\n const client = factory({\n baseUrl: server.url,\n fetch: (input, init) => handle.fetch(new Request(input, init)),\n directory\n });\n\n return client as TClient;\n}\n","import type { Config } from '@opencode-ai/sdk/v2';\nimport { ErrorCode, type OpenCodeStartupContext } from '@repo/shared/errors';\n\n/**\n * Configuration options for starting OpenCode server\n */\nexport interface OpenCodeOptions {\n /** Port for OpenCode server (default: 4096) */\n port?: number;\n /** Working directory for OpenCode (default: container's cwd) */\n directory?: string;\n /** OpenCode configuration */\n config?: Config;\n /** Additional environment variables to pass to the OpenCode process */\n env?: Record<string, string>;\n /**\n * Stable process id for the OpenCode server so it can be looked up directly\n * instead of scanning all processes. Defaults to `opencode-{port}`.\n */\n processId?: string;\n}\n\n/**\n * Server lifecycle management\n */\nexport interface OpenCodeServer {\n /** Port the server is running on */\n port: number;\n /** Base URL for SDK client (http://localhost:{port}) */\n url: string;\n /** Close the server gracefully */\n close(): Promise<void>;\n}\n\n/**\n * Error thrown when OpenCode server fails to start\n */\nexport class OpenCodeStartupError extends Error {\n public readonly code = ErrorCode.OPENCODE_STARTUP_FAILED;\n public readonly context: OpenCodeStartupContext;\n\n constructor(\n message: string,\n context: OpenCodeStartupContext,\n options?: ErrorOptions\n ) {\n super(message, options);\n this.name = 'OpenCodeStartupError';\n this.context = context;\n }\n}\n","import type { SandboxLike } from '@cloudflare/sandbox/extensions';\nimport type { Config } from '@opencode-ai/sdk/v2';\nimport {\n createLogger,\n type Logger,\n type SandboxExecOptions,\n type SandboxProcess,\n type SandboxProcessPromise,\n shellEscape\n} from '@repo/shared';\nimport type { OpenCodeOptions, OpenCodeServer } from './types';\nimport { OpenCodeStartupError } from './types';\n\n// Lazy logger creation to avoid global scope restrictions in Workers\nfunction getLogger(): Logger {\n return createLogger({ component: 'sandbox-do', operation: 'opencode' });\n}\n\nexport interface OpenCodeSandboxLike extends SandboxLike {\n exec(\n command: string | string[],\n options?: SandboxExecOptions\n ): SandboxProcessPromise;\n getProcess(id: string): Promise<SandboxProcess | null>;\n listProcesses(): Promise<SandboxProcess[]>;\n containerFetch(request: Request, port: number): Promise<Response>;\n}\n\nconst DEFAULT_PORT = 4096;\nconst OPENCODE_STARTUP_TIMEOUT_MS = 180_000;\nconst OPENCODE_SERVE = (port: number) =>\n `opencode serve --port ${port} --hostname 0.0.0.0`;\n\n/**\n * Build the full command, optionally with a directory prefix.\n * If directory is provided, we cd to it first so OpenCode uses it as cwd.\n */\nfunction buildOpenCodeCommand(port: number, directory?: string): string {\n const serve = OPENCODE_SERVE(port);\n return directory ? `cd ${shellEscape(directory)} && ${serve}` : serve;\n}\n\n/** Stable process id for the OpenCode server on a given port. */\nfunction defaultProcessId(port: number): string {\n return `opencode-${port}`;\n}\n\n/**\n * Find the OpenCode server by its stable process id. Returns the process if it\n * is still active, null otherwise. A direct lookup avoids scanning every\n * process in the container.\n */\nasync function findExistingOpenCodeProcess(\n sandbox: OpenCodeSandboxLike,\n processId: string\n): Promise<SandboxProcess | null> {\n const process = await sandbox.getProcess(processId);\n if (!process) return null;\n\n const status = await process.status();\n if (status === 'starting' || status === 'running') {\n return process;\n }\n\n return null;\n}\n\n/**\n * Ensures OpenCode 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 * Returns the process handle.\n */\nasync function ensureOpenCodeServer(\n sandbox: OpenCodeSandboxLike,\n port: number,\n processId: string,\n directory?: string,\n config?: Config,\n customEnv?: Record<string, string>\n): Promise<SandboxProcess> {\n const existingProcess = await findExistingOpenCodeProcess(sandbox, processId);\n if (existingProcess) {\n // Reuse existing process - wait for it to be ready if still starting\n if ((await existingProcess.status()) === 'starting') {\n getLogger().debug('Found starting OpenCode process, waiting for ready', {\n port,\n processId: existingProcess.id\n });\n try {\n await existingProcess.waitForPort(port, {\n mode: 'http',\n path: '/path',\n status: 200,\n timeout: OPENCODE_STARTUP_TIMEOUT_MS\n });\n } catch (e) {\n const logs = await existingProcess.getLogs();\n throw new OpenCodeStartupError(\n `OpenCode server failed to start. Stderr: ${logs.stderr || '(empty)'}`,\n { port, stderr: logs.stderr, command: existingProcess.command },\n { cause: e }\n );\n }\n }\n getLogger().debug('Reusing existing OpenCode process', {\n port,\n processId: existingProcess.id\n });\n return existingProcess;\n }\n\n // Try to start a new OpenCode server\n try {\n return await startOpenCodeServer(\n sandbox,\n port,\n processId,\n directory,\n config,\n customEnv\n );\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 findExistingOpenCodeProcess(sandbox, processId);\n if (retryProcess) {\n getLogger().debug(\n 'Startup failed but found concurrent process, reusing',\n {\n port,\n processId: retryProcess.id\n }\n );\n // Wait for the concurrent server to be ready\n if ((await retryProcess.status()) === 'starting') {\n try {\n await retryProcess.waitForPort(port, {\n mode: 'http',\n path: '/path',\n status: 200,\n timeout: OPENCODE_STARTUP_TIMEOUT_MS\n });\n } catch (e) {\n const logs = await retryProcess.getLogs();\n throw new OpenCodeStartupError(\n `OpenCode server failed to start. Stderr: ${logs.stderr || '(empty)'}`,\n { port, stderr: logs.stderr, command: retryProcess.command },\n { cause: e }\n );\n }\n }\n return retryProcess;\n }\n\n // No concurrent server found - the failure was genuine\n throw startupError;\n }\n}\n\n/**\n * Internal function to start a new OpenCode server process.\n */\nasync function startOpenCodeServer(\n sandbox: OpenCodeSandboxLike,\n port: number,\n processId: string,\n directory?: string,\n config?: Config,\n customEnv?: Record<string, string>\n): Promise<SandboxProcess> {\n getLogger().info('Starting OpenCode server', { port, directory });\n\n // Pass config via OPENCODE_CONFIG_CONTENT and also extract API keys to env vars\n // because OpenCode's provider auth looks for env vars like ANTHROPIC_API_KEY\n const env: Record<string, string> = {};\n\n if (config) {\n env.OPENCODE_CONFIG_CONTENT = JSON.stringify(config);\n\n // Extract API keys from provider config\n // Support both options.apiKey (official type) and legacy top-level apiKey\n if (\n config.provider &&\n typeof config.provider === 'object' &&\n !Array.isArray(config.provider)\n ) {\n for (const [providerId, providerConfig] of Object.entries(\n config.provider\n )) {\n if (providerId === 'cloudflare-ai-gateway') {\n continue;\n }\n\n // Try options.apiKey first (official Config type)\n let apiKey = providerConfig?.options?.apiKey;\n // Fall back to top-level apiKey for convenience\n if (!apiKey) {\n apiKey = (providerConfig as Record<string, unknown> | undefined)\n ?.apiKey as string | undefined;\n }\n if (typeof apiKey === 'string') {\n const envVar = `${providerId.toUpperCase()}_API_KEY`;\n env[envVar] = apiKey;\n }\n }\n\n const aiGatewayConfig = config.provider['cloudflare-ai-gateway'];\n if (aiGatewayConfig?.options) {\n const options = aiGatewayConfig.options as Record<string, unknown>;\n\n if (typeof options.accountId === 'string') {\n env.CLOUDFLARE_ACCOUNT_ID = options.accountId;\n }\n\n if (typeof options.gatewayId === 'string') {\n env.CLOUDFLARE_GATEWAY_ID = options.gatewayId;\n }\n\n if (typeof options.apiToken === 'string') {\n env.CLOUDFLARE_API_TOKEN = options.apiToken;\n }\n }\n }\n }\n\n // Custom env vars override config-extracted ones\n if (customEnv) {\n Object.assign(env, customEnv);\n }\n\n const command = buildOpenCodeCommand(port, directory);\n const process = await sandbox.exec(command, {\n processId,\n env: Object.keys(env).length > 0 ? env : undefined\n });\n\n // Wait for server to be ready - check the actual health endpoint\n try {\n await process.waitForPort(port, {\n mode: 'http',\n path: '/path',\n status: 200,\n timeout: OPENCODE_STARTUP_TIMEOUT_MS\n });\n getLogger().info('OpenCode server started successfully', {\n port,\n processId: process.id\n });\n } catch (e) {\n const logs = await process.getLogs();\n const error = e instanceof Error ? e : undefined;\n getLogger().error('OpenCode server failed to start', error, {\n port,\n stderr: logs.stderr\n });\n throw new OpenCodeStartupError(\n `OpenCode server failed to start. Stderr: ${logs.stderr || '(empty)'}`,\n { port, stderr: logs.stderr, command },\n { cause: e }\n );\n }\n\n return process;\n}\n\n/**\n * Starts an OpenCode server inside a Sandbox container.\n *\n * This function manages the server lifecycle only - use `createOpenCodeClient()` if you\n * also need a typed SDK client for programmatic access.\n *\n * If an OpenCode server is already running on the specified port, this function\n * will reuse it instead of starting a new one.\n *\n * @param sandbox - The Sandbox instance to run OpenCode in\n * @param options - Configuration options\n * @returns Promise resolving to server handle { port, url, close() }\n *\n * @example\n * ```typescript\n * import { getSandbox } from '@cloudflare/sandbox'\n * import { createOpenCodeServer } from '@cloudflare/sandbox/opencode'\n *\n * const sandbox = getSandbox(env.Sandbox, 'my-agent')\n * const server = await createOpenCodeServer(sandbox, {\n * directory: '/home/user/my-project',\n * config: {\n * provider: {\n * anthropic: {\n * options: { apiKey: env.ANTHROPIC_KEY }\n * },\n * // Or use Cloudflare AI Gateway (with unified billing, no provider keys needed).\n * // 'cloudflare-ai-gateway': {\n * // options: {\n * // accountId: env.CF_ACCOUNT_ID,\n * // gatewayId: env.CF_GATEWAY_ID,\n * // apiToken: env.CF_API_TOKEN\n * // },\n * // models: { 'anthropic/claude-sonnet-4-5-20250929': {} }\n * // }\n * }\n * }\n * })\n *\n * // Proxy requests to the web UI\n * return sandbox.containerFetch(request, server.port)\n *\n * // When done\n * await server.close()\n * ```\n */\nexport async function createOpenCodeServer(\n sandbox: OpenCodeSandboxLike,\n options?: OpenCodeOptions\n): Promise<OpenCodeServer> {\n const port = options?.port ?? DEFAULT_PORT;\n const processId = options?.processId ?? defaultProcessId(port);\n const process = await ensureOpenCodeServer(\n sandbox,\n port,\n processId,\n options?.directory,\n options?.config,\n options?.env\n );\n\n return {\n port,\n url: `http://localhost:${port}`,\n close: async () => {\n process.kill('SIGTERM');\n await process.exitCode.catch(() => {\n /* exit observed */\n });\n }\n };\n}\n\n/**\n * Proxy a request directly to the OpenCode server.\n *\n * Unlike `proxyToOpenCode()`, this helper does not apply any web UI redirects\n * or query parameter rewrites. Use it for API/CLI traffic where raw request\n * forwarding is preferred.\n */\nexport function proxyToOpenCodeServer(\n request: Request,\n sandbox: OpenCodeSandboxLike,\n server: OpenCodeServer\n): Promise<Response> {\n return sandbox.containerFetch(request, server.port);\n}\n\n/**\n * Proxy a request to the OpenCode web UI.\n *\n * This function handles the redirect and proxying only - you must start the\n * server separately using `createOpenCodeServer()`.\n *\n * Specifically handles:\n * 1. Ensuring the `?url=` parameter is set (required for OpenCode's frontend to\n * make API calls through the proxy instead of directly to localhost:4096)\n * 2. Proxying the request to the container\n *\n * @param request - The incoming HTTP request\n * @param sandbox - The Sandbox instance running OpenCode\n * @param server - The OpenCode server handle from createOpenCodeServer()\n * @returns Response from OpenCode or a redirect response\n *\n * @example\n * ```typescript\n * import { getSandbox } from '@cloudflare/sandbox'\n * import { createOpenCodeServer, proxyToOpenCode } from '@cloudflare/sandbox/opencode'\n *\n * export default {\n * async fetch(request: Request, env: Env) {\n * const sandbox = getSandbox(env.Sandbox, 'opencode')\n * const server = await createOpenCodeServer(sandbox, {\n * directory: '/home/user/project',\n * config: {\n * provider: {\n * anthropic: {\n * options: { apiKey: env.ANTHROPIC_KEY }\n * },\n * // Optional: Route all providers through Cloudflare AI Gateway\n * 'cloudflare-ai-gateway': {\n * options: {\n * accountId: env.CF_ACCOUNT_ID,\n * gatewayId: env.CF_GATEWAY_ID,\n * apiToken: env.CF_API_TOKEN\n * }\n * }\n * }\n * }\n * })\n * return proxyToOpenCode(request, sandbox, server)\n * }\n * }\n * ```\n */\nexport function proxyToOpenCode(\n request: Request,\n sandbox: OpenCodeSandboxLike,\n server: OpenCodeServer\n): Response | Promise<Response> {\n const url = new URL(request.url);\n\n // OpenCode's frontend defaults to http://127.0.0.1:4096 when hostname includes\n // \"localhost\" or \"opencode.ai\". The ?url= parameter overrides this behavior.\n // We only redirect GET requests for HTML pages (initial page load).\n // API calls (POST, PATCH, etc.) and asset requests are proxied directly\n // since redirecting POST loses the request body.\n if (!url.searchParams.has('url') && request.method === 'GET') {\n const accept = request.headers.get('accept') || '';\n const isHtmlRequest = accept.includes('text/html') || url.pathname === '/';\n if (isHtmlRequest) {\n url.searchParams.set('url', url.origin);\n return Response.redirect(url.toString(), 302);\n }\n }\n\n return proxyToOpenCodeServer(request, sandbox, server);\n}\n","import {\n SandboxExtension,\n type SandboxLike\n} from '@cloudflare/sandbox/extensions';\nimport { createOpenCodeServer, type OpenCodeSandboxLike } from './opencode';\nimport type { OpenCodeOptions, OpenCodeServer } from './types';\n\nconst DEFAULT_PORT = 4096;\n\n/** Resolved server metadata that is safe to return across the RPC boundary. */\nexport interface OpenCodeServerInfo {\n port: number;\n url: string;\n}\n\n/** Snapshot of the resolved lifecycle configuration. */\nexport interface OpenCodeConfig {\n port: number;\n directory?: string;\n}\n\n/** Current server status as observed in the container. */\nexport interface OpenCodeStatus extends OpenCodeServerInfo {\n running: boolean;\n}\n\n/**\n * The slice of `DurableObjectStorage` the handle uses to persist desired-state.\n * Pass `this.ctx.storage` so the server config survives a Durable Object\n * eviction (cold start) and is recovered lazily on the next `start()`.\n */\nexport interface OpenCodeStateStorage {\n get<T>(key: string): Promise<T | undefined>;\n put<T>(key: string, value: T): Promise<void>;\n}\n\n/** Options for {@link withOpenCode}: server defaults plus optional storage. */\nexport interface WithOpenCodeOptions extends OpenCodeOptions {\n /** Persist desired-state here so it survives DO eviction. */\n storage?: OpenCodeStateStorage;\n}\n\nconst STATE_KEY_PREFIX = 'opencode:desired-state:';\n\n/** Recognise the retryable container-unavailable error across RPC. */\nfunction isContainerUnavailable(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) return false;\n return (error as { name?: unknown }).name === 'ContainerUnavailableError';\n}\n\n/** Stable process id for the OpenCode server on a given port. */\nfunction defaultProcessId(port: number): string {\n return `opencode-${port}`;\n}\n\n/**\n * DO-resident lifecycle handle for an OpenCode server. Owns the durable\n * `opencode serve` process: start/reuse, stop, status, and request proxying.\n *\n * It extends {@link SandboxExtension}, so the DO stub exposes it as\n * `sandbox.opencode` and method calls dispatch through `callExtension` — the\n * same path the interpreter extension uses. `createOpenCodeClient` consumes the\n * same handle from either the Worker stub or the in-DO object.\n *\n * The server starts lazily: nothing runs until `start()` (or `fetch()`, which\n * calls it) is invoked. To start optimistically, call `opencode.start()` from\n * your Sandbox subclass's `onStart`.\n */\nexport class OpenCodeHandle extends SandboxExtension {\n readonly #sandbox: OpenCodeSandboxLike;\n readonly #defaults: OpenCodeOptions;\n readonly #storage: OpenCodeStateStorage | undefined;\n readonly #stateKey: string;\n #server: OpenCodeServer | undefined;\n #lastOptions: OpenCodeOptions | undefined;\n\n constructor(\n sandbox: OpenCodeSandboxLike,\n defaults: OpenCodeOptions = {},\n storage?: OpenCodeStateStorage,\n stateIndex = 0\n ) {\n super(sandbox as SandboxLike);\n this.#sandbox = sandbox;\n this.#defaults = defaults;\n this.#storage = storage;\n this.#stateKey = `${STATE_KEY_PREFIX}${stateIndex}`;\n }\n\n /**\n * Start or reuse the OpenCode server, returning RPC-safe metadata. With no\n * options it reuses the last-used config, recovering persisted desired-state\n * after a cold start. Retries once on a transient `CONTAINER_UNAVAILABLE`\n * (e.g. a rollout in flight).\n */\n async start(options?: OpenCodeOptions): Promise<OpenCodeServerInfo> {\n const recovered =\n options ?? this.#lastOptions ?? (await this.#loadPersisted());\n const resolved = { ...this.#defaults, ...recovered };\n this.#lastOptions = resolved;\n await this.#persist(resolved);\n\n let server: OpenCodeServer;\n try {\n server = await createOpenCodeServer(this.#sandbox, resolved);\n } catch (error) {\n if (!isContainerUnavailable(error)) throw error;\n server = await createOpenCodeServer(this.#sandbox, resolved);\n }\n\n this.#server = server;\n return { port: server.port, url: server.url };\n }\n\n /** Stop the running server, if one was started through this handle. */\n async stop(): Promise<void> {\n if (!this.#server) return;\n await this.#server.close();\n this.#server = undefined;\n }\n\n /** Report whether the named OpenCode server is currently running. */\n async status(): Promise<OpenCodeStatus> {\n const resolved = { ...this.#defaults, ...this.#lastOptions };\n const port = resolved.port ?? DEFAULT_PORT;\n const processId = resolved.processId ?? defaultProcessId(port);\n const proc = await this.#sandbox.getProcess(processId);\n const status = proc ? await proc.status() : null;\n const running = status === 'running' || status === 'starting';\n return { running, port, url: `http://localhost:${port}` };\n }\n\n /** Snapshot of the resolved configuration the client builder reads. */\n async config(): Promise<OpenCodeConfig> {\n const resolved = { ...this.#defaults, ...this.#lastOptions };\n return {\n port: resolved.port ?? DEFAULT_PORT,\n directory: resolved.directory\n };\n }\n\n /**\n * Start the server then route a request into the container. This is the\n * transport the SDK client's `fetch` adapter and the Worker proxy use, so it\n * works identically from the Worker stub (one RPC hop) or in-DO (local).\n */\n async fetch(request: Request): Promise<Response> {\n const server = await this.start();\n return this.#sandbox.containerFetch(request, server.port);\n }\n\n /**\n * Persist resolved desired-state so a cold DO can recover it. Best-effort.\n *\n * Secret-bearing fields (`config`, `env`) are never written to storage. They\n * are sourced fresh from the handle's defaults — which the Sandbox rebuilds\n * from the environment on every construction — so a cold start always uses\n * current credentials rather than a durable copy.\n */\n async #persist(options: OpenCodeOptions): Promise<void> {\n if (!this.#storage) return;\n const { config: _config, env: _env, ...safe } = options;\n await this.#storage.put(this.#stateKey, safe);\n }\n\n /** Read persisted desired-state, if any, after a DO eviction. */\n async #loadPersisted(): Promise<OpenCodeOptions | undefined> {\n if (!this.#storage) return undefined;\n return this.#storage.get<OpenCodeOptions>(this.#stateKey);\n }\n}\n\n/**\n * Assigns each handle a stable per-sandbox index so its persisted state key is\n * deterministic across DO reconstruction (field initializers run in the same\n * order each time). Keyed weakly so it is collected with the sandbox.\n */\nconst stateIndexCounter = new WeakMap<OpenCodeSandboxLike, number>();\n\n/**\n * Factory — attach as a field on a Sandbox subclass:\n * `opencode = withOpenCode(this, { directory, config, storage: this.ctx.storage })`.\n *\n * Pass `storage` to persist desired-state so the server is recovered after a DO\n * eviction (cold start). The server starts lazily on first use.\n */\nexport function withOpenCode(\n sandbox: OpenCodeSandboxLike,\n options: WithOpenCodeOptions = {}\n): OpenCodeHandle {\n const { storage, ...defaults } = options;\n const index = stateIndexCounter.get(sandbox) ?? 0;\n stateIndexCounter.set(sandbox, index + 1);\n return new OpenCodeHandle(sandbox, defaults, storage, index);\n}\n","import type { OpenCodeHandle } from './lifecycle';\n\n/** Configuration for {@link createOpenCodeProxy}. */\nexport interface OpenCodeProxyOptions {\n /** Origin the OpenCode web UI should call back through. Defaults to the request origin. */\n callbackOrigin?: string;\n}\n\n/**\n * Proxy a request to the OpenCode web UI through a lifecycle handle.\n *\n * For an initial HTML page load that still lacks the `?url=` parameter, returns\n * a redirect that adds it (so the OpenCode frontend calls back through the\n * proxy instead of `127.0.0.1:4096`). Every other request is routed through\n * `handle.fetch`, which ensures the server is running before forwarding into the\n * container.\n */\nexport function proxyToOpenCodeUI(\n request: Request,\n handle: OpenCodeHandle,\n options?: OpenCodeProxyOptions\n): Response | Promise<Response> {\n const url = new URL(request.url);\n\n // OpenCode's frontend defaults its API base to 127.0.0.1:4096; the ?url=\n // parameter overrides that. Only redirect initial HTML GET loads — redirecting\n // a POST would drop the body.\n if (!url.searchParams.has('url') && request.method === 'GET') {\n const accept = request.headers.get('accept') || '';\n const isHtmlRequest = accept.includes('text/html') || url.pathname === '/';\n if (isHtmlRequest) {\n url.searchParams.set('url', options?.callbackOrigin ?? url.origin);\n return Response.redirect(url.toString(), 302);\n }\n }\n\n // Ensure-then-forward: the handle starts the server on demand before proxying.\n return handle.fetch(request);\n}\n\n/**\n * Curried Worker fetch wrapper for OpenCode.\n *\n * `createOpenCodeProxy(resolve, options?)` captures a lazy per-request resolver\n * for the OpenCode lifecycle handle (`sandbox.opencode`) and returns a function\n * that wraps the user's worker entrypoint.\n *\n * The wrapped handler runs first. If it returns a 404 (or has no `fetch`), the\n * request falls through to the OpenCode web-UI proxy — the redirect handshake\n * for HTML loads and an ensure-then-forward into the container for everything\n * else. A 404 from the user handler is the \"not mine, proxy it\" signal, so the\n * handler only needs to own its own routes and `return new Response('Not\n * found', { status: 404 })` for the rest.\n *\n * ```ts\n * export default createOpenCodeProxy(\n * (env) => getSandbox(env.Sandbox, 'my-sandbox').opencode\n * )({\n * async fetch(request, env) {\n * // handle your own routes, else:\n * return new Response('Not found', { status: 404 });\n * }\n * });\n * ```\n */\nexport function createOpenCodeProxy<Env>(\n resolve: (env: Env) => OpenCodeHandle,\n options?: OpenCodeProxyOptions\n): (handler: ExportedHandler<Env>) => ExportedHandler<Env> {\n return (handler) => ({\n ...handler,\n async fetch(request, env, ctx) {\n if (handler.fetch) {\n const response = await handler.fetch(request, env, ctx);\n if (response.status !== 404) return response;\n }\n return proxyToOpenCodeUI(request, resolve(env), options);\n }\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["createSDKClient: OpenCodeClientFactory | undefined","DEFAULT_PORT","env: Record<string, string>","#sandbox","#defaults","#storage","#stateKey","#lastOptions","#loadPersisted","#persist","server: OpenCodeServer","#server"],"sources":["../../../../extensions/opencode/src/client.ts","../../../../extensions/opencode/src/types.ts","../../../../extensions/opencode/src/opencode.ts","../../../../extensions/opencode/src/lifecycle.ts","../../../../extensions/opencode/src/proxy.ts"],"sourcesContent":["import type { OpencodeClient } from '@opencode-ai/sdk/v2/client';\nimport type { OpenCodeHandle } from './lifecycle';\nimport type { OpenCodeOptions } from './types';\n\ntype OpenCodeClientFactory = (options: {\n baseUrl: string;\n fetch: typeof fetch;\n directory?: string;\n}) => OpencodeClient;\n\nlet createSDKClient: OpenCodeClientFactory | undefined;\n\nasync function loadSDK(): Promise<OpenCodeClientFactory> {\n if (createSDKClient) return createSDKClient;\n try {\n const sdk = await import('@opencode-ai/sdk/v2/client');\n createSDKClient = sdk.createOpencodeClient as OpenCodeClientFactory;\n return createSDKClient;\n } catch {\n throw new Error(\n '@opencode-ai/sdk is required for OpenCode integration. ' +\n 'Install it with: npm install @opencode-ai/sdk'\n );\n }\n}\n\n/**\n * Build a typed OpenCode SDK client from a lifecycle handle.\n *\n * Works against either the Worker stub (`sandbox.opencode`) or the in-DO object\n * (`this.opencode`): it ensures the server through the handle, reads the stored\n * config, and routes every request through `handle.fetch`. Because the handle\n * owns `containerFetch`, this helper never touches sandbox transport directly.\n *\n * The SDK is imported lazily so the peer dependency is only required when an\n * OpenCode client is actually built.\n */\nexport async function createOpenCodeClient<TClient = OpencodeClient>(\n handle: OpenCodeHandle,\n options?: OpenCodeOptions\n): Promise<TClient> {\n const server = await handle.start(options);\n const config = await handle.config();\n const directory = options?.directory ?? config.directory;\n\n const factory = await loadSDK();\n const client = factory({\n baseUrl: server.url,\n fetch: (input, init) => handle.fetch(new Request(input, init)),\n directory\n });\n\n return client as TClient;\n}\n","import type { Config } from '@opencode-ai/sdk/v2';\nimport { ErrorCode, type OpenCodeStartupContext } from '@repo/shared/errors';\n\n/**\n * Configuration options for starting OpenCode server\n */\nexport interface OpenCodeOptions {\n /** Port for OpenCode server (default: 4096) */\n port?: number;\n /** Working directory for OpenCode (default: container's cwd) */\n directory?: string;\n /** OpenCode configuration */\n config?: Config;\n /** Additional environment variables to pass to the OpenCode process */\n env?: Record<string, string>;\n}\n\n/**\n * Server lifecycle management\n */\nexport interface OpenCodeServer {\n /** Port the server is running on */\n port: number;\n /** Base URL for SDK client (http://localhost:{port}) */\n url: string;\n /** Close the server gracefully */\n close(): Promise<void>;\n}\n\n/**\n * Error thrown when OpenCode server fails to start\n */\nexport class OpenCodeStartupError extends Error {\n public readonly code = ErrorCode.OPENCODE_STARTUP_FAILED;\n public readonly context: OpenCodeStartupContext;\n\n constructor(\n message: string,\n context: OpenCodeStartupContext,\n options?: ErrorOptions\n ) {\n super(message, options);\n this.name = 'OpenCodeStartupError';\n this.context = context;\n }\n}\n","import {\n createExtensionProcessSandbox,\n type SandboxLike\n} from '@cloudflare/sandbox/extensions';\nimport type { Config } from '@opencode-ai/sdk/v2';\nimport {\n createLogger,\n type Logger,\n type SandboxCommand,\n type SandboxProcess\n} from '@repo/shared';\nimport type { OpenCodeOptions, OpenCodeServer } from './types';\nimport { OpenCodeStartupError } from './types';\n\n// Lazy logger creation to avoid global scope restrictions in Workers\nfunction getLogger(): Logger {\n return createLogger({ component: 'sandbox-do', operation: 'opencode' });\n}\n\nexport interface OpenCodeSandboxLike extends SandboxLike {\n exec: NonNullable<SandboxLike['exec']>;\n getProcess: NonNullable<SandboxLike['getProcess']>;\n listProcesses: NonNullable<SandboxLike['listProcesses']>;\n containerFetch(request: Request, port: number): Promise<Response>;\n}\n\nconst DEFAULT_PORT = 4096;\nconst OPENCODE_STARTUP_TIMEOUT_MS = 180_000;\nfunction openCodeCommand(port: number): SandboxCommand {\n return ['opencode', 'serve', '--port', String(port), '--hostname', '0.0.0.0'];\n}\n\nfunction commandsEqual(a: SandboxCommand, b: SandboxCommand): boolean {\n return a.length === b.length && a.every((value, index) => value === b[index]);\n}\n\n/** Recover a running OpenCode process from runtime-local process status. */\nexport async function findExistingOpenCodeProcess(\n sandbox: OpenCodeSandboxLike,\n port: number,\n directory?: string\n): Promise<SandboxProcess | null> {\n const command = openCodeCommand(port);\n const processes = createExtensionProcessSandbox(sandbox);\n const status = (await processes.listProcesses()).find(\n (candidate) =>\n candidate.state === 'running' &&\n candidate.cwd === directory &&\n commandsEqual(candidate.command, command)\n );\n return status ? processes.getProcess(status.id) : null;\n}\n\nasync function collectStderr(process: SandboxProcess): Promise<string> {\n const stream = await process.logs({ replay: true, follow: false });\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let stderr = '';\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value.type === 'stderr') {\n stderr += decoder.decode(value.data, { stream: true });\n }\n }\n return stderr + decoder.decode();\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * Ensures OpenCode 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 * Returns the process handle.\n */\nasync function ensureOpenCodeServer(\n sandbox: OpenCodeSandboxLike,\n port: number,\n directory?: string,\n config?: Config,\n customEnv?: Record<string, string>\n): Promise<SandboxProcess> {\n const existingProcess = await findExistingOpenCodeProcess(\n sandbox,\n port,\n directory\n );\n if (existingProcess) {\n getLogger().debug('Reusing existing OpenCode process', {\n port,\n processId: existingProcess.id\n });\n return existingProcess;\n }\n\n // Try to start a new OpenCode server\n try {\n return await startOpenCodeServer(\n sandbox,\n port,\n directory,\n config,\n customEnv\n );\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 findExistingOpenCodeProcess(\n sandbox,\n port,\n directory\n );\n if (retryProcess) {\n getLogger().debug(\n 'Startup failed but found concurrent process, reusing',\n { port, processId: retryProcess.id }\n );\n return retryProcess;\n }\n\n // No concurrent server found - the failure was genuine\n throw startupError;\n }\n}\n\n/**\n * Internal function to start a new OpenCode server process.\n */\nasync function startOpenCodeServer(\n sandbox: OpenCodeSandboxLike,\n port: number,\n directory?: string,\n config?: Config,\n customEnv?: Record<string, string>\n): Promise<SandboxProcess> {\n getLogger().info('Starting OpenCode server', { port, directory });\n\n // Pass config via OPENCODE_CONFIG_CONTENT and also extract API keys to env vars\n // because OpenCode's provider auth looks for env vars like ANTHROPIC_API_KEY\n const env: Record<string, string> = {};\n\n if (config) {\n env.OPENCODE_CONFIG_CONTENT = JSON.stringify(config);\n\n // Extract API keys from provider config\n // Support both options.apiKey (official type) and legacy top-level apiKey\n if (\n config.provider &&\n typeof config.provider === 'object' &&\n !Array.isArray(config.provider)\n ) {\n for (const [providerId, providerConfig] of Object.entries(\n config.provider\n )) {\n if (providerId === 'cloudflare-ai-gateway') {\n continue;\n }\n\n // Try options.apiKey first (official Config type)\n let apiKey = providerConfig?.options?.apiKey;\n // Fall back to top-level apiKey for convenience\n if (!apiKey) {\n apiKey = (providerConfig as Record<string, unknown> | undefined)\n ?.apiKey as string | undefined;\n }\n if (typeof apiKey === 'string') {\n const envVar = `${providerId.toUpperCase()}_API_KEY`;\n env[envVar] = apiKey;\n }\n }\n\n const aiGatewayConfig = config.provider['cloudflare-ai-gateway'];\n if (aiGatewayConfig?.options) {\n const options = aiGatewayConfig.options as Record<string, unknown>;\n\n if (typeof options.accountId === 'string') {\n env.CLOUDFLARE_ACCOUNT_ID = options.accountId;\n }\n\n if (typeof options.gatewayId === 'string') {\n env.CLOUDFLARE_GATEWAY_ID = options.gatewayId;\n }\n\n if (typeof options.apiToken === 'string') {\n env.CLOUDFLARE_API_TOKEN = options.apiToken;\n }\n }\n }\n }\n\n // Custom env vars override config-extracted ones\n if (customEnv) {\n Object.assign(env, customEnv);\n }\n\n const command = openCodeCommand(port);\n const process = await createExtensionProcessSandbox(sandbox).exec(command, {\n cwd: directory,\n env: Object.keys(env).length > 0 ? env : undefined\n });\n\n // Wait for server to be ready - check the actual health endpoint\n try {\n await process.waitForPort(port, {\n mode: 'http',\n path: '/path',\n status: 200,\n timeout: OPENCODE_STARTUP_TIMEOUT_MS\n });\n getLogger().info('OpenCode server started successfully', {\n port,\n processId: process.id\n });\n } catch (e) {\n const stderr = await collectStderr(process);\n const error = e instanceof Error ? e : undefined;\n getLogger().error('OpenCode server failed to start', error, {\n port,\n stderr\n });\n throw new OpenCodeStartupError(\n `OpenCode server failed to start. Stderr: ${stderr || '(empty)'}`,\n { port, stderr, command: command.join(' ') },\n { cause: e }\n );\n }\n\n return process;\n}\n\n/**\n * Starts an OpenCode server inside a Sandbox container.\n *\n * This function manages the server lifecycle only - use `createOpenCodeClient()` if you\n * also need a typed SDK client for programmatic access.\n *\n * If an OpenCode server is already running on the specified port, this function\n * will reuse it instead of starting a new one.\n *\n * @param sandbox - The Sandbox instance to run OpenCode in\n * @param options - Configuration options\n * @returns Promise resolving to server handle { port, url, close() }\n *\n * @example\n * ```typescript\n * import { getSandbox } from '@cloudflare/sandbox'\n * import { createOpenCodeServer } from '@cloudflare/sandbox/opencode'\n *\n * const sandbox = getSandbox(env.Sandbox, 'my-agent')\n * const server = await createOpenCodeServer(sandbox, {\n * directory: '/home/user/my-project',\n * config: {\n * provider: {\n * anthropic: {\n * options: { apiKey: env.ANTHROPIC_KEY }\n * },\n * // Or use Cloudflare AI Gateway (with unified billing, no provider keys needed).\n * // 'cloudflare-ai-gateway': {\n * // options: {\n * // accountId: env.CF_ACCOUNT_ID,\n * // gatewayId: env.CF_GATEWAY_ID,\n * // apiToken: env.CF_API_TOKEN\n * // },\n * // models: { 'anthropic/claude-sonnet-4-5-20250929': {} }\n * // }\n * }\n * }\n * })\n *\n * // Proxy requests to the web UI\n * return sandbox.containerFetch(request, server.port)\n *\n * // When done\n * await server.close()\n * ```\n */\nexport async function createOpenCodeServer(\n sandbox: OpenCodeSandboxLike,\n options?: OpenCodeOptions\n): Promise<OpenCodeServer> {\n const port = options?.port ?? DEFAULT_PORT;\n const process = await ensureOpenCodeServer(\n sandbox,\n port,\n options?.directory,\n options?.config,\n options?.env\n );\n\n return {\n port,\n url: `http://localhost:${port}`,\n close: async () => {\n await process.kill(15);\n await process.waitForExit().catch(() => {\n /* exit observed */\n });\n }\n };\n}\n\n/**\n * Proxy a request directly to the OpenCode server.\n *\n * Unlike `proxyToOpenCode()`, this helper does not apply any web UI redirects\n * or query parameter rewrites. Use it for API/CLI traffic where raw request\n * forwarding is preferred.\n */\nexport function proxyToOpenCodeServer(\n request: Request,\n sandbox: OpenCodeSandboxLike,\n server: OpenCodeServer\n): Promise<Response> {\n return sandbox.containerFetch(request, server.port);\n}\n\n/**\n * Proxy a request to the OpenCode web UI.\n *\n * This function handles the redirect and proxying only - you must start the\n * server separately using `createOpenCodeServer()`.\n *\n * Specifically handles:\n * 1. Ensuring the `?url=` parameter is set (required for OpenCode's frontend to\n * make API calls through the proxy instead of directly to localhost:4096)\n * 2. Proxying the request to the container\n *\n * @param request - The incoming HTTP request\n * @param sandbox - The Sandbox instance running OpenCode\n * @param server - The OpenCode server handle from createOpenCodeServer()\n * @returns Response from OpenCode or a redirect response\n *\n * @example\n * ```typescript\n * import { getSandbox } from '@cloudflare/sandbox'\n * import { createOpenCodeServer, proxyToOpenCode } from '@cloudflare/sandbox/opencode'\n *\n * export default {\n * async fetch(request: Request, env: Env) {\n * const sandbox = getSandbox(env.Sandbox, 'opencode')\n * const server = await createOpenCodeServer(sandbox, {\n * directory: '/home/user/project',\n * config: {\n * provider: {\n * anthropic: {\n * options: { apiKey: env.ANTHROPIC_KEY }\n * },\n * // Optional: Route all providers through Cloudflare AI Gateway\n * 'cloudflare-ai-gateway': {\n * options: {\n * accountId: env.CF_ACCOUNT_ID,\n * gatewayId: env.CF_GATEWAY_ID,\n * apiToken: env.CF_API_TOKEN\n * }\n * }\n * }\n * }\n * })\n * return proxyToOpenCode(request, sandbox, server)\n * }\n * }\n * ```\n */\nexport function proxyToOpenCode(\n request: Request,\n sandbox: OpenCodeSandboxLike,\n server: OpenCodeServer\n): Response | Promise<Response> {\n const url = new URL(request.url);\n\n // OpenCode's frontend defaults to http://127.0.0.1:4096 when hostname includes\n // \"localhost\" or \"opencode.ai\". The ?url= parameter overrides this behavior.\n // We only redirect GET requests for HTML pages (initial page load).\n // API calls (POST, PATCH, etc.) and asset requests are proxied directly\n // since redirecting POST loses the request body.\n if (!url.searchParams.has('url') && request.method === 'GET') {\n const accept = request.headers.get('accept') || '';\n const isHtmlRequest = accept.includes('text/html') || url.pathname === '/';\n if (isHtmlRequest) {\n url.searchParams.set('url', url.origin);\n return Response.redirect(url.toString(), 302);\n }\n }\n\n return proxyToOpenCodeServer(request, sandbox, server);\n}\n","import {\n SandboxExtension,\n type SandboxLike\n} from '@cloudflare/sandbox/extensions';\nimport {\n createOpenCodeServer,\n findExistingOpenCodeProcess,\n type OpenCodeSandboxLike\n} from './opencode';\nimport type { OpenCodeOptions, OpenCodeServer } from './types';\n\nconst DEFAULT_PORT = 4096;\n\n/** Resolved server metadata that is safe to return across the RPC boundary. */\nexport interface OpenCodeServerInfo {\n port: number;\n url: string;\n}\n\n/** Snapshot of the resolved lifecycle configuration. */\nexport interface OpenCodeConfig {\n port: number;\n directory?: string;\n}\n\n/** Current server status as observed in the container. */\nexport interface OpenCodeStatus extends OpenCodeServerInfo {\n running: boolean;\n}\n\n/**\n * The slice of `DurableObjectStorage` the handle uses to persist desired-state.\n * Pass `this.ctx.storage` so the server config survives a Durable Object\n * eviction (cold start) and is recovered lazily on the next `start()`.\n */\nexport interface OpenCodeStateStorage {\n get<T>(key: string): Promise<T | undefined>;\n put<T>(key: string, value: T): Promise<void>;\n}\n\n/** Options for {@link withOpenCode}: server defaults plus optional storage. */\nexport interface WithOpenCodeOptions extends OpenCodeOptions {\n /** Persist desired-state here so it survives DO eviction. */\n storage?: OpenCodeStateStorage;\n}\n\nconst STATE_KEY_PREFIX = 'opencode:desired-state:';\n\n/** Recognise the retryable container-unavailable error across RPC. */\nfunction isContainerUnavailable(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) return false;\n return (error as { name?: unknown }).name === 'ContainerUnavailableError';\n}\n\n/**\n * DO-resident lifecycle handle for an OpenCode server. Owns the durable\n * `opencode serve` process: start/reuse, stop, status, and request proxying.\n *\n * It extends {@link SandboxExtension}, so the DO stub exposes it as\n * `sandbox.opencode` and method calls dispatch through `callExtension` — the\n * same path the interpreter extension uses. `createOpenCodeClient` consumes the\n * same handle from either the Worker stub or the in-DO object.\n *\n * The server starts lazily: nothing runs until `start()` (or `fetch()`, which\n * calls it) is invoked. To start optimistically, call `opencode.start()` from\n * your Sandbox subclass's `onStart`.\n */\nexport class OpenCodeHandle extends SandboxExtension {\n readonly #sandbox: OpenCodeSandboxLike;\n readonly #defaults: OpenCodeOptions;\n readonly #storage: OpenCodeStateStorage | undefined;\n readonly #stateKey: string;\n #server: OpenCodeServer | undefined;\n #lastOptions: OpenCodeOptions | undefined;\n\n constructor(\n sandbox: OpenCodeSandboxLike,\n defaults: OpenCodeOptions = {},\n storage?: OpenCodeStateStorage,\n stateIndex = 0\n ) {\n super(sandbox as SandboxLike);\n this.#sandbox = sandbox;\n this.#defaults = defaults;\n this.#storage = storage;\n this.#stateKey = `${STATE_KEY_PREFIX}${stateIndex}`;\n }\n\n /**\n * Start or reuse the OpenCode server, returning RPC-safe metadata. With no\n * options it reuses the last-used config, recovering persisted desired-state\n * after a cold start. Retries once on a transient `CONTAINER_UNAVAILABLE`\n * (e.g. a rollout in flight).\n */\n async start(options?: OpenCodeOptions): Promise<OpenCodeServerInfo> {\n const recovered =\n options ?? this.#lastOptions ?? (await this.#loadPersisted());\n const resolved = { ...this.#defaults, ...recovered };\n this.#lastOptions = resolved;\n await this.#persist(resolved);\n\n let server: OpenCodeServer;\n try {\n server = await createOpenCodeServer(this.#sandbox, resolved);\n } catch (error) {\n if (!isContainerUnavailable(error)) throw error;\n server = await createOpenCodeServer(this.#sandbox, resolved);\n }\n\n this.#server = server;\n return { port: server.port, url: server.url };\n }\n\n /** Stop the running server, if one was started through this handle. */\n async stop(): Promise<void> {\n if (!this.#server) return;\n await this.#server.close();\n this.#server = undefined;\n }\n\n /** Report whether the named OpenCode server is currently running. */\n async status(): Promise<OpenCodeStatus> {\n const resolved = { ...this.#defaults, ...this.#lastOptions };\n const port = resolved.port ?? DEFAULT_PORT;\n const process = await findExistingOpenCodeProcess(\n this.#sandbox,\n port,\n resolved.directory\n );\n return {\n running: process !== null,\n port,\n url: `http://localhost:${port}`\n };\n }\n\n /** Snapshot of the resolved configuration the client builder reads. */\n async config(): Promise<OpenCodeConfig> {\n const resolved = { ...this.#defaults, ...this.#lastOptions };\n return {\n port: resolved.port ?? DEFAULT_PORT,\n directory: resolved.directory\n };\n }\n\n /**\n * Start the server then route a request into the container. This is the\n * transport the SDK client's `fetch` adapter and the Worker proxy use, so it\n * works identically from the Worker stub (one RPC hop) or in-DO (local).\n */\n async fetch(request: Request): Promise<Response> {\n const server = await this.start();\n return this.#sandbox.containerFetch(request, server.port);\n }\n\n /**\n * Persist resolved desired-state so a cold DO can recover it. Best-effort.\n *\n * Secret-bearing fields (`config`, `env`) are never written to storage. They\n * are sourced fresh from the handle's defaults — which the Sandbox rebuilds\n * from the environment on every construction — so a cold start always uses\n * current credentials rather than a durable copy.\n */\n async #persist(options: OpenCodeOptions): Promise<void> {\n if (!this.#storage) return;\n const { config: _config, env: _env, ...safe } = options;\n await this.#storage.put(this.#stateKey, safe);\n }\n\n /** Read persisted desired-state, if any, after a DO eviction. */\n async #loadPersisted(): Promise<OpenCodeOptions | undefined> {\n if (!this.#storage) return undefined;\n return this.#storage.get<OpenCodeOptions>(this.#stateKey);\n }\n}\n\n/**\n * Assigns each handle a stable per-sandbox index so its persisted state key is\n * deterministic across DO reconstruction (field initializers run in the same\n * order each time). Keyed weakly so it is collected with the sandbox.\n */\nconst stateIndexCounter = new WeakMap<OpenCodeSandboxLike, number>();\n\n/**\n * Factory — attach as a field on a Sandbox subclass:\n * `opencode = withOpenCode(this, { directory, config, storage: this.ctx.storage })`.\n *\n * Pass `storage` to persist desired-state so the server is recovered after a DO\n * eviction (cold start). The server starts lazily on first use.\n */\nexport function withOpenCode(\n sandbox: OpenCodeSandboxLike,\n options: WithOpenCodeOptions = {}\n): OpenCodeHandle {\n const { storage, ...defaults } = options;\n const index = stateIndexCounter.get(sandbox) ?? 0;\n stateIndexCounter.set(sandbox, index + 1);\n return new OpenCodeHandle(sandbox, defaults, storage, index);\n}\n","import type { OpenCodeHandle } from './lifecycle';\n\n/** Configuration for {@link createOpenCodeProxy}. */\nexport interface OpenCodeProxyOptions {\n /** Origin the OpenCode web UI should call back through. Defaults to the request origin. */\n callbackOrigin?: string;\n}\n\n/**\n * Proxy a request to the OpenCode web UI through a lifecycle handle.\n *\n * For an initial HTML page load that still lacks the `?url=` parameter, returns\n * a redirect that adds it (so the OpenCode frontend calls back through the\n * proxy instead of `127.0.0.1:4096`). Every other request is routed through\n * `handle.fetch`, which ensures the server is running before forwarding into the\n * container.\n */\nexport function proxyToOpenCodeUI(\n request: Request,\n handle: OpenCodeHandle,\n options?: OpenCodeProxyOptions\n): Response | Promise<Response> {\n const url = new URL(request.url);\n\n // OpenCode's frontend defaults its API base to 127.0.0.1:4096; the ?url=\n // parameter overrides that. Only redirect initial HTML GET loads — redirecting\n // a POST would drop the body.\n if (!url.searchParams.has('url') && request.method === 'GET') {\n const accept = request.headers.get('accept') || '';\n const isHtmlRequest = accept.includes('text/html') || url.pathname === '/';\n if (isHtmlRequest) {\n url.searchParams.set('url', options?.callbackOrigin ?? url.origin);\n return Response.redirect(url.toString(), 302);\n }\n }\n\n // Ensure-then-forward: the handle starts the server on demand before proxying.\n return handle.fetch(request);\n}\n\n/**\n * Curried Worker fetch wrapper for OpenCode.\n *\n * `createOpenCodeProxy(resolve, options?)` captures a lazy per-request resolver\n * for the OpenCode lifecycle handle (`sandbox.opencode`) and returns a function\n * that wraps the user's worker entrypoint.\n *\n * The wrapped handler runs first. If it returns a 404 (or has no `fetch`), the\n * request falls through to the OpenCode web-UI proxy — the redirect handshake\n * for HTML loads and an ensure-then-forward into the container for everything\n * else. A 404 from the user handler is the \"not mine, proxy it\" signal, so the\n * handler only needs to own its own routes and `return new Response('Not\n * found', { status: 404 })` for the rest.\n *\n * ```ts\n * export default createOpenCodeProxy(\n * (env) => getSandbox(env.Sandbox, 'my-sandbox').opencode\n * )({\n * async fetch(request, env) {\n * // handle your own routes, else:\n * return new Response('Not found', { status: 404 });\n * }\n * });\n * ```\n */\nexport function createOpenCodeProxy<Env>(\n resolve: (env: Env) => OpenCodeHandle,\n options?: OpenCodeProxyOptions\n): (handler: ExportedHandler<Env>) => ExportedHandler<Env> {\n return (handler) => ({\n ...handler,\n async fetch(request, env, ctx) {\n if (handler.fetch) {\n const response = await handler.fetch(request, env, ctx);\n if (response.status !== 404) return response;\n }\n return proxyToOpenCodeUI(request, resolve(env), options);\n }\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region ../shared/dist/process-types.d.ts
|
|
2
|
+
type SandboxCommand = readonly [executable: string, ...args: string[]];
|
|
3
|
+
/**
|
|
4
|
+
* Terminal outcome observed for the root subprocess.
|
|
5
|
+
*
|
|
6
|
+
* The process resource remains running until its supervised process group has
|
|
7
|
+
* settled. Signals delivered only to descendants do not rewrite this outcome.
|
|
8
|
+
*/
|
|
9
|
+
interface ProcessExit {
|
|
10
|
+
code: number;
|
|
11
|
+
signal?: number;
|
|
12
|
+
timedOut: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface ProcessFailure {
|
|
15
|
+
code: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
interface ProcessStatusBase {
|
|
19
|
+
id: string;
|
|
20
|
+
pid: number;
|
|
21
|
+
command: SandboxCommand;
|
|
22
|
+
cwd?: string;
|
|
23
|
+
startedAt: string;
|
|
24
|
+
}
|
|
25
|
+
/** Lifecycle state for the complete supervised process group. */
|
|
26
|
+
type ProcessStatus = (ProcessStatusBase & {
|
|
27
|
+
state: 'running';
|
|
28
|
+
}) | (ProcessStatusBase & {
|
|
29
|
+
state: 'exited';
|
|
30
|
+
exit: ProcessExit;
|
|
31
|
+
endedAt: string;
|
|
32
|
+
}) | (ProcessStatusBase & {
|
|
33
|
+
state: 'error';
|
|
34
|
+
error: ProcessFailure;
|
|
35
|
+
endedAt: string;
|
|
36
|
+
});
|
|
37
|
+
type ProcessLogCursor = string;
|
|
38
|
+
type ProcessLogEvent = {
|
|
39
|
+
type: 'stdout' | 'stderr';
|
|
40
|
+
cursor: ProcessLogCursor;
|
|
41
|
+
timestamp: string;
|
|
42
|
+
data: Uint8Array;
|
|
43
|
+
} | {
|
|
44
|
+
type: 'terminal';
|
|
45
|
+
state: 'exited';
|
|
46
|
+
cursor: ProcessLogCursor;
|
|
47
|
+
timestamp: string;
|
|
48
|
+
exit: ProcessExit;
|
|
49
|
+
} | {
|
|
50
|
+
type: 'terminal';
|
|
51
|
+
state: 'error';
|
|
52
|
+
cursor: ProcessLogCursor;
|
|
53
|
+
timestamp: string;
|
|
54
|
+
error: ProcessFailure;
|
|
55
|
+
} | {
|
|
56
|
+
type: 'truncated';
|
|
57
|
+
cursor?: ProcessLogCursor;
|
|
58
|
+
timestamp: string;
|
|
59
|
+
};
|
|
60
|
+
interface WaitForLogResult {
|
|
61
|
+
stream: 'stdout' | 'stderr';
|
|
62
|
+
text: string;
|
|
63
|
+
match: string;
|
|
64
|
+
cursor?: ProcessLogCursor;
|
|
65
|
+
}
|
|
66
|
+
interface ProcessLogsRPCOptions {
|
|
67
|
+
since?: ProcessLogCursor;
|
|
68
|
+
replay?: boolean;
|
|
69
|
+
follow?: boolean;
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
export { ProcessLogsRPCOptions as a, WaitForLogResult as c, ProcessLogEvent as i, ProcessFailure as n, ProcessStatus as o, ProcessLogCursor as r, SandboxCommand as s, ProcessExit as t };
|
|
73
|
+
//# sourceMappingURL=process-types-GStiZ8f8.d.ts.map
|