11import { getSandbox , type Sandbox } from "@cloudflare/sandbox" ;
2+ import { Hono } from "hono" ;
3+ import { HTTPException } from "hono/http-exception" ;
4+ import { runPromptTest , type PromptTestRequest } from "./prompt-test" ;
25
36export { Sandbox } from "@cloudflare/sandbox" ;
47
5- type Env = {
6- Bindings : {
7- Sandbox : DurableObjectNamespace < Sandbox > ;
8- ASSETS : Fetcher ;
9- ANTHROPIC_API_KEY ?: string ;
10- OPENAI_API_KEY ?: string ;
11- } ;
8+ type Bindings = {
9+ Sandbox : DurableObjectNamespace < Sandbox > ;
10+ ASSETS : Fetcher ;
11+ ANTHROPIC_API_KEY ?: string ;
12+ OPENAI_API_KEY ?: string ;
13+ CODEX_API_KEY ?: string ;
1214} ;
1315
16+ type AppEnv = { Bindings : Bindings } ;
17+
1418const PORT = 8000 ;
1519
1620/** Check if sandbox-agent is already running by probing its health endpoint */
@@ -23,54 +27,60 @@ async function isServerRunning(sandbox: Sandbox): Promise<boolean> {
2327 }
2428}
2529
26- /** Ensure sandbox-agent is running in the container */
27- async function ensureRunning ( sandbox : Sandbox , env : Env [ "Bindings" ] ) : Promise < void > {
28- if ( await isServerRunning ( sandbox ) ) return ;
29-
30- // Set environment variables for agents
30+ async function getReadySandbox ( name : string , env : Bindings ) : Promise < Sandbox > {
31+ const sandbox = getSandbox ( env . Sandbox , name ) ;
3132 const envVars : Record < string , string > = { } ;
3233 if ( env . ANTHROPIC_API_KEY ) envVars . ANTHROPIC_API_KEY = env . ANTHROPIC_API_KEY ;
3334 if ( env . OPENAI_API_KEY ) envVars . OPENAI_API_KEY = env . OPENAI_API_KEY ;
35+ if ( env . CODEX_API_KEY ) envVars . CODEX_API_KEY = env . CODEX_API_KEY ;
36+ if ( ! envVars . CODEX_API_KEY && envVars . OPENAI_API_KEY ) envVars . CODEX_API_KEY = envVars . OPENAI_API_KEY ;
3437 await sandbox . setEnvVars ( envVars ) ;
3538
36- // Start sandbox-agent server as background process
37- await sandbox . startProcess ( `sandbox-agent server --no-token --host 0.0.0.0 --port ${ PORT } ` ) ;
39+ if ( ! ( await isServerRunning ( sandbox ) ) ) {
40+ await sandbox . startProcess ( `sandbox-agent server --no-token --host 0.0.0.0 --port ${ PORT } ` ) ;
3841
39- // Poll health endpoint until server is ready (max ~6 seconds)
40- for ( let i = 0 ; i < 30 ; i ++ ) {
41- if ( await isServerRunning ( sandbox ) ) return ;
42- await new Promise ( ( r ) => setTimeout ( r , 200 ) ) ;
42+ for ( let i = 0 ; i < 30 ; i ++ ) {
43+ if ( await isServerRunning ( sandbox ) ) break ;
44+ await new Promise ( ( r ) => setTimeout ( r , 200 ) ) ;
45+ }
4346 }
47+ return sandbox ;
4448}
4549
46- export default {
47- async fetch ( request : Request , env : Env [ "Bindings" ] ) : Promise < Response > {
48- const url = new URL ( request . url ) ;
49-
50- // Proxy requests to sandbox-agent: /sandbox/:name/v1/...
51- const match = url . pathname . match ( / ^ \/ s a n d b o x \/ ( [ ^ / ] + ) ( \/ .* ) ? $ / ) ;
52- if ( match ) {
53- if ( ! env . ANTHROPIC_API_KEY && ! env . OPENAI_API_KEY ) {
54- return Response . json (
55- { error : "ANTHROPIC_API_KEY or OPENAI_API_KEY must be set" } ,
56- { status : 500 }
57- ) ;
58- }
59-
60- const name = match [ 1 ] ;
61- const path = match [ 2 ] || "/" ;
62- const sandbox = getSandbox ( env . Sandbox , name ) ;
63-
64- await ensureRunning ( sandbox , env ) ;
65-
66- // Proxy request to container
67- return sandbox . containerFetch (
68- new Request ( `http://localhost${ path } ${ url . search } ` , request ) ,
69- PORT
70- ) ;
71- }
50+ async function proxyToSandbox ( sandbox : Sandbox , request : Request , path : string ) : Promise < Response > {
51+ const query = new URL ( request . url ) . search ;
52+ return sandbox . containerFetch ( new Request ( `http://localhost${ path } ${ query } ` , request ) , PORT ) ;
53+ }
54+
55+ const app = new Hono < AppEnv > ( ) ;
56+
57+ app . onError ( ( error ) => {
58+ return new Response ( String ( error ) , { status : 500 } ) ;
59+ } ) ;
60+
61+ app . post ( "/sandbox/:name/prompt" , async ( c ) => {
62+ if ( ! ( c . req . header ( "content-type" ) ?? "" ) . includes ( "application/json" ) ) {
63+ throw new HTTPException ( 400 , { message : "Content-Type must be application/json" } ) ;
64+ }
65+
66+ let payload : PromptTestRequest ;
67+ try {
68+ payload = await c . req . json < PromptTestRequest > ( ) ;
69+ } catch {
70+ throw new HTTPException ( 400 , { message : "Invalid JSON body" } ) ;
71+ }
72+
73+ const sandbox = await getReadySandbox ( c . req . param ( "name" ) , c . env ) ;
74+ return c . json ( await runPromptTest ( sandbox , payload , PORT ) ) ;
75+ } ) ;
76+
77+ app . all ( "/sandbox/:name/proxy/*" , async ( c ) => {
78+ const sandbox = await getReadySandbox ( c . req . param ( "name" ) , c . env ) ;
79+ const wildcard = c . req . param ( "*" ) ;
80+ const path = wildcard ? `/${ wildcard } ` : "/" ;
81+ return proxyToSandbox ( sandbox , c . req . raw , path ) ;
82+ } ) ;
83+
84+ app . all ( "*" , ( c ) => c . env . ASSETS . fetch ( c . req . raw ) ) ;
7285
73- // Serve frontend assets
74- return env . ASSETS . fetch ( request ) ;
75- } ,
76- } satisfies ExportedHandler < Env [ "Bindings" ] > ;
86+ export default app ;
0 commit comments