From 6208f2a561c58eb8ebd05a0d8cad157fecda8559 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Sat, 15 Nov 2025 14:39:34 -0800 Subject: [PATCH 01/15] openai agents shell example --- examples/openai-agents/Dockerfile | 9 + examples/openai-agents/README.md | 21 + examples/openai-agents/env.d.ts | 8 + examples/openai-agents/index.html | 14 + examples/openai-agents/package.json | 27 + examples/openai-agents/public/favicon.ico | Bin 0 -> 14510 bytes examples/openai-agents/public/normalize.css | 351 ++++++ examples/openai-agents/src/app.tsx | 125 ++ examples/openai-agents/src/index.css | 138 +++ examples/openai-agents/src/index.ts | 246 ++++ examples/openai-agents/tsconfig.json | 15 + examples/openai-agents/vite.config.ts | 7 + examples/openai-agents/wrangler.jsonc | 35 + examples/typescript-validator/index.html | 2 +- examples/typescript-validator/src/index.css | 11 +- examples/typescript-validator/wrangler.jsonc | 2 +- package-lock.json | 1150 +++++++++++++++++- 17 files changed, 2126 insertions(+), 35 deletions(-) create mode 100644 examples/openai-agents/Dockerfile create mode 100644 examples/openai-agents/README.md create mode 100644 examples/openai-agents/env.d.ts create mode 100644 examples/openai-agents/index.html create mode 100644 examples/openai-agents/package.json create mode 100644 examples/openai-agents/public/favicon.ico create mode 100644 examples/openai-agents/public/normalize.css create mode 100644 examples/openai-agents/src/app.tsx create mode 100644 examples/openai-agents/src/index.css create mode 100644 examples/openai-agents/src/index.ts create mode 100644 examples/openai-agents/tsconfig.json create mode 100644 examples/openai-agents/vite.config.ts create mode 100644 examples/openai-agents/wrangler.jsonc diff --git a/examples/openai-agents/Dockerfile b/examples/openai-agents/Dockerfile new file mode 100644 index 00000000..b7798530 --- /dev/null +++ b/examples/openai-agents/Dockerfile @@ -0,0 +1,9 @@ +# This image is unique to this repo, and you'll never need it. +# Whenever you're integrating with sandbox SDK in your own project, +# you should use the official image instead: +# FROM docker.io/cloudflare/sandbox:0.5.0 +# FROM cloudflare/sandbox-test:0.5.0 + +# On a mac, you might need to actively pick up the +# arm64 build of the image. +FROM --platform=linux/arm64 cloudflare/sandbox-test:0.5.0 diff --git a/examples/openai-agents/README.md b/examples/openai-agents/README.md new file mode 100644 index 00000000..132036b0 --- /dev/null +++ b/examples/openai-agents/README.md @@ -0,0 +1,21 @@ +# OpenAI Agents with Cloudflare Sandbox + +Example using OpenAI Agents to execute shell commands in a Cloudflare Sandbox. + +## Setup + +```bash +npm install +npm run types +npm start +``` + +## Usage + +Enter natural language commands in the UI. The agent will execute shell commands in the sandbox and return the results. + +## Deploy + +```bash +npm run deploy +``` diff --git a/examples/openai-agents/env.d.ts b/examples/openai-agents/env.d.ts new file mode 100644 index 00000000..5296fb46 --- /dev/null +++ b/examples/openai-agents/env.d.ts @@ -0,0 +1,8 @@ +/* eslint-disable */ +// Generated by Wrangler by running `wrangler types env.d.ts --include-runtime false` (hash: d70ad9fbe15919b5bf8325ca39967c07) +declare namespace Cloudflare { + interface Env { + Sandbox: DurableObjectNamespace /* Sandbox */; + } +} +interface Env extends Cloudflare.Env {} diff --git a/examples/openai-agents/index.html b/examples/openai-agents/index.html new file mode 100644 index 00000000..71d001ab --- /dev/null +++ b/examples/openai-agents/index.html @@ -0,0 +1,14 @@ + + + + + + OpenAI Agents - Sandbox SDK + + + + +
+ + + diff --git a/examples/openai-agents/package.json b/examples/openai-agents/package.json new file mode 100644 index 00000000..1ec1cbd5 --- /dev/null +++ b/examples/openai-agents/package.json @@ -0,0 +1,27 @@ +{ + "name": "@cloudflare/sandbox-openai-agents-example", + "version": "1.0.0", + "description": "Using OpenAI Agents with Sandbox SDK", + "private": true, + "scripts": { + "start": "vite dev", + "deploy": "vite build && wrangler deploy", + "types": "wrangler types env.d.ts --include-runtime false" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "module", + "dependencies": { + "@openai/agents": "^0.3.2", + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "^1.14.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "vite": "^7.2.2" + } +} diff --git a/examples/openai-agents/public/favicon.ico b/examples/openai-agents/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ade1c1db25d715510f11aa1efaa6be42ba5dbeb3 GIT binary patch literal 14510 zcmc(G2XtH4l`U;gGA*-`8UHvL&(C~&@{@5da+Bkd{J6x9V_R$zdoMCc6{=FCBsQ~% zq)4%d5}Qa-?7jC+0t7$+ECAShucG8`3-6u3?*pIAXq>TJem>T^>j5C%yZfGd?z!jg zeQ0EKhtXX|KlzCfuJ;)|@Ha+A&l(vS-FM&CXMAsm@1A&K>+}CMG79}4Mn(_dH~16o zFno`Tkr96PHLpAT{r&G0e`8}~bC0p{?nfML93Ow~xtAY2bt>eA)bu>lkdVlS*l&pF z4)M3RviElo8Qo!SZnxXm*tWyO*mB*})anz9J+_}mM8tnmU*DmZ%Lk~nRl!|M&h)?K zUL!X*w;!0AI;LA#dXSZs7jI*`pDgycQdDFjwYABqwziR%%au1Ynh|Pj>f~Z#5}sqv z5Hatz*cx*zF&BF2Ho)57tDEkzt5LfH1naM(R@)+B`=jKsl8qLg+kF!6%}+8X<~AP?Cf3Wx4(T2 zS6A}%I(#GJasg>H<8kbMfB(2$aq$Jz*sQ_58??5zjh5sUHb@Q$fHLdeUe^# z?T>n66AOJoK{+igZT`;N`|Q8O#}`j#Ua2RUe1cnCyh`iq>$JMcp26BGFkGejhED!J z|MMYw^syaWaBw)uy46D8VPRP>7ZrC=K|u?3Nrrel6ZfwP&k*j%`4G1y)3W`F;r7whVk=zEOn>lOUO#4=sIx=Cxm!0rc@&*$eisku>caYC#7 zcii_chA|WK^E)nO)cAMH#%XSDQ$IX3LsCgEO-(Lxt1Ii={M;hHFuy@Pis9vwlA2!{ z?(@I1prAnT8TPrdy64LD%r$n8{(}!b;$~;(^=hStAJk6qD&;V>x2bgX^^%v_Z(Cbi z|3>r|_L;rP$|Xe~f1;z>M`~=c8}2fg|V`){>zKSiTlughsNTI@M3}EyS)$t zgmrZ@mVDF4`5Q2Ak1x0Nh^>Ba-gi9w_>XOEZ6Dmd+j^&kxyA3D?VaCnb8>s#)7|}d z*7(la!s)GmfY9Kxk@4vXaVZy~qvPGYyu9vbor!VfwmLR4x>Ni;u0OH1aBwm)vn)2= zwP(Wk-QAy>V*Q(&T9TE89XZ%JldZKA?caBlapX4PH4PCmF_hD#g z#3}K4U*EqFhY`E!P74bsR}&NaaWhj#LgU%h)Wi<&ZTa2icAS-^BWGjf%7urC%LeZxm8JX2Z-?k{hmGUoW&kD-qO;IY^+&xr-(BNq}7fSi=nwWg;ZWvg>}+J-BJZ96lxk67~!Pd z{d`77&Zp65&g}S0;}`ojHnzFP%*G0t|k}EXWKcsX%A(^VH8>q9h8*99mdV4j9BWjXLdq~=?zR}v+O{vLg@qeLz!N;&i z{-e3MOQVg=0kW{%|AnE?!|3lgxw!g~``**^-g{^se%P0eP)2$QX|$6R5D-r1&RwAL z@>*(X?hyLy>FGniN@{DDvIZNdDL1cp7~}lWtr*0y@80beYiDzXKQMu!&Suc)_%bD4NGD56dqO@zQBmh9Jw1<#i)*Q&u>&|dsHV1s>grml zy+f|=?Chb`)XPpcJ2&JR!be-%ea)`!r^#(^fZlUoC>=c(OM$_u6d06B;SpCTCN_`a z&ll3gq!Nma&!vpZRWyygC@?63OtBV^96d!Lp)tUdLD@N_R94M19`oysZ|$S-=Sxm8PTox@bu zpr-2D9-5g$|0e?Il~>+?kM2djbBqE5Bau@iQgTW*rKDz4dPV^-lu=PhwO*+nqLPw| z0z?0Xd@m^}=^s43{I!RVoW-*exX|z`6q`^6>@8GMCZ~obE&OPdWQu9(QO_Z+Ewa2s z$c1L+u2Do}96kU1OIWLR@Iy~JcHAF%%^CPdBE==7QbJ-n?3csrp;z^e0C#f+>tFP{ zLx+5Rec(Xw7bpA@=>3okJ}#k@vT|Fgszyy6U1Ov|pBmi?4UewU*u)x5POZ_{_$n!s z(=;`6oysa2(ErPrgI#1{VUKm{MF+i4(8-hU!(Sti_r+0Mdk+ZDP{K6{oFxJ*+VR4-XhZo`hz%;&y zSYc1kKKmlQ^_B_Zr8U{wxzgUf2Y|yzz!4UH4!LKpUN<;Rs=h&!?fnM_C%kyt{{rll z!)Ii|S8G(H=$WSBku{p0S*Hb-`>h!AHPK`bJ(nJRbO*ik z((jR5{)u)O?0+&K~xBL}V`YQ764dk2Xe=GMiP(7K~X8)ci*q*G0ekLPwy1w;+0q5L@f3AT&)@)P2WhuR^LJ%&^tsrg zFS2u*`19ur`RtrV>=QZ~&@9l*%vJQm`WJHEo7X?bvPO&WLydMG^SXfZR6YI2FMrKH z_|T*LBab}EAvecFpEvZs z%5r}LhPAC6{$?(}WfRuFPPahn-f7&Q%l+(U_j14b^#lC#zj=v{AM@oqTDz!ErAf}r ztpCUDeY5w0fvG2pOS-X7sP%RAD#V*QYG_cx{2M5mH63HHSyP9hyad{trJ zY9(dQ7>$mu;5;PuZ}5Zd{Th4$F#-Ok)z0v(t-aLVD)&*!WN*z+&%c7c?ltrx`1+l< z_Raq0?|FawtpmeaU~8YngQcYvnw(hT z1_q|6sZl`rSuwEWtE(istNrh*4x6$xoD) zNk4CG>&H1!%j3L;=U$^_w3X#e>g!no zvA&(uN*!>_=tqWUZs@d=dbMhVSF1+3{$AF%ysNUZ>EFId-|V+hQ919h{T$=wXJ%HY zALoVT#ZB~k9czyH-Br4N{R0{q8l$F$4o)sZ{1}*|Uey?N<4n=g+%w+TKlnZ&`gt9vx0l&e1*(hPq- ztR2z!tF=RtcKIK+`V;nvZ);<*udli?o&Xf1kptRIo&a3qU+E?AlFVmW{(U>`tNfeU zh0VoR{q6f62kVRB{4RZgFZ#Re?8JW#_3^noAtB*Mj9V3Pvgfis#LwT?l|5HHs@v2RPgJ(Du5!yE}OA_q6i#^fY#Me9!U7 zVV|U+fUvHx(6hr)k@4fPu@_V^G4WTxwK)3u`Tc@Dd&@T8*BC_ZT6kV$WaK@zw$6L@ z?6FNSHMLOgde>~@owrT(#>hF1ckKozVnMsjt;iC5jJ=%`IoLaswWR|&IeL))>2NA6 zs3wV|AA8+6Db*7=%M2seQt^3tl{n+YE(HdL?uY$FM*TaDf!Sfp7R)C1+FH8?n;6^7 znC!9vZ;WPa1I*TFR={n=o0(W~=B8HMp53;bjWzfjYbVay%8fgIEQrg_D&~>1a|=t? z^ur@_oImzMD_QIWHtQBne{>Vg{z;Ta7`)&Gu99N=Hmg z?3Tdcld-WqziXErZ@kN14?c=BF}4F2WzUCPeDw&8jLsr|9_N~xW%^5(vcO?P_-ykf_V%`4 zqMy6Cc-?PiW?yG!?gV=~^1F;3^(LmyoT;fZfmb4MEVO60E1C<=i!R_UfE!$vt&JOe z$BUw)E+YR>&?NGX_D%(5UCF2Xyke@VWZX_Wb%9@!N)#l+82bB%kbjSZXVP#LmCY0% zcVRU#G4T-wTD0Zu3_!$gZSA?k#Kd6?JR3L-SG}2;D`#%*4$SZ2Ead^Lo;c6!BWrMD z);3;bW8*3CVD@$%WQP_Q5RF{06X&NzRDywLvG52$)r>eo`s>@m?)7 zrBVY9t)H4(y7h&Hri838+CMa1Fs7C zaz8j$4Yy%jaWjf+GF9V zx3JttmevPgEIa&SCG&MqgQ+xUW)J4x>Dtoh=6dkkFQVc@+$U0o^~0Ur<< zl|X)e!ITa@uB@~Mc|G_q*uNWGWKT~oDVhDjO?Gw3nSOv4dX-xcy*PfAFlqBNa>v?y?9q2Qb1;qTovyKs#zT+AdZYX^ZB3kUC#c;OOdXP00M4LBpW zLVN7SnL`1a8?ZBOvT8ZprwnR*a#O%>gvk0nAz6 z2G+pMGw3bj5c~$b-w&*;+?ySpj+29vkDlqJjBjS#tM}n3V2&lV4R3b1ZfnT|V z_?$rzQJKW}n&_A-bT%ea;DggISJL3noZ$OzZu=0cY-!)VBj7`WC@kz8MMjP6}DFeD}R!$@3=C^=vY6q9x0`9L0nzEE8XV$NBUhCXU)(NJsqAhmUkP={npcwd3KfX1eN@WgBIp9$m`R^a2` z#2#oXaPoWKJA{}OK<~dFjkOR9KTO74=3sBm7Ot1l^C<^=nnM?}Q?L zg;PjK4ES{L`sXi!Gfo%u7I53(?+Ov4$`MzU$WwKcn_GBFv^%?sz7rM}bD!6K|J8$s z&cHY0_>-rT5D(L_Hj5}JwT99&o2V4JLrq;D_97igx{+T2GpnKK8(4xCuuQt)Rq*et zz`G7DViH={1me%K5I;}*htiH6&k350iHRllZx_VmeRS}k4`Ny%V%ZsDe0)S?JmOd) z#l|ILzB7cmV7`RhgX>WZQBhI3^LG5A?|6IrKg;$_rk9*Nb%75H&lJ|_g`_IM&r8eU z56v3*;kaPu0qqj$hJbYxy2X}$F@?3t+Vm{=)1gJ2OJ;x{wH45l3Xv;6xAR4zzT>Sw z8ADI764ru?izj095%lYay(9?xbEpuT!q3ElzrIKppnGLz7V(nqep09gk#jfv_N(yw z`i8%GzsoIG7m0p zfwcugn}#;hH!zFwtZ^aGH=)ZgUt-z}n+sRWh1b5r*tbsz zd$d2a)9~{4eSw&SoQ~mDA-b zO<1EnSdT-3Zli^6GXme3p4os#01X9rmzE7RG>kiCZJBYX_>OV-BhWF1hUSIe&Y|Yz zLQ*=*2=fxnE6GtFroy23gzvih5C+Q-&= zK?i~c!p1U>xzIufn4Se!y0S^?{t@g07WDALPtb3E^IQ1RtMnRlp+EhJ=~GsMUd3vM z+}$w-@8jfmlEv3p=zaD28R$xKdEcL{T{Zy5wXiGf|0>ggBcif6X8Ww17HE1Z<;YAt}82F)*cDE(e`lwJJS=GzQpEYVqz71?40m@ezqqa8bqYUe|*H-K-xa;`wh)dz& zh&QqMTpD!UV%WY3>y_z=YzjYAgQ`>(&C zhaP&I9(wq3AtvqEu@h_hWqS3sH*i+9VRbJzCdX!=SM;?eB^CU5yYGlG$M1B4b#zP- zL0{%_QM-_wTt@K;`N&_puvhitOgc$}0{*Ll?j`vBP3><>gEZ9N3BJF5EMjdS)5RJ7 z!J#E^3H^v!*OAxc(a(PNKe3JT0P|zw(AR#_vXWIF2Mf_k+-wHdSL%S)K^YIHxzU`rj>2s zUx#f)-PAB2+v*W9Cc*yjC8l+=+5)EQH#MVfaB3BHN}-?LbFYB^fd?LjuKGCt^wZDj zVRwB_Rz5#IGD(uIo?6tq{OfkVVei>X^q+vBlp@rE;;hrHN5i>Cj=j2`E?=(3xf5|; zJ`T+fv11atH~fd$U({X=n(KD$bNhSX-_mB8{n9F@PIu{+s=Vs?qdrQa1x_+G;&9}6)PT-w^@h@`c;+O)1lT!)`JCINI>pMDz5HFDr zqm`FS!8z4~htVSEm_oj@3>^xw|If7NTjv7!x7LC%9$??Vm=+QL$Dt$YbTfQgTOZEn zsuh_;c0?wX9UbT$@EX<)?;7kI{I!3p|4p9x>$D_s&pqWI=flo?ZEYIVFii;SmDR{f zyGNl}w^3zfr=VMFwX@Ji5z8?L>)U+Duokw)v4x%0F^K!X@-p%|?EB;6OW@YVaDLKJ zZFT3BEg$%^JcjuM+HGkeqW=U1CFNx2GTuU?@9UcZ_q#y-@E2A$AXki|zPb^-a1V6Q zNg5empjpK673gvs8=~!TW#bVV%ZCh2(81YW1#Nh0a+wBoGdREMxaMZ$u}#vq8GhJM z(4@C)eJ2@xGo9tssq@c3EB_=Xw+ZV?%?o}Gt(^J#5Y`%7Z%kJxE@}WbtQK_IvC&1? z6BscDLk)?z4;%6|QAb}`T%iSM>5~%+0`}hCQN65tkXoDM_1p9zA$MV8`8KbJsO;bd z|2Jq zBbFO1EMT3?EzZEwl=>F=56=ffqL|&7SzSa z6=VA9s&4G>6NWgyh+2V_YtWFd(+cX$uU-3yh6dR@R8dE}g0vdcaSYGUH2h)#XO|^b zzqPO;=*$fF*eK3iIIHycjgefY<0YN_dP%1SdV9CPdwdVrjgB7=|1omp(N_H50QNh;)MjG&f%9P=?gI>%}e z@EvgXj*(I^#GxKQFO_JxZmAacND{YV#&_}=(f*;K@pmVuR8Mzw4Ksd#n}wgqWJ8Gc zQ?Ms+uWjK+-2|(h5bG9J5l`AtcUpzoyl$x)zA;F>>S0c;8s<<(ukVrTfO&wIb?bQ4 z<5OFU^5dpD$#D_87&k;5-}Y*-f5nJII*R|BSm)N(KSX}80qp=aA2{DLwfB%S|CtfU` z?@>-5MsISPn+AJdzy1;8-ZEm&7{-8kKpolI@&=E%&aW)5@xacb)(8K8v811#Tp(GO zDl9!c{qM9|?SJ9q8E$c(7yJFbu8E0-PvX3y?`R)Dj9lY4HkiM&I-zSq&B_AeiB2<0 z<0Di2!ptJSiX3d2#hLjPT-W)j@p7_L9Nr~2Z)`k+~B|v-`}hIT+=&vRogfC$w05}1GS=WKqgVR zF`a;6W_D&*@!5ZktCQ2wf6B?O9h4$J>+I;^v;)I5k6P1J?3=61zp)SD%FRs7LHF#4 z1Sfwlw8Cdl^Zsx}MTPLcHq6F!la^Kx(%scdsO`~9p(jf^WFNw|ANQ*@e6O { + try { + const response = await fetch('/run', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ input }) + }); + + if (!response.ok) { + throw new Error(`API error: ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error('API call failed:', error); + throw error; + } +} + +function App() { + const [input, setInput] = useState(''); + const [response, setResponse] = useState(null); + const [loading, setLoading] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!input.trim() || loading) return; + + setLoading(true); + setResponse(null); + + try { + const result = await makeApiCall(input); + setResponse(result); + } catch (error) { + console.error('Error:', error); + setResponse({ + naturalResponse: 'An error occurred while processing your request.', + toolResults: [] + }); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+ setInput(e.target.value)} + disabled={loading} + /> +
+ + {loading && ( +
+
Processing...
+
+ )} + + {response && !loading && ( +
+ {response.naturalResponse && ( +
+
Response:
+
{response.naturalResponse}
+
+ )} + + {response.toolResults.length > 0 && ( +
+
Command Results:
+ {response.toolResults.map((result, index) => ( +
+
$ {result.command}
+ {result.stdout && ( +
{result.stdout}
+ )} + {result.stderr && ( +
{result.stderr}
+ )} + {result.exitCode !== null && result.exitCode !== 0 && ( +
+ Exit code: {result.exitCode} +
+ )} +
+ ))} +
+ )} + + {!response.naturalResponse && response.toolResults.length === 0 && ( +
No response received.
+ )} +
+ )} +
+
+ ); +} + +createRoot(document.getElementById('root')!).render(); diff --git a/examples/openai-agents/src/index.css b/examples/openai-agents/src/index.css new file mode 100644 index 00000000..150254d1 --- /dev/null +++ b/examples/openai-agents/src/index.css @@ -0,0 +1,138 @@ +* { + box-sizing: border-box; +} + +body { + margin: 0; + padding: 0; + font-family: + -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', + 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background-color: #ffffff; + color: #1a1a1a; +} + +.app { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; +} + +.container { + width: 100%; + max-width: 600px; +} + +form { + margin-bottom: 1.5rem; +} + +.input { + width: 100%; + padding: 0.75rem 1rem; + font-size: 1rem; + border: 1px solid #808080; + border-radius: 4px; + background-color: #ffffff; + color: #1a1a1a; + outline: none; + transition: border-color 0.2s; +} + +.input:focus { + border-color: #404040; +} + +.input:disabled { + background-color: #f5f5f5; + cursor: not-allowed; +} + +.input::placeholder { + color: #808080; +} + +.output { + border: 1px solid #808080; + border-radius: 4px; + background-color: #fafafa; + padding: 1rem; + min-height: 100px; +} + +.output-section { + margin-bottom: 1.5rem; +} + +.output-section:last-child { + margin-bottom: 0; +} + +.output-label { + font-weight: 600; + color: #1a1a1a; + margin-bottom: 0.5rem; + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.output-content { + color: #1a1a1a; + white-space: pre-wrap; + word-wrap: break-word; + line-height: 1.5; +} + +.tool-result { + margin-bottom: 1rem; + padding: 0.75rem; + background-color: #ffffff; + border: 1px solid #d0d0d0; + border-radius: 4px; +} + +.tool-result:last-child { + margin-bottom: 0; +} + +.tool-command { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.9rem; + color: #404040; + margin-bottom: 0.5rem; + font-weight: 600; +} + +.tool-output { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.85rem; + color: #1a1a1a; + white-space: pre-wrap; + word-wrap: break-word; + margin-top: 0.5rem; + padding-left: 0.5rem; + border-left: 2px solid #808080; +} + +.tool-error { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.85rem; + color: #404040; + white-space: pre-wrap; + word-wrap: break-word; + margin-top: 0.5rem; + padding-left: 0.5rem; + border-left: 2px solid #808080; +} + +.tool-exit-code { + font-size: 0.8rem; + color: #808080; + margin-top: 0.5rem; + font-style: italic; +} diff --git a/examples/openai-agents/src/index.ts b/examples/openai-agents/src/index.ts new file mode 100644 index 00000000..0f779aaf --- /dev/null +++ b/examples/openai-agents/src/index.ts @@ -0,0 +1,246 @@ +import process from 'node:process'; +import { Sandbox, getSandbox } from '@cloudflare/sandbox'; +import { env } from 'cloudflare:workers'; + +export { Sandbox }; // export the Sandbox class for the worker + +import { + Agent, + run, + withTrace, + Shell, + ShellAction, + ShellResult, + ShellOutputResult, + shellTool +} from '@openai/agents'; +// import chalk from 'chalk'; + +// Tool result for API responses +interface ToolResult { + command: string; + stdout: string; + stderr: string; + exitCode: number | null; +} + +class SandboxShell implements Shell { + private sandbox: Sandbox; + private cwd: string = '/workspace'; + public results: ToolResult[] = []; + + constructor(public readonly binding: DurableObjectNamespace) { + // Get a sandbox instance with a consistent ID + this.sandbox = getSandbox(binding, 'shell-session'); + } + + async run(action: ShellAction): Promise { + const output: ShellResult['output'] = []; + + for (const command of action.commands) { + let stdout = ''; + let stderr = ''; + let exitCode: number | null = 0; + let outcome: ShellOutputResult['outcome'] = { + type: 'exit', + exitCode: 0 + }; + try { + const result = await this.sandbox.exec(command, { + timeout: action.timeoutMs, + cwd: this.cwd + }); + stdout = result.stdout; + stderr = result.stderr; + exitCode = result.exitCode; + // exec returns a result even for failed commands, so check success field + // Timeout would be indicated by a specific error or exit code + outcome = { type: 'exit', exitCode }; + } catch (error: any) { + // Handle network/HTTP errors or timeout errors + exitCode = typeof error?.exitCode === 'number' ? error.exitCode : null; + stdout = error?.stdout ?? ''; + stderr = error?.stderr ?? ''; + + // Check if it's a timeout error + const errorMessage = error?.message ?? ''; + if ( + errorMessage.includes('timeout') || + errorMessage.includes('Timeout') || + errorMessage.includes('timed out') + ) { + outcome = { type: 'timeout' }; + } else { + outcome = { type: 'exit', exitCode: exitCode ?? 1 }; + } + } + output.push({ + command, + stdout, + stderr, + outcome + }); + + // Collect results for API responses + const collectedExitCode = + outcome.type === 'exit' ? outcome.exitCode : null; + this.results.push({ + command: String(command), + stdout: String(stdout), + stderr: String(stderr), + exitCode: collectedExitCode + }); + + if (outcome.type === 'timeout') { + break; + } + } + + return { + output, + providerData: { + working_directory: this.cwd + } + }; + } +} + +async function handleRunRequest(request: Request, env: Env): Promise { + try { + // Parse request body + const body = (await request.json()) as { input?: string }; + const input = body.input; + + if (!input || typeof input !== 'string') { + return new Response( + JSON.stringify({ error: 'Missing or invalid input field' }), + { status: 400, headers: { 'Content-Type': 'application/json' } } + ); + } + + // Create shell (automatically collects results) + const shell = new SandboxShell( + env.Sandbox as unknown as DurableObjectNamespace + ); + + // Create agent with auto-approval for web API + const agent = new Agent({ + name: 'Shell Assistant', + model: 'gpt-5.1', + instructions: + 'You can execute shell commands to inspect the repository. Keep responses concise and include command output when helpful.', + tools: [ + shellTool({ + shell, + needsApproval: false // Auto-approve for web API + }) + ] + }); + + // Run the agent + const result = await run(agent, input); + + // Format response + const response = { + naturalResponse: result.finalOutput || null, + toolResults: shell.results + }; + + return new Response(JSON.stringify(response), { + headers: { + 'Content-Type': 'application/json' + } + }); + } catch (error: any) { + console.error('❌ Error handling run request:', error); + return new Response( + JSON.stringify({ + error: error.message || 'Internal server error', + naturalResponse: 'An error occurred while processing your request.', + toolResults: [] + }), + { + status: 500, + headers: { + 'Content-Type': 'application/json' + } + } + ); + } +} + +async function promptShellApproval(commands: string[]): Promise { + if (process.env.SHELL_AUTO_APPROVE === '1') { + return true; + } + + console.log('⚠️ Shell command approval required: \n'); + commands.forEach((cmd) => console.log(` 💻 > ${cmd}`)); + const { createInterface } = await import('node:readline/promises'); + const rl = createInterface({ + input: process.stdin, + output: process.stdout + }); + try { + const answer = await rl.question('\nProceed? [y/N] '); + const approved = answer.trim().toLowerCase(); + return approved === 'y' || approved === 'yes'; + } finally { + rl.close(); + } +} + +async function main() { + const shell = new SandboxShell( + env.Sandbox as unknown as DurableObjectNamespace + ); // hmm this should just work + + const agent = new Agent({ + name: 'Shell Assistant', + model: 'gpt-5.1', + instructions: + 'You can execute shell commands to inspect the repository. Keep responses concise and include command output when helpful.', + tools: [ + shellTool({ + shell, + // could also be a function for you to determine if approval is needed + needsApproval: true, + onApproval: async (_ctx, approvalItem) => { + const commands = + approvalItem.rawItem.type === 'shell_call' + ? approvalItem.rawItem.action.commands + : []; + const approve = await promptShellApproval(commands); + return { approve }; + } + }) + ] + }); + + await withTrace('shell-tool-example', async () => { + const result = await run(agent, 'Show the Node.js version.'); + + console.log(`🤖 Agent: ${result.finalOutput}`); + }); +} + +// main().catch((error) => { +// console.error(error); +// process.exitCode = 1; +// }); + +export default { + async fetch( + request: Request, + env: Env, + ctx: ExecutionContext + ): Promise { + const url = new URL(request.url); + + if (url.pathname === '/run' && request.method === 'POST') { + return handleRunRequest(request, env); + } + + return new Response('Not found', { status: 404 }); + } +}; diff --git a/examples/openai-agents/tsconfig.json b/examples/openai-agents/tsconfig.json new file mode 100644 index 00000000..f06dd47c --- /dev/null +++ b/examples/openai-agents/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["esnext", "dom", "dom.iterable"], + "module": "esnext", + "moduleResolution": "bundler", + "types": ["@types/node", "@cloudflare/workers-types"], + "jsx": "react-jsx", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "noEmit": true + } +} diff --git a/examples/openai-agents/vite.config.ts b/examples/openai-agents/vite.config.ts new file mode 100644 index 00000000..62952875 --- /dev/null +++ b/examples/openai-agents/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { cloudflare } from '@cloudflare/vite-plugin'; + +export default defineConfig({ + plugins: [react(), cloudflare()] +}); diff --git a/examples/openai-agents/wrangler.jsonc b/examples/openai-agents/wrangler.jsonc new file mode 100644 index 00000000..b16e5526 --- /dev/null +++ b/examples/openai-agents/wrangler.jsonc @@ -0,0 +1,35 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "sandbox-openai-agents-example", + "main": "src/index.ts", + "compatibility_date": "2025-11-15", + "compatibility_flags": ["nodejs_compat"], + "observability": { + "enabled": true + }, + "assets": { + "directory": "public" + }, + "containers": [ + { + "class_name": "Sandbox", + "image": "./Dockerfile", + "instance_type": "lite", + "max_instances": 1 + } + ], + "durable_objects": { + "bindings": [ + { + "class_name": "Sandbox", + "name": "Sandbox" + } + ] + }, + "migrations": [ + { + "new_sqlite_classes": ["Sandbox"], + "tag": "v1" + } + ] +} diff --git a/examples/typescript-validator/index.html b/examples/typescript-validator/index.html index 1dd5e4f6..ff0dff72 100644 --- a/examples/typescript-validator/index.html +++ b/examples/typescript-validator/index.html @@ -1,4 +1,4 @@ - + diff --git a/examples/typescript-validator/src/index.css b/examples/typescript-validator/src/index.css index 49b01f39..b4c2c93f 100644 --- a/examples/typescript-validator/src/index.css +++ b/examples/typescript-validator/src/index.css @@ -1,4 +1,4 @@ -@import "tailwindcss"; +@import 'tailwindcss'; @theme { /* Cloudflare Accent Colors */ @@ -31,14 +31,15 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; + font-family: + -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', + 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; background-color: var(--color-bg-cream); } -code, pre { +code, +pre { font-family: 'Monaco', 'Courier New', 'Courier', monospace; } diff --git a/examples/typescript-validator/wrangler.jsonc b/examples/typescript-validator/wrangler.jsonc index f42d6475..d993649f 100644 --- a/examples/typescript-validator/wrangler.jsonc +++ b/examples/typescript-validator/wrangler.jsonc @@ -14,7 +14,7 @@ "containers": [ { "class_name": "Sandbox", - "image": "./Dockerfile", + "image": "./Dockerfile" } ], "durable_objects": { diff --git a/package-lock.json b/package-lock.json index 21fa0c7d..c053c50b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -97,6 +97,23 @@ "wrangler": "4.47.0" } }, + "examples/openai-agents": { + "name": "@cloudflare/sandbox-openai-agents-example", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@openai/agents": "^0.3.2", + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "^1.14.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "vite": "^7.2.2" + } + }, "examples/typescript-validator": { "name": "@cloudflare/sandbox-typescript-validator-example", "version": "1.0.0", @@ -977,6 +994,10 @@ "resolved": "examples/minimal", "link": true }, + "node_modules/@cloudflare/sandbox-openai-agents-example": { + "resolved": "examples/openai-agents", + "link": true + }, "node_modules/@cloudflare/sandbox-typescript-validator-example": { "resolved": "examples/typescript-validator", "link": true @@ -2878,6 +2899,39 @@ "node": ">=6 <7 || >=8" } }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.22.0.tgz", + "integrity": "sha512-VUpl106XVTCpDmTBil2ehgJZjhyLY2QZikzF8NvTXtLRF1CvO5iEE2UNZdVIUer35vFOwMKYeUGbjJtvPWan3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + } + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", @@ -3211,6 +3265,72 @@ "@octokit/openapi-types": "^20.0.0" } }, + "node_modules/@openai/agents": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@openai/agents/-/agents-0.3.2.tgz", + "integrity": "sha512-iMy5d6T0K6kxsPuPl3icAGifVb/7QpvxgXP5LrnPa7JTwKDCCe1XMuOJts5Zi1/JNbHpxWk4HCZ908s2auQSoA==", + "license": "MIT", + "dependencies": { + "@openai/agents-core": "0.3.2", + "@openai/agents-openai": "0.3.2", + "@openai/agents-realtime": "0.3.2", + "debug": "^4.4.0", + "openai": "^6" + }, + "peerDependencies": { + "zod": "^3.25.40 || ^4.0" + } + }, + "node_modules/@openai/agents-core": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@openai/agents-core/-/agents-core-0.3.2.tgz", + "integrity": "sha512-lt8KxmaDFXbGkQKxtuJz79arBvbN/BGvZhjLnVPEGC0p0WoUxGbq2HKG5NX3rajBfZZGA27aTGvYQirSXIUpUg==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "openai": "^6" + }, + "optionalDependencies": { + "@modelcontextprotocol/sdk": "^1.17.2" + }, + "peerDependencies": { + "zod": "^3.25.40 || ^4.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@openai/agents-openai": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@openai/agents-openai/-/agents-openai-0.3.2.tgz", + "integrity": "sha512-Qg0HixgR9pMzQix0x2gVigfx3S2GAWG/akYjSSMDcan4NTd/luGoiJ/ehDNODMhCAudhwM+ciSfwM9AbhJDKmg==", + "license": "MIT", + "dependencies": { + "@openai/agents-core": "0.3.2", + "debug": "^4.4.0", + "openai": "^6" + }, + "peerDependencies": { + "zod": "^3.25.40 || ^4.0" + } + }, + "node_modules/@openai/agents-realtime": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@openai/agents-realtime/-/agents-realtime-0.3.2.tgz", + "integrity": "sha512-T8tOWuMDsk+TsoD0VAzeKDazopEIpGhrpeNunEpIsgIN9wXGI96f2im+BIkPWvPDuaztTmkOul8Zbx1vWhPV5g==", + "license": "MIT", + "dependencies": { + "@openai/agents-core": "0.3.2", + "@types/ws": "^8.18.1", + "debug": "^4.4.0", + "ws": "^8.18.1" + }, + "peerDependencies": { + "zod": "^3.25.40 || ^4.0" + } + }, "node_modules/@oxc-project/runtime": { "version": "0.97.0", "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.97.0.tgz", @@ -4284,7 +4404,6 @@ "version": "24.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -4342,7 +4461,6 @@ "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -4512,6 +4630,20 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "optional": true, + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -4535,6 +4667,41 @@ "node": ">=0.4.0" } }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -4669,6 +4836,40 @@ "dev": true, "license": "MIT" }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "optional": true, + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -4729,6 +4930,16 @@ "@types/react": "^19" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -4739,6 +4950,37 @@ "node": ">=8" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-me-maybe": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", @@ -4970,6 +5212,29 @@ "dev": true, "license": "MIT" }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -4987,11 +5252,35 @@ "node": ">=18" } }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "optional": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -5072,6 +5361,16 @@ "dev": true, "license": "MIT" }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/deprecation": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", @@ -5182,6 +5481,28 @@ } } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT", + "optional": true + }, "node_modules/electron-to-chromium": { "version": "1.5.243", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.243.tgz", @@ -5199,6 +5520,16 @@ "node": ">=14" } }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.3", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", @@ -5237,6 +5568,26 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -5244,6 +5595,19 @@ "dev": true, "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "optional": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.11", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", @@ -5296,6 +5660,13 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT", + "optional": true + }, "node_modules/escape-string-regexp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", @@ -5342,6 +5713,39 @@ "@types/estree": "^1.0.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "optional": true, + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/exit-hook": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", @@ -5365,26 +5769,102 @@ "node": ">=12.0.0" } }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "dev": true, - "license": "MIT" - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "optional": true, + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, - "node_modules/extendable-error": { - "version": "0.1.7", + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extendable-error": { + "version": "0.1.7", "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==", "dev": true, "license": "MIT" }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT", + "optional": true + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -5402,6 +5882,23 @@ "node": ">=8.6.0" } }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -5445,6 +5942,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -5466,6 +5981,26 @@ "dev": true, "license": "ISC" }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -5496,6 +6031,16 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5506,6 +6051,31 @@ "node": ">=6.9.0" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-port": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", @@ -5519,6 +6089,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "optional": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-tsconfig": { "version": "4.13.0", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", @@ -5573,6 +6157,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -5612,6 +6209,32 @@ "dev": true, "license": "MIT" }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", @@ -5669,6 +6292,33 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/human-id": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.2.tgz", @@ -5683,7 +6333,7 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -5706,12 +6356,29 @@ "node": ">= 4" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC", + "optional": true + }, "node_modules/inline-style-parser": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", "license": "MIT" }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -5808,6 +6475,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT", + "optional": true + }, "node_modules/is-subdir": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz", @@ -5848,7 +6522,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/jiti": { @@ -5894,6 +6568,13 @@ "node": ">=6" } }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT", + "optional": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -6458,6 +7139,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mdast-util-find-and-replace": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", @@ -6728,6 +7419,29 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -7328,6 +8042,29 @@ "node": ">=10.0.0" } }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "optional": true, + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/miniflare": { "version": "4.20251011.1", "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20251011.1.tgz", @@ -7458,6 +8195,16 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -7496,6 +8243,19 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/obug": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/obug/-/obug-1.0.0.tgz", @@ -7522,11 +8282,24 @@ "dev": true, "license": "MIT" }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "optional": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -7657,6 +8430,16 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7671,7 +8454,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -7741,6 +8524,16 @@ "node": ">=6" } }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/pkg-pr-new": { "version": "0.0.60", "resolved": "https://registry.npmjs.org/pkg-pr-new/-/pkg-pr-new-0.0.60.tgz", @@ -7853,6 +8646,36 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "optional": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/quansync": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", @@ -7940,6 +8763,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", + "optional": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/react": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", @@ -8114,6 +8963,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -8282,6 +9141,34 @@ "fsevents": "~2.3.2" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -8306,11 +9193,32 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/scheduler": { @@ -8332,6 +9240,52 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC", + "optional": true + }, "node_modules/sharp": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", @@ -8376,7 +9330,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -8389,7 +9343,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -8527,6 +9481,82 @@ "win32" ] }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "optional": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "optional": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -8640,6 +9670,16 @@ "dev": true, "license": "MIT" }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/std-env": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", @@ -8898,6 +9938,16 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", @@ -9156,6 +10206,21 @@ "node": ">=4" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "optional": true, + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -9205,7 +10270,6 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, "license": "MIT" }, "node_modules/unenv": { @@ -9322,6 +10386,16 @@ "node": ">= 4.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unrun": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/unrun/-/unrun-0.2.9.tgz", @@ -9401,6 +10475,16 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -9683,7 +10767,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -10419,14 +11503,13 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -10481,6 +11564,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -10498,6 +11582,16 @@ "node": ">=20" } }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "optional": true, + "peerDependencies": { + "zod": "^3.24.1" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", From 1a83e0f84c0f33957e91fd02437ae7fc7079776c Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Sat, 15 Nov 2025 14:58:54 -0800 Subject: [PATCH 02/15] remove dead code --- examples/openai-agents/src/index.ts | 67 +---------------------------- 1 file changed, 1 insertion(+), 66 deletions(-) diff --git a/examples/openai-agents/src/index.ts b/examples/openai-agents/src/index.ts index 0f779aaf..a6f11f83 100644 --- a/examples/openai-agents/src/index.ts +++ b/examples/openai-agents/src/index.ts @@ -1,20 +1,15 @@ -import process from 'node:process'; import { Sandbox, getSandbox } from '@cloudflare/sandbox'; -import { env } from 'cloudflare:workers'; - export { Sandbox }; // export the Sandbox class for the worker import { Agent, run, - withTrace, Shell, ShellAction, ShellResult, ShellOutputResult, shellTool } from '@openai/agents'; -// import chalk from 'chalk'; // Tool result for API responses interface ToolResult { @@ -169,71 +164,11 @@ async function handleRunRequest(request: Request, env: Env): Promise { } } -async function promptShellApproval(commands: string[]): Promise { - if (process.env.SHELL_AUTO_APPROVE === '1') { - return true; - } - - console.log('⚠️ Shell command approval required: \n'); - commands.forEach((cmd) => console.log(` 💻 > ${cmd}`)); - const { createInterface } = await import('node:readline/promises'); - const rl = createInterface({ - input: process.stdin, - output: process.stdout - }); - try { - const answer = await rl.question('\nProceed? [y/N] '); - const approved = answer.trim().toLowerCase(); - return approved === 'y' || approved === 'yes'; - } finally { - rl.close(); - } -} - -async function main() { - const shell = new SandboxShell( - env.Sandbox as unknown as DurableObjectNamespace - ); // hmm this should just work - - const agent = new Agent({ - name: 'Shell Assistant', - model: 'gpt-5.1', - instructions: - 'You can execute shell commands to inspect the repository. Keep responses concise and include command output when helpful.', - tools: [ - shellTool({ - shell, - // could also be a function for you to determine if approval is needed - needsApproval: true, - onApproval: async (_ctx, approvalItem) => { - const commands = - approvalItem.rawItem.type === 'shell_call' - ? approvalItem.rawItem.action.commands - : []; - const approve = await promptShellApproval(commands); - return { approve }; - } - }) - ] - }); - - await withTrace('shell-tool-example', async () => { - const result = await run(agent, 'Show the Node.js version.'); - - console.log(`🤖 Agent: ${result.finalOutput}`); - }); -} - -// main().catch((error) => { -// console.error(error); -// process.exitCode = 1; -// }); - export default { async fetch( request: Request, env: Env, - ctx: ExecutionContext + _ctx: ExecutionContext ): Promise { const url = new URL(request.url); From 861c18aa3181974c2a1a7f1f85538cd355b784b5 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Sat, 15 Nov 2025 23:37:00 -0800 Subject: [PATCH 03/15] Add file editing and improved logging to OpenAI agents example Introduces file creation, update, and deletion capabilities via a new WorkspaceEditor and applyPatchTool integration. Adds a logger utility for structured debug/info/error output throughout the backend. Refactors the UI to support persistent chat history, multi-column output, and improved message rendering. Updates dependencies to include Sandpack React for future file editing features. Improves documentation and exposes port 8080 in Dockerfile for local development. --- examples/openai-agents/Dockerfile | 3 + examples/openai-agents/README.md | 19 +- examples/openai-agents/package.json | 1 + examples/openai-agents/src/app.tsx | 302 ++++++++++++--- examples/openai-agents/src/index.css | 189 +++++++++- examples/openai-agents/src/index.ts | 376 ++++++++++++++++++- examples/openai-agents/src/logger.ts | 41 ++ package-lock.json | 536 ++++++++++++++++++++++++++- 8 files changed, 1381 insertions(+), 86 deletions(-) create mode 100644 examples/openai-agents/src/logger.ts diff --git a/examples/openai-agents/Dockerfile b/examples/openai-agents/Dockerfile index b7798530..6d82a757 100644 --- a/examples/openai-agents/Dockerfile +++ b/examples/openai-agents/Dockerfile @@ -7,3 +7,6 @@ # On a mac, you might need to actively pick up the # arm64 build of the image. FROM --platform=linux/arm64 cloudflare/sandbox-test:0.5.0 + +# Required during local development to access exposed ports +EXPOSE 8080 diff --git a/examples/openai-agents/README.md b/examples/openai-agents/README.md index 132036b0..8de96674 100644 --- a/examples/openai-agents/README.md +++ b/examples/openai-agents/README.md @@ -1,18 +1,29 @@ # OpenAI Agents with Cloudflare Sandbox -Example using OpenAI Agents to execute shell commands in a Cloudflare Sandbox. +A conversational AI assistant that executes shell commands and edits files in a Cloudflare Sandbox. ## Setup +Create a `.env` file with your OpenAI API key: + +``` +OPENAI_API_KEY=your-api-key-here +``` + +Then start the development server: + ```bash -npm install -npm run types npm start ``` ## Usage -Enter natural language commands in the UI. The agent will execute shell commands in the sandbox and return the results. +Enter natural language commands in the chat interface. The assistant can: + +- Execute shell commands +- Create, edit, and delete files + +All conversations are saved in your browser's localStorage. ## Deploy diff --git a/examples/openai-agents/package.json b/examples/openai-agents/package.json index 1ec1cbd5..3e25a439 100644 --- a/examples/openai-agents/package.json +++ b/examples/openai-agents/package.json @@ -13,6 +13,7 @@ "license": "ISC", "type": "module", "dependencies": { + "@codesandbox/sandpack-react": "^2.20.0", "@openai/agents": "^0.3.2", "react": "^19.2.0", "react-dom": "^19.2.0" diff --git a/examples/openai-agents/src/app.tsx b/examples/openai-agents/src/app.tsx index ef3d5bf7..623d5b7b 100644 --- a/examples/openai-agents/src/app.tsx +++ b/examples/openai-agents/src/app.tsx @@ -1,19 +1,39 @@ import { createRoot } from 'react-dom/client'; -import { useState } from 'react'; +import { useState, useEffect, useRef } from 'react'; import './index.css'; -interface ToolResult { +interface CommandResult { command: string; stdout: string; stderr: string; exitCode: number | null; + timestamp: number; +} + +interface FileOperationResult { + operation: 'create' | 'update' | 'delete'; + path: string; + status: 'completed' | 'failed'; + output: string; + error?: string; + timestamp: number; } interface Response { naturalResponse: string | null; - toolResults: ToolResult[]; + commandResults: CommandResult[]; + fileOperations?: FileOperationResult[]; +} + +interface Message { + id: string; + input: string; + response: Response | null; + timestamp: number; } +const STORAGE_KEY = 'openai-agents-history'; + async function makeApiCall(input: string): Promise { try { const response = await fetch('/run', { @@ -37,35 +57,252 @@ async function makeApiCall(input: string): Promise { function App() { const [input, setInput] = useState(''); - const [response, setResponse] = useState(null); + const [messages, setMessages] = useState([]); const [loading, setLoading] = useState(false); + const messagesEndRef = useRef(null); + const inputRef = useRef(null); + + // Load messages from localStorage on mount + useEffect(() => { + const saved = localStorage.getItem(STORAGE_KEY); + if (saved) { + try { + const parsed = JSON.parse(saved); + setMessages(parsed); + } catch (error) { + console.error('Error loading history:', error); + } + } + }, []); + + // Save messages to localStorage whenever they change + useEffect(() => { + if (messages.length > 0) { + localStorage.setItem(STORAGE_KEY, JSON.stringify(messages)); + } + }, [messages]); + + // Scroll to bottom whenever messages change + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages, loading]); + + // Focus input on mount + useEffect(() => { + inputRef.current?.focus(); + }, []); + + // Focus input after response comes in + useEffect(() => { + if (!loading && messages.length > 0) { + // Small delay to ensure DOM is updated + setTimeout(() => { + inputRef.current?.focus(); + }, 100); + } + }, [loading, messages.length]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!input.trim() || loading) return; + const userInput = input.trim(); + setInput(''); setLoading(true); - setResponse(null); + + // Add user message immediately + const userMessage: Message = { + id: Date.now().toString(), + input: userInput, + response: null, + timestamp: Date.now() + }; + setMessages((prev) => [...prev, userMessage]); try { - const result = await makeApiCall(input); - setResponse(result); + const result = await makeApiCall(userInput); + // Update the message with the response + setMessages((prev) => + prev.map((msg) => + msg.id === userMessage.id ? { ...msg, response: result } : msg + ) + ); } catch (error) { console.error('Error:', error); - setResponse({ + const errorResponse: Response = { naturalResponse: 'An error occurred while processing your request.', - toolResults: [] - }); + commandResults: [], + fileOperations: [] + }; + setMessages((prev) => + prev.map((msg) => + msg.id === userMessage.id ? { ...msg, response: errorResponse } : msg + ) + ); } finally { setLoading(false); } }; + const clearHistory = () => { + if (confirm('Are you sure you want to clear all history?')) { + setMessages([]); + localStorage.removeItem(STORAGE_KEY); + } + }; + + const renderMessage = (message: Message) => ( +
+
+
You:
+
{message.input}
+
+ + {message.response ? ( +
+
+
+
+
Response:
+ {message.response.naturalResponse ? ( +
+ {message.response.naturalResponse} +
+ ) : ( +
No response received.
+ )} +
+
+ +
+ {(() => { + // Combine and sort all results by timestamp + const allResults = [ + ...message.response.commandResults.map((r) => ({ + type: 'command' as const, + ...r + })), + ...(message.response.fileOperations || []).map((r) => ({ + type: 'file' as const, + ...r + })) + ].sort((a, b) => a.timestamp - b.timestamp); + + if (allResults.length === 0) { + return ( +
+
Results:
+
+ No operations performed. +
+
+ ); + } + + return ( +
+
Results:
+ {allResults.map((result, index) => { + const timestamp = new Date( + result.timestamp + ).toLocaleTimeString(); + + if (result.type === 'command') { + return ( +
+
+
+ $ {result.command} +
+
{timestamp}
+
+ {result.stdout && ( +
{result.stdout}
+ )} + {result.stderr && ( +
{result.stderr}
+ )} + {result.exitCode !== null && + result.exitCode !== 0 && ( +
+ Exit code: {result.exitCode} +
+ )} +
+ ); + } else { + return ( +
+
+
+ {result.operation === 'create' && '📄 Create'} + {result.operation === 'update' && '✏️ Update'} + {result.operation === 'delete' && + '🗑️ Delete'}{' '} + {result.path} +
+
{timestamp}
+
+
+ {result.output} +
+ {result.error && ( +
+ Error: {result.error} +
+ )} +
+ ); + } + })} +
+ ); + })()} +
+
+
+ ) : ( +
+
Processing...
+
+ )} +
+ ); + return (
-
+
+

Sandbox Studio

+ {messages.length > 0 && ( + + )} +
+ +
+ {messages.length === 0 ? ( +
+
+ Start a conversation by entering a command below. +
+
+ ) : ( + messages.map(renderMessage) + )} +
+
+ + - - {loading && ( -
-
Processing...
-
- )} - - {response && !loading && ( -
- {response.naturalResponse && ( -
-
Response:
-
{response.naturalResponse}
-
- )} - - {response.toolResults.length > 0 && ( -
-
Command Results:
- {response.toolResults.map((result, index) => ( -
-
$ {result.command}
- {result.stdout && ( -
{result.stdout}
- )} - {result.stderr && ( -
{result.stderr}
- )} - {result.exitCode !== null && result.exitCode !== 0 && ( -
- Exit code: {result.exitCode} -
- )} -
- ))} -
- )} - - {!response.naturalResponse && response.toolResults.length === 0 && ( -
No response received.
- )} -
- )}
); diff --git a/examples/openai-agents/src/index.css b/examples/openai-agents/src/index.css index 150254d1..0bb3ccd6 100644 --- a/examples/openai-agents/src/index.css +++ b/examples/openai-agents/src/index.css @@ -17,18 +17,121 @@ body { .app { min-height: 100vh; display: flex; - align-items: center; - justify-content: center; - padding: 1rem; + flex-direction: column; + padding: 0; } .container { width: 100%; - max-width: 600px; + max-width: 1200px; + margin: 0 auto; + height: 100vh; + display: flex; + flex-direction: column; + padding: 1rem; } -form { - margin-bottom: 1.5rem; +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + padding-bottom: 1rem; + border-bottom: 1px solid #d0d0d0; +} + +.app-title { + margin: 0; + font-size: 1.5rem; + font-weight: 600; + color: #1a1a1a; +} + +.clear-button { + padding: 0.5rem 1rem; + font-size: 0.9rem; + background-color: #f5f5f5; + color: #1a1a1a; + border: 1px solid #808080; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.2s; +} + +.clear-button:hover { + background-color: #e0e0e0; +} + +.messages-container { + flex: 1; + overflow-y: auto; + margin-bottom: 1rem; + padding-right: 0.5rem; +} + +.messages-container::-webkit-scrollbar { + width: 8px; +} + +.messages-container::-webkit-scrollbar-track { + background: #f5f5f5; + border-radius: 4px; +} + +.messages-container::-webkit-scrollbar-thumb { + background: #808080; + border-radius: 4px; +} + +.messages-container::-webkit-scrollbar-thumb:hover { + background: #404040; +} + +.empty-state { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + padding: 2rem; +} + +.message { + margin-bottom: 2rem; +} + +.message:last-child { + margin-bottom: 0; +} + +.message-input { + margin-bottom: 1rem; + padding: 0.75rem; + background-color: #f0f0f0; + border-radius: 4px; +} + +.message-label { + font-weight: 600; + color: #404040; + margin-bottom: 0.5rem; + font-size: 0.9rem; +} + +.message-content { + color: #1a1a1a; + white-space: pre-wrap; + word-wrap: break-word; + line-height: 1.5; +} + +.message-response { + margin-left: 1rem; +} + +.input-form { + margin-top: auto; + padding-top: 1rem; + border-top: 1px solid #d0d0d0; } .input { @@ -64,6 +167,64 @@ form { min-height: 100px; } +.output-columns { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.5rem; +} + +.output-column { + display: flex; + flex-direction: column; +} + +.output-column-left { + border-right: 1px solid #d0d0d0; + padding-right: 1.5rem; + max-height: 400px; + overflow-y: auto; +} + +.output-column-left::-webkit-scrollbar { + width: 6px; +} + +.output-column-left::-webkit-scrollbar-track { + background: #f5f5f5; + border-radius: 4px; +} + +.output-column-left::-webkit-scrollbar-thumb { + background: #808080; + border-radius: 4px; +} + +.output-column-left::-webkit-scrollbar-thumb:hover { + background: #404040; +} + +.output-column-right { + padding-left: 1.5rem; +} + +@media (max-width: 768px) { + .output-columns { + grid-template-columns: 1fr; + } + + .output-column-left { + border-right: none; + border-bottom: 1px solid #d0d0d0; + padding-right: 0; + padding-bottom: 1.5rem; + margin-bottom: 1.5rem; + } + + .output-column-right { + padding-left: 0; + } +} + .output-section { margin-bottom: 1.5rem; } @@ -100,12 +261,26 @@ form { margin-bottom: 0; } +.tool-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; +} + .tool-command { font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.9rem; color: #404040; - margin-bottom: 0.5rem; font-weight: 600; + flex: 1; +} + +.tool-timestamp { + font-size: 0.75rem; + color: #808080; + margin-left: 0.5rem; + white-space: nowrap; } .tool-output { diff --git a/examples/openai-agents/src/index.ts b/examples/openai-agents/src/index.ts index a6f11f83..10daeb85 100644 --- a/examples/openai-agents/src/index.ts +++ b/examples/openai-agents/src/index.ts @@ -8,31 +8,50 @@ import { ShellAction, ShellResult, ShellOutputResult, - shellTool + shellTool, + applyPatchTool, + ApplyPatchOperation, + ApplyPatchResult, + applyDiff, + Editor } from '@openai/agents'; -// Tool result for API responses -interface ToolResult { +import { logger } from './logger'; + +// Command result for API responses +interface CommandResult { command: string; stdout: string; stderr: string; exitCode: number | null; + timestamp: number; +} + +// File operation result for API responses +interface FileOperationResult { + operation: 'create' | 'update' | 'delete'; + path: string; + status: 'completed' | 'failed'; + output: string; + error?: string; + timestamp: number; } class SandboxShell implements Shell { - private sandbox: Sandbox; private cwd: string = '/workspace'; - public results: ToolResult[] = []; + public results: CommandResult[] = []; - constructor(public readonly binding: DurableObjectNamespace) { - // Get a sandbox instance with a consistent ID - this.sandbox = getSandbox(binding, 'shell-session'); - } + constructor(private readonly sandbox: Sandbox) {} async run(action: ShellAction): Promise { + logger.debug('SandboxShell.run called', { + commands: action.commands, + timeout: action.timeoutMs + }); const output: ShellResult['output'] = []; for (const command of action.commands) { + logger.debug('Executing command', { command, cwd: this.cwd }); let stdout = ''; let stderr = ''; let exitCode: number | null = 0; @@ -51,6 +70,25 @@ class SandboxShell implements Shell { // exec returns a result even for failed commands, so check success field // Timeout would be indicated by a specific error or exit code outcome = { type: 'exit', exitCode }; + + logger.debug('Command executed successfully', { + command, + exitCode, + stdoutLength: stdout.length, + stderrLength: stderr.length + }); + + // Log warnings for non-zero exit codes or stderr output + if (exitCode !== 0) { + logger.warn(`Command failed with exit code ${exitCode}`, { + command, + stderr + }); + } else if (stderr) { + logger.warn(`Command produced stderr output`, { command, stderr }); + } else { + logger.info(`Command completed successfully`, { command }); + } } catch (error: any) { // Handle network/HTTP errors or timeout errors exitCode = typeof error?.exitCode === 'number' ? error.exitCode : null; @@ -64,8 +102,17 @@ class SandboxShell implements Shell { errorMessage.includes('Timeout') || errorMessage.includes('timed out') ) { + logger.error(`Command timed out`, { + command, + timeout: action.timeoutMs + }); outcome = { type: 'timeout' }; } else { + logger.error(`Error executing command`, { + command, + error: errorMessage || error, + exitCode + }); outcome = { type: 'exit', exitCode: exitCode ?? 1 }; } } @@ -79,18 +126,30 @@ class SandboxShell implements Shell { // Collect results for API responses const collectedExitCode = outcome.type === 'exit' ? outcome.exitCode : null; + const timestamp = Date.now(); this.results.push({ command: String(command), stdout: String(stdout), stderr: String(stderr), - exitCode: collectedExitCode + exitCode: collectedExitCode, + timestamp + }); + logger.debug('Result collected', { + command, + exitCode: collectedExitCode, + timestamp }); if (outcome.type === 'timeout') { + logger.warn('Breaking command loop due to timeout'); break; } } + logger.debug('SandboxShell.run completed', { + totalCommands: action.commands.length, + resultsCount: this.results.length + }); return { output, providerData: { @@ -100,59 +159,328 @@ class SandboxShell implements Shell { } } +class WorkspaceEditor implements Editor { + public results: FileOperationResult[] = []; + + constructor( + private readonly sandbox: Sandbox, + private readonly root: string = '/workspace' + ) {} + + async createFile( + operation: Extract + ): Promise { + const targetPath = this.resolve(operation.path); + logger.debug('WorkspaceEditor.createFile called', { + path: operation.path, + targetPath + }); + + try { + // Create parent directory if needed + const dirPath = this.getDirname(targetPath); + if (dirPath !== this.root && dirPath !== '/') { + logger.debug('Creating parent directory', { dirPath }); + await this.sandbox.mkdir(dirPath, { recursive: true }); + } + + const content = applyDiff('', operation.diff, 'create'); + logger.debug('Writing file content', { + path: targetPath, + contentLength: content.length + }); + await this.sandbox.writeFile(targetPath, content, { encoding: 'utf-8' }); + const timestamp = Date.now(); + const result: FileOperationResult = { + operation: 'create', + path: operation.path, + status: 'completed', + output: `Created ${operation.path}`, + timestamp + }; + this.results.push(result); + logger.info('File created successfully', { + path: operation.path, + timestamp + }); + return { status: 'completed', output: `Created ${operation.path}` }; + } catch (error: any) { + const timestamp = Date.now(); + const result: FileOperationResult = { + operation: 'create', + path: operation.path, + status: 'failed', + output: `Failed to create ${operation.path}`, + error: error?.message || String(error), + timestamp + }; + this.results.push(result); + logger.error('Failed to create file', { + path: operation.path, + error: error?.message || error + }); + throw error; + } + } + + async updateFile( + operation: Extract + ): Promise { + const targetPath = this.resolve(operation.path); + logger.debug('WorkspaceEditor.updateFile called', { + path: operation.path, + targetPath + }); + + try { + let original: string; + try { + logger.debug('Reading original file', { path: targetPath }); + const fileInfo = await this.sandbox.readFile(targetPath, { + encoding: 'utf-8' + }); + original = fileInfo.content; + logger.debug('Original file read', { + path: targetPath, + originalLength: original.length + }); + } catch (error: any) { + // Sandbox API may throw errors for missing files + if ( + error?.message?.includes('not found') || + error?.message?.includes('ENOENT') || + error?.status === 404 + ) { + logger.error('Cannot update missing file', { path: operation.path }); + throw new Error(`Cannot update missing file: ${operation.path}`); + } + logger.error('Error reading file', { + path: operation.path, + error: error?.message || error + }); + throw error; + } + + const patched = applyDiff(original, operation.diff); + logger.debug('Applied diff', { + path: targetPath, + originalLength: original.length, + patchedLength: patched.length + }); + await this.sandbox.writeFile(targetPath, patched, { encoding: 'utf-8' }); + const timestamp = Date.now(); + const result: FileOperationResult = { + operation: 'update', + path: operation.path, + status: 'completed', + output: `Updated ${operation.path}`, + timestamp + }; + this.results.push(result); + logger.info('File updated successfully', { + path: operation.path, + timestamp + }); + return { status: 'completed', output: `Updated ${operation.path}` }; + } catch (error: any) { + const timestamp = Date.now(); + const result: FileOperationResult = { + operation: 'update', + path: operation.path, + status: 'failed', + output: `Failed to update ${operation.path}`, + error: error?.message || String(error), + timestamp + }; + this.results.push(result); + logger.error('Failed to update file', { + path: operation.path, + error: error?.message || error + }); + throw error; + } + } + + async deleteFile( + operation: Extract + ): Promise { + const targetPath = this.resolve(operation.path); + logger.debug('WorkspaceEditor.deleteFile called', { + path: operation.path, + targetPath + }); + + try { + await this.sandbox.deleteFile(targetPath); + const timestamp = Date.now(); + const result: FileOperationResult = { + operation: 'delete', + path: operation.path, + status: 'completed', + output: `Deleted ${operation.path}`, + timestamp + }; + this.results.push(result); + logger.info('File deleted successfully', { + path: operation.path, + timestamp + }); + return { status: 'completed', output: `Deleted ${operation.path}` }; + } catch (error: any) { + const timestamp = Date.now(); + const result: FileOperationResult = { + operation: 'delete', + path: operation.path, + status: 'failed', + output: `Failed to delete ${operation.path}`, + error: error?.message || String(error), + timestamp + }; + this.results.push(result); + logger.error('Failed to delete file', { + path: operation.path, + error: error?.message || error + }); + throw error; + } + } + + private resolve(relativePath: string): string { + // Remove leading ./ or / if present, then join with root + const normalized = relativePath.replace(/^\.\//, '').replace(/^\//, ''); + const resolved = normalized ? `${this.root}/${normalized}` : this.root; + // Ensure the resolved path is within the workspace + if (!resolved.startsWith(this.root)) { + throw new Error(`Operation outside workspace: ${relativePath}`); + } + // Normalize path separators + return resolved.replace(/\/+/g, '/'); + } + + private getDirname(filePath: string): string { + const lastSlash = filePath.lastIndexOf('/'); + if (lastSlash === -1) { + return '/'; + } + return filePath.substring(0, lastSlash) || '/'; + } + + async seedWorkspace(): Promise { + logger.debug('Seeding workspace', { root: this.root }); + await this.sandbox.mkdir(this.root, { recursive: true }); + logger.info('Workspace seeded', { root: this.root }); + } +} + async function handleRunRequest(request: Request, env: Env): Promise { + logger.debug('handleRunRequest called', { + method: request.method, + url: request.url + }); + try { // Parse request body + logger.debug('Parsing request body'); const body = (await request.json()) as { input?: string }; const input = body.input; if (!input || typeof input !== 'string') { + logger.warn('Invalid or missing input field', { input }); return new Response( JSON.stringify({ error: 'Missing or invalid input field' }), { status: 400, headers: { 'Content-Type': 'application/json' } } ); } - // Create shell (automatically collects results) - const shell = new SandboxShell( - env.Sandbox as unknown as DurableObjectNamespace + logger.info('Processing request', { inputLength: input.length }); + + // Get sandbox instance (reused for both shell and editor) + logger.debug('Getting sandbox instance', { + sessionId: 'workspace-session' + }); + const sandbox = getSandbox( + env.Sandbox as unknown as DurableObjectNamespace, + 'workspace-session' ); - // Create agent with auto-approval for web API + // Create shell (automatically collects results) + logger.debug('Creating SandboxShell'); + const shell = new SandboxShell(sandbox); + + // Create workspace editor + logger.debug('Creating WorkspaceEditor', { root: '/workspace' }); + const editor = new WorkspaceEditor(sandbox, '/workspace'); + + // this seems to hang operations, so just commenting out for now + // await editor.seedWorkspace(); + + // Create agent with both shell and patch tools, auto-approval for web API + logger.debug('Creating Agent', { + name: 'Sandbox Studio', + model: 'gpt-5.1' + }); const agent = new Agent({ - name: 'Shell Assistant', + name: 'Sandbox Studio', model: 'gpt-5.1', instructions: - 'You can execute shell commands to inspect the repository. Keep responses concise and include command output when helpful.', + 'You can execute shell commands and edit files in the workspace. Use shell commands to inspect the repository and the apply_patch tool to create, update, or delete files. Keep responses concise and include command output when helpful.', tools: [ shellTool({ shell, needsApproval: false // Auto-approve for web API + }), + applyPatchTool({ + editor, + needsApproval: false // Auto-approve for web API }) ] }); // Run the agent + logger.info('Running agent', { input }); const result = await run(agent, input); + logger.debug('Agent run completed', { + hasOutput: !!result.finalOutput, + outputLength: result.finalOutput?.length || 0 + }); + + // Combine and sort all results by timestamp + const allResults = [ + ...shell.results.map((r) => ({ type: 'command' as const, ...r })), + ...editor.results.map((r) => ({ type: 'file' as const, ...r })) + ].sort((a, b) => a.timestamp - b.timestamp); - // Format response + logger.debug('Results collected', { + commandResults: shell.results.length, + fileOperations: editor.results.length, + totalResults: allResults.length + }); + + // Format response with combined and sorted results const response = { naturalResponse: result.finalOutput || null, - toolResults: shell.results + commandResults: shell.results.sort((a, b) => a.timestamp - b.timestamp), + fileOperations: editor.results.sort((a, b) => a.timestamp - b.timestamp), + allResults }; + logger.info('Request completed successfully'); return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' } }); } catch (error: any) { - console.error('❌ Error handling run request:', error); + logger.error('Error handling run request', { + error: error?.message || error, + stack: error?.stack + }); return new Response( JSON.stringify({ error: error.message || 'Internal server error', naturalResponse: 'An error occurred while processing your request.', - toolResults: [] + commandResults: [], + fileOperations: [] }), { status: 500, @@ -171,11 +499,19 @@ export default { _ctx: ExecutionContext ): Promise { const url = new URL(request.url); + logger.debug('Fetch handler called', { + pathname: url.pathname, + method: request.method + }); if (url.pathname === '/run' && request.method === 'POST') { return handleRunRequest(request, env); } + logger.warn('Route not found', { + pathname: url.pathname, + method: request.method + }); return new Response('Not found', { status: 404 }); } }; diff --git a/examples/openai-agents/src/logger.ts b/examples/openai-agents/src/logger.ts new file mode 100644 index 00000000..13e338c1 --- /dev/null +++ b/examples/openai-agents/src/logger.ts @@ -0,0 +1,41 @@ +// Global flag to enable/disable logging +const DEBUG = true; + +class Logger { + private enabled: boolean; + + constructor(enabled: boolean = DEBUG) { + this.enabled = enabled; + } + + log(message: string, ...args: any[]): void { + if (this.enabled) { + console.log(`[LOG] ${message}`, ...args); + } + } + + info(message: string, ...args: any[]): void { + if (this.enabled) { + console.info(`[INFO] ${message}`, ...args); + } + } + + warn(message: string, ...args: any[]): void { + if (this.enabled) { + console.warn(`[WARN] ${message}`, ...args); + } + } + + error(message: string, ...args: any[]): void { + // Errors are always logged regardless of DEBUG flag + console.error(`[ERROR] ${message}`, ...args); + } + + debug(message: string, ...args: any[]): void { + if (this.enabled) { + console.debug(`[DEBUG] ${message}`, ...args); + } + } +} + +export const logger = new Logger(); diff --git a/package-lock.json b/package-lock.json index c053c50b..0c53fb41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -102,6 +102,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@codesandbox/sandpack-react": "^2.20.0", "@openai/agents": "^0.3.2", "react": "^19.2.0", "react-dom": "^19.2.0" @@ -1849,6 +1850,182 @@ "dev": true, "license": "MIT OR Apache-2.0" }, + "node_modules/@codemirror/autocomplete": { + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.19.1.tgz", + "integrity": "sha512-q6NenYkEy2fn9+JyjIxMWcNjzTL/IhwqfzOut1/G3PrIFkrbl4AL7Wkse5tLrQUUyqGoAKU5+Pi5jnnXxH5HGw==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz", + "integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", + "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", + "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", + "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.2.tgz", + "integrity": "sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", + "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.38.7", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.7.tgz", + "integrity": "sha512-+b0imJTgzehmMToqT9DWPBdeRj7/qDsJj7MzQ+1+do2KK2UkxKuLaHlUVeZk855wO6my6cfbF1c+Qozs8B3YqA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@codesandbox/nodebox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@codesandbox/nodebox/-/nodebox-0.1.8.tgz", + "integrity": "sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==", + "license": "SEE LICENSE IN ./LICENSE", + "dependencies": { + "outvariant": "^1.4.0", + "strict-event-emitter": "^0.4.3" + } + }, + "node_modules/@codesandbox/sandpack-client": { + "version": "2.19.8", + "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-client/-/sandpack-client-2.19.8.tgz", + "integrity": "sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ==", + "license": "Apache-2.0", + "dependencies": { + "@codesandbox/nodebox": "0.1.8", + "buffer": "^6.0.3", + "dequal": "^2.0.2", + "mime-db": "^1.52.0", + "outvariant": "1.4.0", + "static-browser-server": "1.0.3" + } + }, + "node_modules/@codesandbox/sandpack-react": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-react/-/sandpack-react-2.20.0.tgz", + "integrity": "sha512-takd1YpW/PMQ6KPQfvseWLHWklJovGY8QYj8MtWnskGKbjOGJ6uZfyZbcJ6aCFLQMpNyjTqz9AKNbvhCOZ1TUQ==", + "license": "Apache-2.0", + "dependencies": { + "@codemirror/autocomplete": "^6.4.0", + "@codemirror/commands": "^6.1.3", + "@codemirror/lang-css": "^6.0.1", + "@codemirror/lang-html": "^6.4.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.3.2", + "@codemirror/state": "^6.2.0", + "@codemirror/view": "^6.7.1", + "@codesandbox/sandpack-client": "^2.19.8", + "@lezer/highlight": "^1.1.3", + "@react-hook/intersection-observer": "^3.1.1", + "@stitches/core": "^1.2.6", + "anser": "^2.1.1", + "clean-set": "^1.1.2", + "dequal": "^2.0.2", + "escape-carriage": "^1.3.1", + "lz-string": "^1.4.4", + "react-devtools-inline": "4.4.0", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19", + "react-dom": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/@codesandbox/sandpack-react/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -2827,6 +3004,63 @@ "node": ">=10" } }, + "node_modules/@lezer/common": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.3.0.tgz", + "integrity": "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==", + "license": "MIT" + }, + "node_modules/@lezer/css": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz", + "integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz", + "integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.3.tgz", + "integrity": "sha512-yenN5SqAxAPv/qMnpWW0AT7l+SxVrgG+u0tNsRQWqbrz66HIl8DnEbBObvy21J5K7+I1v7gsAnlE2VQ5yYVSeA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, "node_modules/@manypkg/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", @@ -2899,6 +3133,12 @@ "node": ">=6 <7 || >=8" } }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.22.0.tgz", @@ -3265,6 +3505,12 @@ "@octokit/openapi-types": "^20.0.0" } }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "license": "MIT" + }, "node_modules/@openai/agents": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@openai/agents/-/agents-0.3.2.tgz", @@ -3400,6 +3646,28 @@ "url": "https://github.com/sponsors/sxzz" } }, + "node_modules/@react-hook/intersection-observer": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-hook/intersection-observer/-/intersection-observer-3.1.2.tgz", + "integrity": "sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ==", + "license": "MIT", + "dependencies": { + "@react-hook/passive-layout-effect": "^1.2.0", + "intersection-observer": "^0.10.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/@react-hook/passive-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz", + "integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8" + } + }, "node_modules/@remix-run/node-fetch-server": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@remix-run/node-fetch-server/-/node-fetch-server-0.8.1.tgz", @@ -3996,6 +4264,12 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/@stitches/core": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz", + "integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==", + "license": "MIT" + }, "node_modules/@tailwindcss/node": { "version": "4.1.17", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz", @@ -4702,6 +4976,12 @@ } } }, + "node_modules/anser": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/anser/-/anser-2.3.3.tgz", + "integrity": "sha512-QGY1oxYE7/kkeNmbtY/2ZjQ07BCG3zYdz+k/+sf69kMzEIxb93guHkPnIXITQ+BYi61oQwG74twMOX1tD4aesg==", + "license": "MIT" + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -4789,6 +5069,26 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/baseline-browser-mapping": { "version": "2.8.21", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.21.tgz", @@ -4917,6 +5217,30 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/bun-types": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.2.tgz", @@ -5132,6 +5456,12 @@ "dev": true, "license": "MIT" }, + "node_modules/clean-set": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/clean-set/-/clean-set-1.1.2.tgz", + "integrity": "sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug==", + "license": "MIT" + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -5276,6 +5606,12 @@ "node": ">= 0.10" } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -5297,6 +5633,19 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/dataloader": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz", @@ -5608,6 +5957,46 @@ "node": ">= 0.4" } }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/esbuild": { "version": "0.25.11", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", @@ -5660,6 +6049,12 @@ "node": ">=6" } }, + "node_modules/escape-carriage": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.1.tgz", + "integrity": "sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==", + "license": "MIT" + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -5679,6 +6074,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -5723,6 +6133,16 @@ "node": ">= 0.6" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", @@ -5845,6 +6265,15 @@ "dev": true, "license": "MIT" }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -6346,6 +6775,26 @@ "url": "https://opencollective.com/express" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -6369,6 +6818,13 @@ "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", "license": "MIT" }, + "node_modules/intersection-observer": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.10.0.tgz", + "integrity": "sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==", + "deprecated": "The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019.", + "license": "W3C-20150513" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -7119,6 +7575,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -8047,7 +8512,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", - "optional": true, "engines": { "node": ">= 0.6" } @@ -8205,6 +8669,12 @@ "node": ">= 0.6" } }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -8333,6 +8803,12 @@ "dev": true, "license": "MIT" }, + "node_modules/outvariant": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz", + "integrity": "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==", + "license": "MIT" + }, "node_modules/p-filter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", @@ -8798,11 +9274,21 @@ "node": ">=0.10.0" } }, + "node_modules/react-devtools-inline": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/react-devtools-inline/-/react-devtools-inline-4.4.0.tgz", + "integrity": "sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ==", + "license": "MIT", + "dependencies": { + "es6-symbol": "^3" + } + }, "node_modules/react-dom": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -9670,6 +10156,30 @@ "dev": true, "license": "MIT" }, + "node_modules/static-browser-server": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/static-browser-server/-/static-browser-server-1.0.3.tgz", + "integrity": "sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==", + "license": "Apache-2.0", + "dependencies": { + "@open-draft/deferred-promise": "^2.1.0", + "dotenv": "^16.0.3", + "mime-db": "^1.52.0", + "outvariant": "^1.3.0" + } + }, + "node_modules/static-browser-server/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -9698,6 +10208,12 @@ "npm": ">=6" } }, + "node_modules/strict-event-emitter": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", + "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==", + "license": "MIT" + }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -9765,6 +10281,12 @@ "dev": true, "license": "MIT" }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, "node_modules/style-to-js": { "version": "1.1.18", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.18.tgz", @@ -10196,6 +10718,12 @@ "win32" ] }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, "node_modules/type-detect": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", @@ -10735,6 +11263,12 @@ "dev": true, "license": "MIT" }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", From 00f87b9de41a679ea32bd30156715b75d4d15aca Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Sun, 16 Nov 2025 00:14:13 -0800 Subject: [PATCH 04/15] Update Dockerfile --- examples/openai-agents/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/openai-agents/Dockerfile b/examples/openai-agents/Dockerfile index 6d82a757..84025409 100644 --- a/examples/openai-agents/Dockerfile +++ b/examples/openai-agents/Dockerfile @@ -10,3 +10,4 @@ FROM --platform=linux/arm64 cloudflare/sandbox-test:0.5.0 # Required during local development to access exposed ports EXPOSE 8080 +EXPOSE 3000 From f9cdc62ebad80d50e59a43b516e72924da17d66b Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Sun, 16 Nov 2025 07:11:55 -0800 Subject: [PATCH 05/15] Refactor types and remove unused dependency Moved CommandResult and FileOperationResult interfaces to a new types.ts file and updated imports in app.tsx and index.ts. Removed @codesandbox/sandpack-react from dependencies and cleaned up related code. Updated env.d.ts for improved type safety and added a type-check script. --- examples/openai-agents/env.d.ts | 18 +- examples/openai-agents/package.json | 4 +- examples/openai-agents/src/app.tsx | 18 +- examples/openai-agents/src/index.ts | 39 +- examples/openai-agents/src/types.ts | 18 + package-lock.json | 535 +--------------------------- 6 files changed, 42 insertions(+), 590 deletions(-) create mode 100644 examples/openai-agents/src/types.ts diff --git a/examples/openai-agents/env.d.ts b/examples/openai-agents/env.d.ts index 5296fb46..7e155674 100644 --- a/examples/openai-agents/env.d.ts +++ b/examples/openai-agents/env.d.ts @@ -1,8 +1,22 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types env.d.ts --include-runtime false` (hash: d70ad9fbe15919b5bf8325ca39967c07) +// Generated by Wrangler by running `wrangler types env.d.ts --include-runtime false` (hash: 1f6b96358a96f1fdba78ce47af501d9f) declare namespace Cloudflare { + interface GlobalProps { + mainModule: typeof import('./src/index'); + durableNamespaces: 'Sandbox'; + } interface Env { - Sandbox: DurableObjectNamespace /* Sandbox */; + OPENAI_API_KEY: string; + Sandbox: DurableObjectNamespace; } } interface Env extends Cloudflare.Env {} +type StringifyValues> = { + [Binding in keyof EnvType]: EnvType[Binding] extends string + ? EnvType[Binding] + : string; +}; +declare namespace NodeJS { + interface ProcessEnv + extends StringifyValues> {} +} diff --git a/examples/openai-agents/package.json b/examples/openai-agents/package.json index 3e25a439..bb8d7a77 100644 --- a/examples/openai-agents/package.json +++ b/examples/openai-agents/package.json @@ -6,14 +6,14 @@ "scripts": { "start": "vite dev", "deploy": "vite build && wrangler deploy", - "types": "wrangler types env.d.ts --include-runtime false" + "types": "wrangler types env.d.ts --include-runtime false", + "check": "tsc --noEmit" }, "keywords": [], "author": "", "license": "ISC", "type": "module", "dependencies": { - "@codesandbox/sandpack-react": "^2.20.0", "@openai/agents": "^0.3.2", "react": "^19.2.0", "react-dom": "^19.2.0" diff --git a/examples/openai-agents/src/app.tsx b/examples/openai-agents/src/app.tsx index 623d5b7b..228fa055 100644 --- a/examples/openai-agents/src/app.tsx +++ b/examples/openai-agents/src/app.tsx @@ -1,23 +1,7 @@ import { createRoot } from 'react-dom/client'; import { useState, useEffect, useRef } from 'react'; import './index.css'; - -interface CommandResult { - command: string; - stdout: string; - stderr: string; - exitCode: number | null; - timestamp: number; -} - -interface FileOperationResult { - operation: 'create' | 'update' | 'delete'; - path: string; - status: 'completed' | 'failed'; - output: string; - error?: string; - timestamp: number; -} +import type { CommandResult, FileOperationResult } from './types'; interface Response { naturalResponse: string | null; diff --git a/examples/openai-agents/src/index.ts b/examples/openai-agents/src/index.ts index 10daeb85..4a5ad593 100644 --- a/examples/openai-agents/src/index.ts +++ b/examples/openai-agents/src/index.ts @@ -17,25 +17,7 @@ import { } from '@openai/agents'; import { logger } from './logger'; - -// Command result for API responses -interface CommandResult { - command: string; - stdout: string; - stderr: string; - exitCode: number | null; - timestamp: number; -} - -// File operation result for API responses -interface FileOperationResult { - operation: 'create' | 'update' | 'delete'; - path: string; - status: 'completed' | 'failed'; - output: string; - error?: string; - timestamp: number; -} +import type { CommandResult, FileOperationResult } from './types'; class SandboxShell implements Shell { private cwd: string = '/workspace'; @@ -364,12 +346,6 @@ class WorkspaceEditor implements Editor { } return filePath.substring(0, lastSlash) || '/'; } - - async seedWorkspace(): Promise { - logger.debug('Seeding workspace', { root: this.root }); - await this.sandbox.mkdir(this.root, { recursive: true }); - logger.info('Workspace seeded', { root: this.root }); - } } async function handleRunRequest(request: Request, env: Env): Promise { @@ -398,10 +374,7 @@ async function handleRunRequest(request: Request, env: Env): Promise { logger.debug('Getting sandbox instance', { sessionId: 'workspace-session' }); - const sandbox = getSandbox( - env.Sandbox as unknown as DurableObjectNamespace, - 'workspace-session' - ); + const sandbox = getSandbox(env.Sandbox, 'workspace-session'); // Create shell (automatically collects results) logger.debug('Creating SandboxShell'); @@ -411,9 +384,6 @@ async function handleRunRequest(request: Request, env: Env): Promise { logger.debug('Creating WorkspaceEditor', { root: '/workspace' }); const editor = new WorkspaceEditor(sandbox, '/workspace'); - // this seems to hang operations, so just commenting out for now - // await editor.seedWorkspace(); - // Create agent with both shell and patch tools, auto-approval for web API logger.debug('Creating Agent', { name: 'Sandbox Studio', @@ -444,7 +414,7 @@ async function handleRunRequest(request: Request, env: Env): Promise { outputLength: result.finalOutput?.length || 0 }); - // Combine and sort all results by timestamp + // Combine and sort all results by timestamp for logging const allResults = [ ...shell.results.map((r) => ({ type: 'command' as const, ...r })), ...editor.results.map((r) => ({ type: 'file' as const, ...r })) @@ -460,8 +430,7 @@ async function handleRunRequest(request: Request, env: Env): Promise { const response = { naturalResponse: result.finalOutput || null, commandResults: shell.results.sort((a, b) => a.timestamp - b.timestamp), - fileOperations: editor.results.sort((a, b) => a.timestamp - b.timestamp), - allResults + fileOperations: editor.results.sort((a, b) => a.timestamp - b.timestamp) }; logger.info('Request completed successfully'); diff --git a/examples/openai-agents/src/types.ts b/examples/openai-agents/src/types.ts new file mode 100644 index 00000000..6bd859ba --- /dev/null +++ b/examples/openai-agents/src/types.ts @@ -0,0 +1,18 @@ +// Command result for API responses +export interface CommandResult { + command: string; + stdout: string; + stderr: string; + exitCode: number | null; + timestamp: number; +} + +// File operation result for API responses +export interface FileOperationResult { + operation: 'create' | 'update' | 'delete'; + path: string; + status: 'completed' | 'failed'; + output: string; + error?: string; + timestamp: number; +} diff --git a/package-lock.json b/package-lock.json index 0c53fb41..87f1e31c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -102,7 +102,6 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@codesandbox/sandpack-react": "^2.20.0", "@openai/agents": "^0.3.2", "react": "^19.2.0", "react-dom": "^19.2.0" @@ -1850,182 +1849,6 @@ "dev": true, "license": "MIT OR Apache-2.0" }, - "node_modules/@codemirror/autocomplete": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.19.1.tgz", - "integrity": "sha512-q6NenYkEy2fn9+JyjIxMWcNjzTL/IhwqfzOut1/G3PrIFkrbl4AL7Wkse5tLrQUUyqGoAKU5+Pi5jnnXxH5HGw==", - "license": "MIT", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.17.0", - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@codemirror/commands": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz", - "integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==", - "license": "MIT", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.4.0", - "@codemirror/view": "^6.27.0", - "@lezer/common": "^1.1.0" - } - }, - "node_modules/@codemirror/lang-css": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", - "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", - "license": "MIT", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@lezer/common": "^1.0.2", - "@lezer/css": "^1.1.7" - } - }, - "node_modules/@codemirror/lang-html": { - "version": "6.4.11", - "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", - "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", - "license": "MIT", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/lang-css": "^6.0.0", - "@codemirror/lang-javascript": "^6.0.0", - "@codemirror/language": "^6.4.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.17.0", - "@lezer/common": "^1.0.0", - "@lezer/css": "^1.1.0", - "@lezer/html": "^1.3.12" - } - }, - "node_modules/@codemirror/lang-javascript": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", - "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", - "license": "MIT", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.6.0", - "@codemirror/lint": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.17.0", - "@lezer/common": "^1.0.0", - "@lezer/javascript": "^1.0.0" - } - }, - "node_modules/@codemirror/language": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", - "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", - "license": "MIT", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.23.0", - "@lezer/common": "^1.1.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0", - "style-mod": "^4.0.0" - } - }, - "node_modules/@codemirror/lint": { - "version": "6.9.2", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.2.tgz", - "integrity": "sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==", - "license": "MIT", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.35.0", - "crelt": "^1.0.5" - } - }, - "node_modules/@codemirror/state": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", - "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", - "license": "MIT", - "dependencies": { - "@marijn/find-cluster-break": "^1.0.0" - } - }, - "node_modules/@codemirror/view": { - "version": "6.38.7", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.7.tgz", - "integrity": "sha512-+b0imJTgzehmMToqT9DWPBdeRj7/qDsJj7MzQ+1+do2KK2UkxKuLaHlUVeZk855wO6my6cfbF1c+Qozs8B3YqA==", - "license": "MIT", - "dependencies": { - "@codemirror/state": "^6.5.0", - "crelt": "^1.0.6", - "style-mod": "^4.1.0", - "w3c-keyname": "^2.2.4" - } - }, - "node_modules/@codesandbox/nodebox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@codesandbox/nodebox/-/nodebox-0.1.8.tgz", - "integrity": "sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==", - "license": "SEE LICENSE IN ./LICENSE", - "dependencies": { - "outvariant": "^1.4.0", - "strict-event-emitter": "^0.4.3" - } - }, - "node_modules/@codesandbox/sandpack-client": { - "version": "2.19.8", - "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-client/-/sandpack-client-2.19.8.tgz", - "integrity": "sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ==", - "license": "Apache-2.0", - "dependencies": { - "@codesandbox/nodebox": "0.1.8", - "buffer": "^6.0.3", - "dequal": "^2.0.2", - "mime-db": "^1.52.0", - "outvariant": "1.4.0", - "static-browser-server": "1.0.3" - } - }, - "node_modules/@codesandbox/sandpack-react": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-react/-/sandpack-react-2.20.0.tgz", - "integrity": "sha512-takd1YpW/PMQ6KPQfvseWLHWklJovGY8QYj8MtWnskGKbjOGJ6uZfyZbcJ6aCFLQMpNyjTqz9AKNbvhCOZ1TUQ==", - "license": "Apache-2.0", - "dependencies": { - "@codemirror/autocomplete": "^6.4.0", - "@codemirror/commands": "^6.1.3", - "@codemirror/lang-css": "^6.0.1", - "@codemirror/lang-html": "^6.4.0", - "@codemirror/lang-javascript": "^6.1.2", - "@codemirror/language": "^6.3.2", - "@codemirror/state": "^6.2.0", - "@codemirror/view": "^6.7.1", - "@codesandbox/sandpack-client": "^2.19.8", - "@lezer/highlight": "^1.1.3", - "@react-hook/intersection-observer": "^3.1.1", - "@stitches/core": "^1.2.6", - "anser": "^2.1.1", - "clean-set": "^1.1.2", - "dequal": "^2.0.2", - "escape-carriage": "^1.3.1", - "lz-string": "^1.4.4", - "react-devtools-inline": "4.4.0", - "react-is": "^17.0.2" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17 || ^18 || ^19", - "react-dom": "^16.8.0 || ^17 || ^18 || ^19" - } - }, - "node_modules/@codesandbox/sandpack-react/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT" - }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -3004,63 +2827,6 @@ "node": ">=10" } }, - "node_modules/@lezer/common": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.3.0.tgz", - "integrity": "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==", - "license": "MIT" - }, - "node_modules/@lezer/css": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz", - "integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.3.0" - } - }, - "node_modules/@lezer/highlight": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", - "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.3.0" - } - }, - "node_modules/@lezer/html": { - "version": "1.3.12", - "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz", - "integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/javascript": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", - "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.1.3", - "@lezer/lr": "^1.3.0" - } - }, - "node_modules/@lezer/lr": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.3.tgz", - "integrity": "sha512-yenN5SqAxAPv/qMnpWW0AT7l+SxVrgG+u0tNsRQWqbrz66HIl8DnEbBObvy21J5K7+I1v7gsAnlE2VQ5yYVSeA==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.0.0" - } - }, "node_modules/@manypkg/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", @@ -3133,12 +2899,6 @@ "node": ">=6 <7 || >=8" } }, - "node_modules/@marijn/find-cluster-break": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", - "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", - "license": "MIT" - }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.22.0.tgz", @@ -3505,12 +3265,6 @@ "@octokit/openapi-types": "^20.0.0" } }, - "node_modules/@open-draft/deferred-promise": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", - "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", - "license": "MIT" - }, "node_modules/@openai/agents": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@openai/agents/-/agents-0.3.2.tgz", @@ -3646,28 +3400,6 @@ "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/@react-hook/intersection-observer": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@react-hook/intersection-observer/-/intersection-observer-3.1.2.tgz", - "integrity": "sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ==", - "license": "MIT", - "dependencies": { - "@react-hook/passive-layout-effect": "^1.2.0", - "intersection-observer": "^0.10.0" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/@react-hook/passive-layout-effect": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz", - "integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==", - "license": "MIT", - "peerDependencies": { - "react": ">=16.8" - } - }, "node_modules/@remix-run/node-fetch-server": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@remix-run/node-fetch-server/-/node-fetch-server-0.8.1.tgz", @@ -4264,12 +3996,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/@stitches/core": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz", - "integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==", - "license": "MIT" - }, "node_modules/@tailwindcss/node": { "version": "4.1.17", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz", @@ -4976,12 +4702,6 @@ } } }, - "node_modules/anser": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/anser/-/anser-2.3.3.tgz", - "integrity": "sha512-QGY1oxYE7/kkeNmbtY/2ZjQ07BCG3zYdz+k/+sf69kMzEIxb93guHkPnIXITQ+BYi61oQwG74twMOX1tD4aesg==", - "license": "MIT" - }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -5069,26 +4789,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/baseline-browser-mapping": { "version": "2.8.21", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.21.tgz", @@ -5217,30 +4917,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/bun-types": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.2.tgz", @@ -5456,12 +5132,6 @@ "dev": true, "license": "MIT" }, - "node_modules/clean-set": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/clean-set/-/clean-set-1.1.2.tgz", - "integrity": "sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug==", - "license": "MIT" - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -5606,12 +5276,6 @@ "node": ">= 0.10" } }, - "node_modules/crelt": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", - "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -5633,19 +5297,6 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, - "node_modules/d": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", - "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", - "license": "ISC", - "dependencies": { - "es5-ext": "^0.10.64", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.12" - } - }, "node_modules/dataloader": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz", @@ -5957,46 +5608,6 @@ "node": ">= 0.4" } }, - "node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "hasInstallScript": true, - "license": "ISC", - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", - "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", - "license": "ISC", - "dependencies": { - "d": "^1.0.2", - "ext": "^1.7.0" - }, - "engines": { - "node": ">=0.12" - } - }, "node_modules/esbuild": { "version": "0.25.11", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", @@ -6049,12 +5660,6 @@ "node": ">=6" } }, - "node_modules/escape-carriage": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.1.tgz", - "integrity": "sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==", - "license": "MIT" - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -6074,21 +5679,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "license": "ISC", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -6133,16 +5723,6 @@ "node": ">= 0.6" } }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, "node_modules/eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", @@ -6265,15 +5845,6 @@ "dev": true, "license": "MIT" }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "license": "ISC", - "dependencies": { - "type": "^2.7.2" - } - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -6775,26 +6346,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -6818,13 +6369,6 @@ "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", "license": "MIT" }, - "node_modules/intersection-observer": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.10.0.tgz", - "integrity": "sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==", - "deprecated": "The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019.", - "license": "W3C-20150513" - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -7575,15 +7119,6 @@ "yallist": "^3.0.2" } }, - "node_modules/lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "license": "MIT", - "bin": { - "lz-string": "bin/bin.js" - } - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -8512,6 +8047,7 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", + "optional": true, "engines": { "node": ">= 0.6" } @@ -8669,12 +8205,6 @@ "node": ">= 0.6" } }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "license": "ISC" - }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -8803,12 +8333,6 @@ "dev": true, "license": "MIT" }, - "node_modules/outvariant": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz", - "integrity": "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==", - "license": "MIT" - }, "node_modules/p-filter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", @@ -9274,15 +8798,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-devtools-inline": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/react-devtools-inline/-/react-devtools-inline-4.4.0.tgz", - "integrity": "sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ==", - "license": "MIT", - "dependencies": { - "es6-symbol": "^3" - } - }, "node_modules/react-dom": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", @@ -10156,30 +9671,6 @@ "dev": true, "license": "MIT" }, - "node_modules/static-browser-server": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/static-browser-server/-/static-browser-server-1.0.3.tgz", - "integrity": "sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==", - "license": "Apache-2.0", - "dependencies": { - "@open-draft/deferred-promise": "^2.1.0", - "dotenv": "^16.0.3", - "mime-db": "^1.52.0", - "outvariant": "^1.3.0" - } - }, - "node_modules/static-browser-server/node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -10208,12 +9699,6 @@ "npm": ">=6" } }, - "node_modules/strict-event-emitter": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", - "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==", - "license": "MIT" - }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -10281,12 +9766,6 @@ "dev": true, "license": "MIT" }, - "node_modules/style-mod": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", - "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", - "license": "MIT" - }, "node_modules/style-to-js": { "version": "1.1.18", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.18.tgz", @@ -10718,12 +10197,6 @@ "win32" ] }, - "node_modules/type": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", - "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", - "license": "ISC" - }, "node_modules/type-detect": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", @@ -11263,12 +10736,6 @@ "dev": true, "license": "MIT" }, - "node_modules/w3c-keyname": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", - "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", - "license": "MIT" - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", From 02183eca70897ec71fb90a85082a9e5db08c5d5d Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Sun, 16 Nov 2025 07:17:00 -0800 Subject: [PATCH 06/15] Improve error handling and logging utilities Introduces helper functions for consistent error property extraction and message formatting. Refactors error handling in SandboxShell, WorkspaceEditor, and handleRunRequest to use these helpers, improving reliability and clarity in error logging and reporting. --- examples/openai-agents/src/index.ts | 81 +++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 23 deletions(-) diff --git a/examples/openai-agents/src/index.ts b/examples/openai-agents/src/index.ts index 4a5ad593..380cf5db 100644 --- a/examples/openai-agents/src/index.ts +++ b/examples/openai-agents/src/index.ts @@ -19,6 +19,32 @@ import { import { logger } from './logger'; import type { CommandResult, FileOperationResult } from './types'; +// Helper functions for error handling +function isErrorWithProperties(error: unknown): error is { + message?: string; + exitCode?: number; + stdout?: string; + stderr?: string; + status?: number; + stack?: string; +} { + return typeof error === 'object' && error !== null; +} + +function getErrorMessage(error: unknown): string { + if (isErrorWithProperties(error) && typeof error.message === 'string') { + return error.message; + } + return String(error); +} + +function getErrorStack(error: unknown): string | undefined { + if (isErrorWithProperties(error) && typeof error.stack === 'string') { + return error.stack; + } + return undefined; +} + class SandboxShell implements Shell { private cwd: string = '/workspace'; public results: CommandResult[] = []; @@ -71,14 +97,16 @@ class SandboxShell implements Shell { } else { logger.info(`Command completed successfully`, { command }); } - } catch (error: any) { + } catch (error: unknown) { // Handle network/HTTP errors or timeout errors - exitCode = typeof error?.exitCode === 'number' ? error.exitCode : null; - stdout = error?.stdout ?? ''; - stderr = error?.stderr ?? ''; + const errorObj = isErrorWithProperties(error) ? error : {}; + exitCode = + typeof errorObj.exitCode === 'number' ? errorObj.exitCode : null; + stdout = typeof errorObj.stdout === 'string' ? errorObj.stdout : ''; + stderr = typeof errorObj.stderr === 'string' ? errorObj.stderr : ''; // Check if it's a timeout error - const errorMessage = error?.message ?? ''; + const errorMessage = getErrorMessage(error); if ( errorMessage.includes('timeout') || errorMessage.includes('Timeout') || @@ -186,20 +214,21 @@ class WorkspaceEditor implements Editor { timestamp }); return { status: 'completed', output: `Created ${operation.path}` }; - } catch (error: any) { + } catch (error: unknown) { const timestamp = Date.now(); + const errorMessage = getErrorMessage(error); const result: FileOperationResult = { operation: 'create', path: operation.path, status: 'failed', output: `Failed to create ${operation.path}`, - error: error?.message || String(error), + error: errorMessage, timestamp }; this.results.push(result); logger.error('Failed to create file', { path: operation.path, - error: error?.message || error + error: errorMessage }); throw error; } @@ -226,19 +255,21 @@ class WorkspaceEditor implements Editor { path: targetPath, originalLength: original.length }); - } catch (error: any) { + } catch (error: unknown) { // Sandbox API may throw errors for missing files + const errorObj = isErrorWithProperties(error) ? error : {}; + const errorMessage = getErrorMessage(error); if ( - error?.message?.includes('not found') || - error?.message?.includes('ENOENT') || - error?.status === 404 + errorMessage.includes('not found') || + errorMessage.includes('ENOENT') || + errorObj.status === 404 ) { logger.error('Cannot update missing file', { path: operation.path }); throw new Error(`Cannot update missing file: ${operation.path}`); } logger.error('Error reading file', { path: operation.path, - error: error?.message || error + error: errorMessage }); throw error; } @@ -264,20 +295,21 @@ class WorkspaceEditor implements Editor { timestamp }); return { status: 'completed', output: `Updated ${operation.path}` }; - } catch (error: any) { + } catch (error: unknown) { const timestamp = Date.now(); + const errorMessage = getErrorMessage(error); const result: FileOperationResult = { operation: 'update', path: operation.path, status: 'failed', output: `Failed to update ${operation.path}`, - error: error?.message || String(error), + error: errorMessage, timestamp }; this.results.push(result); logger.error('Failed to update file', { path: operation.path, - error: error?.message || error + error: errorMessage }); throw error; } @@ -308,20 +340,21 @@ class WorkspaceEditor implements Editor { timestamp }); return { status: 'completed', output: `Deleted ${operation.path}` }; - } catch (error: any) { + } catch (error: unknown) { const timestamp = Date.now(); + const errorMessage = getErrorMessage(error); const result: FileOperationResult = { operation: 'delete', path: operation.path, status: 'failed', output: `Failed to delete ${operation.path}`, - error: error?.message || String(error), + error: errorMessage, timestamp }; this.results.push(result); logger.error('Failed to delete file', { path: operation.path, - error: error?.message || error + error: errorMessage }); throw error; } @@ -439,14 +472,16 @@ async function handleRunRequest(request: Request, env: Env): Promise { 'Content-Type': 'application/json' } }); - } catch (error: any) { + } catch (error: unknown) { + const errorMessage = getErrorMessage(error); + const errorStack = getErrorStack(error); logger.error('Error handling run request', { - error: error?.message || error, - stack: error?.stack + error: errorMessage, + stack: errorStack }); return new Response( JSON.stringify({ - error: error.message || 'Internal server error', + error: errorMessage || 'Internal server error', naturalResponse: 'An error occurred while processing your request.', commandResults: [], fileOperations: [] From 9453aed543ba5a0f0a1345ee5ed89b01275b088e Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Sun, 16 Nov 2025 07:19:16 -0800 Subject: [PATCH 07/15] Update package.json --- examples/openai-agents/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/openai-agents/package.json b/examples/openai-agents/package.json index bb8d7a77..e40e3e54 100644 --- a/examples/openai-agents/package.json +++ b/examples/openai-agents/package.json @@ -7,7 +7,7 @@ "start": "vite dev", "deploy": "vite build && wrangler deploy", "types": "wrangler types env.d.ts --include-runtime false", - "check": "tsc --noEmit" + "typecheck": "tsc --noEmit" }, "keywords": [], "author": "", From d6b934d7227f14f46036bdb418d77e48aebe0e31 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Mon, 17 Nov 2025 14:02:40 +0000 Subject: [PATCH 08/15] Add OpenAI Agents adapters for Cloudflare Sandbox Introduces Shell and Editor adapters in packages/sandbox/src/openai for integrating OpenAI Agents with Cloudflare Sandbox, including structured command and file operation results. Updates examples/openai-agents to use these adapters, removes local types in favor of shared interfaces, and adds tests for the new adapters. Also updates package.json and tsdown config to export the new openai module and adjusts dependencies. --- .changeset/lovely-friends-study.md | 5 + examples/openai-agents/src/app.tsx | 5 +- examples/openai-agents/src/index.ts | 357 +-------------- examples/openai-agents/src/types.ts | 18 - package-lock.json | 10 +- packages/sandbox/package.json | 14 + packages/sandbox/src/openai/index.ts | 424 ++++++++++++++++++ .../sandbox/tests/openai-shell-editor.test.ts | 196 ++++++++ packages/sandbox/tsdown.config.ts | 2 +- 9 files changed, 657 insertions(+), 374 deletions(-) create mode 100644 .changeset/lovely-friends-study.md delete mode 100644 examples/openai-agents/src/types.ts create mode 100644 packages/sandbox/src/openai/index.ts create mode 100644 packages/sandbox/tests/openai-shell-editor.test.ts diff --git a/.changeset/lovely-friends-study.md b/.changeset/lovely-friends-study.md new file mode 100644 index 00000000..994b24d7 --- /dev/null +++ b/.changeset/lovely-friends-study.md @@ -0,0 +1,5 @@ +--- +'@cloudflare/sandbox': patch +--- + +Add OpenAI Agents adapters diff --git a/examples/openai-agents/src/app.tsx b/examples/openai-agents/src/app.tsx index 228fa055..6b221ff8 100644 --- a/examples/openai-agents/src/app.tsx +++ b/examples/openai-agents/src/app.tsx @@ -1,7 +1,10 @@ import { createRoot } from 'react-dom/client'; import { useState, useEffect, useRef } from 'react'; import './index.css'; -import type { CommandResult, FileOperationResult } from './types'; +import type { + CommandResult, + FileOperationResult +} from '@cloudflare/sandbox/openai'; interface Response { naturalResponse: string | null; diff --git a/examples/openai-agents/src/index.ts b/examples/openai-agents/src/index.ts index 380cf5db..8faf0fa4 100644 --- a/examples/openai-agents/src/index.ts +++ b/examples/openai-agents/src/index.ts @@ -1,23 +1,10 @@ import { Sandbox, getSandbox } from '@cloudflare/sandbox'; +import { Shell, Editor } from '@cloudflare/sandbox/openai'; export { Sandbox }; // export the Sandbox class for the worker -import { - Agent, - run, - Shell, - ShellAction, - ShellResult, - ShellOutputResult, - shellTool, - applyPatchTool, - ApplyPatchOperation, - ApplyPatchResult, - applyDiff, - Editor -} from '@openai/agents'; +import { Agent, run, shellTool, applyPatchTool } from '@openai/agents'; import { logger } from './logger'; -import type { CommandResult, FileOperationResult } from './types'; // Helper functions for error handling function isErrorWithProperties(error: unknown): error is { @@ -45,342 +32,6 @@ function getErrorStack(error: unknown): string | undefined { return undefined; } -class SandboxShell implements Shell { - private cwd: string = '/workspace'; - public results: CommandResult[] = []; - - constructor(private readonly sandbox: Sandbox) {} - - async run(action: ShellAction): Promise { - logger.debug('SandboxShell.run called', { - commands: action.commands, - timeout: action.timeoutMs - }); - const output: ShellResult['output'] = []; - - for (const command of action.commands) { - logger.debug('Executing command', { command, cwd: this.cwd }); - let stdout = ''; - let stderr = ''; - let exitCode: number | null = 0; - let outcome: ShellOutputResult['outcome'] = { - type: 'exit', - exitCode: 0 - }; - try { - const result = await this.sandbox.exec(command, { - timeout: action.timeoutMs, - cwd: this.cwd - }); - stdout = result.stdout; - stderr = result.stderr; - exitCode = result.exitCode; - // exec returns a result even for failed commands, so check success field - // Timeout would be indicated by a specific error or exit code - outcome = { type: 'exit', exitCode }; - - logger.debug('Command executed successfully', { - command, - exitCode, - stdoutLength: stdout.length, - stderrLength: stderr.length - }); - - // Log warnings for non-zero exit codes or stderr output - if (exitCode !== 0) { - logger.warn(`Command failed with exit code ${exitCode}`, { - command, - stderr - }); - } else if (stderr) { - logger.warn(`Command produced stderr output`, { command, stderr }); - } else { - logger.info(`Command completed successfully`, { command }); - } - } catch (error: unknown) { - // Handle network/HTTP errors or timeout errors - const errorObj = isErrorWithProperties(error) ? error : {}; - exitCode = - typeof errorObj.exitCode === 'number' ? errorObj.exitCode : null; - stdout = typeof errorObj.stdout === 'string' ? errorObj.stdout : ''; - stderr = typeof errorObj.stderr === 'string' ? errorObj.stderr : ''; - - // Check if it's a timeout error - const errorMessage = getErrorMessage(error); - if ( - errorMessage.includes('timeout') || - errorMessage.includes('Timeout') || - errorMessage.includes('timed out') - ) { - logger.error(`Command timed out`, { - command, - timeout: action.timeoutMs - }); - outcome = { type: 'timeout' }; - } else { - logger.error(`Error executing command`, { - command, - error: errorMessage || error, - exitCode - }); - outcome = { type: 'exit', exitCode: exitCode ?? 1 }; - } - } - output.push({ - command, - stdout, - stderr, - outcome - }); - - // Collect results for API responses - const collectedExitCode = - outcome.type === 'exit' ? outcome.exitCode : null; - const timestamp = Date.now(); - this.results.push({ - command: String(command), - stdout: String(stdout), - stderr: String(stderr), - exitCode: collectedExitCode, - timestamp - }); - logger.debug('Result collected', { - command, - exitCode: collectedExitCode, - timestamp - }); - - if (outcome.type === 'timeout') { - logger.warn('Breaking command loop due to timeout'); - break; - } - } - - logger.debug('SandboxShell.run completed', { - totalCommands: action.commands.length, - resultsCount: this.results.length - }); - return { - output, - providerData: { - working_directory: this.cwd - } - }; - } -} - -class WorkspaceEditor implements Editor { - public results: FileOperationResult[] = []; - - constructor( - private readonly sandbox: Sandbox, - private readonly root: string = '/workspace' - ) {} - - async createFile( - operation: Extract - ): Promise { - const targetPath = this.resolve(operation.path); - logger.debug('WorkspaceEditor.createFile called', { - path: operation.path, - targetPath - }); - - try { - // Create parent directory if needed - const dirPath = this.getDirname(targetPath); - if (dirPath !== this.root && dirPath !== '/') { - logger.debug('Creating parent directory', { dirPath }); - await this.sandbox.mkdir(dirPath, { recursive: true }); - } - - const content = applyDiff('', operation.diff, 'create'); - logger.debug('Writing file content', { - path: targetPath, - contentLength: content.length - }); - await this.sandbox.writeFile(targetPath, content, { encoding: 'utf-8' }); - const timestamp = Date.now(); - const result: FileOperationResult = { - operation: 'create', - path: operation.path, - status: 'completed', - output: `Created ${operation.path}`, - timestamp - }; - this.results.push(result); - logger.info('File created successfully', { - path: operation.path, - timestamp - }); - return { status: 'completed', output: `Created ${operation.path}` }; - } catch (error: unknown) { - const timestamp = Date.now(); - const errorMessage = getErrorMessage(error); - const result: FileOperationResult = { - operation: 'create', - path: operation.path, - status: 'failed', - output: `Failed to create ${operation.path}`, - error: errorMessage, - timestamp - }; - this.results.push(result); - logger.error('Failed to create file', { - path: operation.path, - error: errorMessage - }); - throw error; - } - } - - async updateFile( - operation: Extract - ): Promise { - const targetPath = this.resolve(operation.path); - logger.debug('WorkspaceEditor.updateFile called', { - path: operation.path, - targetPath - }); - - try { - let original: string; - try { - logger.debug('Reading original file', { path: targetPath }); - const fileInfo = await this.sandbox.readFile(targetPath, { - encoding: 'utf-8' - }); - original = fileInfo.content; - logger.debug('Original file read', { - path: targetPath, - originalLength: original.length - }); - } catch (error: unknown) { - // Sandbox API may throw errors for missing files - const errorObj = isErrorWithProperties(error) ? error : {}; - const errorMessage = getErrorMessage(error); - if ( - errorMessage.includes('not found') || - errorMessage.includes('ENOENT') || - errorObj.status === 404 - ) { - logger.error('Cannot update missing file', { path: operation.path }); - throw new Error(`Cannot update missing file: ${operation.path}`); - } - logger.error('Error reading file', { - path: operation.path, - error: errorMessage - }); - throw error; - } - - const patched = applyDiff(original, operation.diff); - logger.debug('Applied diff', { - path: targetPath, - originalLength: original.length, - patchedLength: patched.length - }); - await this.sandbox.writeFile(targetPath, patched, { encoding: 'utf-8' }); - const timestamp = Date.now(); - const result: FileOperationResult = { - operation: 'update', - path: operation.path, - status: 'completed', - output: `Updated ${operation.path}`, - timestamp - }; - this.results.push(result); - logger.info('File updated successfully', { - path: operation.path, - timestamp - }); - return { status: 'completed', output: `Updated ${operation.path}` }; - } catch (error: unknown) { - const timestamp = Date.now(); - const errorMessage = getErrorMessage(error); - const result: FileOperationResult = { - operation: 'update', - path: operation.path, - status: 'failed', - output: `Failed to update ${operation.path}`, - error: errorMessage, - timestamp - }; - this.results.push(result); - logger.error('Failed to update file', { - path: operation.path, - error: errorMessage - }); - throw error; - } - } - - async deleteFile( - operation: Extract - ): Promise { - const targetPath = this.resolve(operation.path); - logger.debug('WorkspaceEditor.deleteFile called', { - path: operation.path, - targetPath - }); - - try { - await this.sandbox.deleteFile(targetPath); - const timestamp = Date.now(); - const result: FileOperationResult = { - operation: 'delete', - path: operation.path, - status: 'completed', - output: `Deleted ${operation.path}`, - timestamp - }; - this.results.push(result); - logger.info('File deleted successfully', { - path: operation.path, - timestamp - }); - return { status: 'completed', output: `Deleted ${operation.path}` }; - } catch (error: unknown) { - const timestamp = Date.now(); - const errorMessage = getErrorMessage(error); - const result: FileOperationResult = { - operation: 'delete', - path: operation.path, - status: 'failed', - output: `Failed to delete ${operation.path}`, - error: errorMessage, - timestamp - }; - this.results.push(result); - logger.error('Failed to delete file', { - path: operation.path, - error: errorMessage - }); - throw error; - } - } - - private resolve(relativePath: string): string { - // Remove leading ./ or / if present, then join with root - const normalized = relativePath.replace(/^\.\//, '').replace(/^\//, ''); - const resolved = normalized ? `${this.root}/${normalized}` : this.root; - // Ensure the resolved path is within the workspace - if (!resolved.startsWith(this.root)) { - throw new Error(`Operation outside workspace: ${relativePath}`); - } - // Normalize path separators - return resolved.replace(/\/+/g, '/'); - } - - private getDirname(filePath: string): string { - const lastSlash = filePath.lastIndexOf('/'); - if (lastSlash === -1) { - return '/'; - } - return filePath.substring(0, lastSlash) || '/'; - } -} - async function handleRunRequest(request: Request, env: Env): Promise { logger.debug('handleRunRequest called', { method: request.method, @@ -411,11 +62,11 @@ async function handleRunRequest(request: Request, env: Env): Promise { // Create shell (automatically collects results) logger.debug('Creating SandboxShell'); - const shell = new SandboxShell(sandbox); + const shell = new Shell(sandbox); // Create workspace editor logger.debug('Creating WorkspaceEditor', { root: '/workspace' }); - const editor = new WorkspaceEditor(sandbox, '/workspace'); + const editor = new Editor(sandbox, '/workspace'); // Create agent with both shell and patch tools, auto-approval for web API logger.debug('Creating Agent', { diff --git a/examples/openai-agents/src/types.ts b/examples/openai-agents/src/types.ts deleted file mode 100644 index 6bd859ba..00000000 --- a/examples/openai-agents/src/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Command result for API responses -export interface CommandResult { - command: string; - stdout: string; - stderr: string; - exitCode: number | null; - timestamp: number; -} - -// File operation result for API responses -export interface FileOperationResult { - operation: 'create' | 'update' | 'delete'; - path: string; - status: 'completed' | 'failed'; - output: string; - error?: string; - timestamp: number; -} diff --git a/package-lock.json b/package-lock.json index 87f1e31c..78645a36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8803,7 +8803,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -11611,7 +11610,16 @@ "@cloudflare/containers": "^0.0.30" }, "devDependencies": { + "@openai/agents": "^0.3.2", "@repo/shared": "*" + }, + "peerDependencies": { + "@openai/agents": "^0.3.2" + }, + "peerDependenciesMeta": { + "@openai/agents": { + "optional": true + } } }, "packages/sandbox-container": { diff --git a/packages/sandbox/package.json b/packages/sandbox/package.json index b5f6acc1..41a9d66d 100644 --- a/packages/sandbox/package.json +++ b/packages/sandbox/package.json @@ -11,8 +11,17 @@ "@cloudflare/containers": "^0.0.30" }, "devDependencies": { + "@openai/agents": "^0.3.2", "@repo/shared": "*" }, + "peerDependencies": { + "@openai/agents": "^0.3.2" + }, + "peerDependenciesMeta": { + "@openai/agents": { + "optional": true + } + }, "tags": [ "sandbox", "codegen", @@ -36,6 +45,11 @@ "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.js" + }, + "./openai": { + "types": "./dist/openai/index.d.ts", + "import": "./dist/openai/index.js", + "require": "./dist/openai/index.js" } }, "keywords": [], diff --git a/packages/sandbox/src/openai/index.ts b/packages/sandbox/src/openai/index.ts new file mode 100644 index 00000000..9b353b49 --- /dev/null +++ b/packages/sandbox/src/openai/index.ts @@ -0,0 +1,424 @@ +/** + * OpenAI Agents adapters for executing shell commands and file operations + * inside a Cloudflare Sandbox. + */ +import { + type ApplyPatchOperation, + type ApplyPatchResult, + applyDiff, + type Editor as OpenAIEeditor, + type Shell as OpenAIShell, + type ShellAction, + type ShellOutputResult, + type ShellResult +} from '@openai/agents'; + +// Command result for API responses +export interface CommandResult { + command: string; + stdout: string; + stderr: string; + exitCode: number | null; + timestamp: number; +} + +// File operation result for API responses +export interface FileOperationResult { + operation: 'create' | 'update' | 'delete'; + path: string; + status: 'completed' | 'failed'; + output: string; + error?: string; + timestamp: number; +} + +import { createLogger, type Logger } from '@repo/shared'; +import type { Sandbox } from '../sandbox'; + +const logger: Logger = createLogger({ + component: 'sandbox-do', + operation: 'openai-agent' +}); + +// Helper functions for error handling +function isErrorWithProperties(error: unknown): error is { + message?: string; + exitCode?: number; + stdout?: string; + stderr?: string; + status?: number; + stack?: string; +} { + return typeof error === 'object' && error !== null; +} + +function getErrorMessage(error: unknown): string { + if (isErrorWithProperties(error) && typeof error.message === 'string') { + return error.message; + } + return String(error); +} + +/** + * Convert unknown values to Error instances when possible so downstream + * loggers can include stack traces without losing type safety. + */ +function toError(error: unknown): Error | undefined { + return error instanceof Error ? error : undefined; +} + +/** + * Shell implementation that adapts Cloudflare Sandbox exec calls to the + * OpenAI Agents `Shell` contract, including structured result collection. + */ +export class Shell implements OpenAIShell { + private cwd: string = '/workspace'; + public results: CommandResult[] = []; + + constructor(private readonly sandbox: Sandbox) {} + + async run(action: ShellAction): Promise { + logger.debug('SandboxShell.run called', { + commands: action.commands, + timeout: action.timeoutMs + }); + const output: ShellResult['output'] = []; + + for (const command of action.commands) { + logger.debug('Executing command', { command, cwd: this.cwd }); + let stdout = ''; + let stderr = ''; + let exitCode: number | null = 0; + let outcome: ShellOutputResult['outcome'] = { + type: 'exit', + exitCode: 0 + }; + try { + const result = await this.sandbox.exec(command, { + timeout: action.timeoutMs, + cwd: this.cwd + }); + stdout = result.stdout; + stderr = result.stderr; + exitCode = result.exitCode; + // exec returns a result even for failed commands, so check success field + // Timeout would be indicated by a specific error or exit code + outcome = { type: 'exit', exitCode }; + + logger.debug('Command executed successfully', { + command, + exitCode, + stdoutLength: stdout.length, + stderrLength: stderr.length + }); + + // Log warnings for non-zero exit codes or stderr output + if (exitCode !== 0) { + logger.warn(`Command failed with exit code ${exitCode}`, { + command, + stderr + }); + } else if (stderr) { + logger.warn(`Command produced stderr output`, { command, stderr }); + } else { + logger.info(`Command completed successfully`, { command }); + } + } catch (error: unknown) { + // Handle network/HTTP errors or timeout errors + const errorObj = isErrorWithProperties(error) ? error : {}; + exitCode = + typeof errorObj.exitCode === 'number' ? errorObj.exitCode : null; + stdout = typeof errorObj.stdout === 'string' ? errorObj.stdout : ''; + stderr = typeof errorObj.stderr === 'string' ? errorObj.stderr : ''; + + // Check if it's a timeout error + const errorMessage = getErrorMessage(error); + if ( + errorMessage.includes('timeout') || + errorMessage.includes('Timeout') || + errorMessage.includes('timed out') + ) { + logger.error(`Command timed out`, undefined, { + command, + timeout: action.timeoutMs + }); + outcome = { type: 'timeout' }; + } else { + logger.error(`Error executing command`, toError(error), { + command, + error: errorMessage || error, + exitCode + }); + outcome = { type: 'exit', exitCode: exitCode ?? 1 }; + } + } + output.push({ + command, + stdout, + stderr, + outcome + }); + + // Collect results for API responses + const collectedExitCode = + outcome.type === 'exit' ? outcome.exitCode : null; + const timestamp = Date.now(); + this.results.push({ + command: String(command), + stdout: String(stdout), + stderr: String(stderr), + exitCode: collectedExitCode, + timestamp + }); + logger.debug('Result collected', { + command, + exitCode: collectedExitCode, + timestamp + }); + + if (outcome.type === 'timeout') { + logger.warn('Breaking command loop due to timeout'); + break; + } + } + + logger.debug('SandboxShell.run completed', { + totalCommands: action.commands.length, + resultsCount: this.results.length + }); + return { + output, + providerData: { + working_directory: this.cwd + } + }; + } +} + +/** + * Editor implementation that projects applyPatch operations from Agents + * into calls against the sandbox filesystem APIs. + */ +export class Editor implements OpenAIEeditor { + public results: FileOperationResult[] = []; + + constructor( + private readonly sandbox: Sandbox, + private readonly root: string = '/workspace' + ) {} + + /** + * Create a new file inside the sandbox by applying the provided diff. + */ + async createFile( + operation: Extract + ): Promise { + const targetPath = this.resolve(operation.path); + logger.debug('WorkspaceEditor.createFile called', { + path: operation.path, + targetPath + }); + + try { + // Create parent directory if needed + const dirPath = this.getDirname(targetPath); + if (dirPath !== this.root && dirPath !== '/') { + logger.debug('Creating parent directory', { dirPath }); + await this.sandbox.mkdir(dirPath, { recursive: true }); + } + + const content = applyDiff('', operation.diff, 'create'); + logger.debug('Writing file content', { + path: targetPath, + contentLength: content.length + }); + await this.sandbox.writeFile(targetPath, content, { encoding: 'utf-8' }); + const timestamp = Date.now(); + const result: FileOperationResult = { + operation: 'create', + path: operation.path, + status: 'completed', + output: `Created ${operation.path}`, + timestamp + }; + this.results.push(result); + logger.info('File created successfully', { + path: operation.path, + timestamp + }); + return { status: 'completed', output: `Created ${operation.path}` }; + } catch (error: unknown) { + const timestamp = Date.now(); + const errorMessage = getErrorMessage(error); + const result: FileOperationResult = { + operation: 'create', + path: operation.path, + status: 'failed', + output: `Failed to create ${operation.path}`, + error: errorMessage, + timestamp + }; + this.results.push(result); + logger.error('Failed to create file', toError(error), { + path: operation.path, + error: errorMessage + }); + throw error; + } + } + + /** + * Update an existing file by reading its content, applying a diff, and + * writing the patched output back to the sandbox. + */ + async updateFile( + operation: Extract + ): Promise { + const targetPath = this.resolve(operation.path); + logger.debug('WorkspaceEditor.updateFile called', { + path: operation.path, + targetPath + }); + + try { + let original: string; + try { + logger.debug('Reading original file', { path: targetPath }); + const fileInfo = await this.sandbox.readFile(targetPath, { + encoding: 'utf-8' + }); + original = fileInfo.content; + logger.debug('Original file read', { + path: targetPath, + originalLength: original.length + }); + } catch (error: unknown) { + // Sandbox API may throw errors for missing files + const errorObj = isErrorWithProperties(error) ? error : {}; + const errorMessage = getErrorMessage(error); + if ( + errorMessage.includes('not found') || + errorMessage.includes('ENOENT') || + errorObj.status === 404 + ) { + logger.error('Cannot update missing file', undefined, { + path: operation.path + }); + throw new Error(`Cannot update missing file: ${operation.path}`); + } + logger.error('Error reading file', toError(error), { + path: operation.path, + error: errorMessage + }); + throw error; + } + + const patched = applyDiff(original, operation.diff); + logger.debug('Applied diff', { + path: targetPath, + originalLength: original.length, + patchedLength: patched.length + }); + await this.sandbox.writeFile(targetPath, patched, { encoding: 'utf-8' }); + const timestamp = Date.now(); + const result: FileOperationResult = { + operation: 'update', + path: operation.path, + status: 'completed', + output: `Updated ${operation.path}`, + timestamp + }; + this.results.push(result); + logger.info('File updated successfully', { + path: operation.path, + timestamp + }); + return { status: 'completed', output: `Updated ${operation.path}` }; + } catch (error: unknown) { + const timestamp = Date.now(); + const errorMessage = getErrorMessage(error); + const result: FileOperationResult = { + operation: 'update', + path: operation.path, + status: 'failed', + output: `Failed to update ${operation.path}`, + error: errorMessage, + timestamp + }; + this.results.push(result); + logger.error('Failed to update file', toError(error), { + path: operation.path, + error: errorMessage + }); + throw error; + } + } + + /** + * Delete a file that was previously created through applyPatch calls. + */ + async deleteFile( + operation: Extract + ): Promise { + const targetPath = this.resolve(operation.path); + logger.debug('WorkspaceEditor.deleteFile called', { + path: operation.path, + targetPath + }); + + try { + await this.sandbox.deleteFile(targetPath); + const timestamp = Date.now(); + const result: FileOperationResult = { + operation: 'delete', + path: operation.path, + status: 'completed', + output: `Deleted ${operation.path}`, + timestamp + }; + this.results.push(result); + logger.info('File deleted successfully', { + path: operation.path, + timestamp + }); + return { status: 'completed', output: `Deleted ${operation.path}` }; + } catch (error: unknown) { + const timestamp = Date.now(); + const errorMessage = getErrorMessage(error); + const result: FileOperationResult = { + operation: 'delete', + path: operation.path, + status: 'failed', + output: `Failed to delete ${operation.path}`, + error: errorMessage, + timestamp + }; + this.results.push(result); + logger.error('Failed to delete file', toError(error), { + path: operation.path, + error: errorMessage + }); + throw error; + } + } + + private resolve(relativePath: string): string { + // Remove leading ./ or / if present, then join with root + const normalized = relativePath.replace(/^\.\//, '').replace(/^\//, ''); + const resolved = normalized ? `${this.root}/${normalized}` : this.root; + // Ensure the resolved path is within the workspace + if (!resolved.startsWith(this.root)) { + throw new Error(`Operation outside workspace: ${relativePath}`); + } + // Normalize path separators + return resolved.replace(/\/+/g, '/'); + } + + private getDirname(filePath: string): string { + const lastSlash = filePath.lastIndexOf('/'); + if (lastSlash === -1) { + return '/'; + } + return filePath.substring(0, lastSlash) || '/'; + } +} diff --git a/packages/sandbox/tests/openai-shell-editor.test.ts b/packages/sandbox/tests/openai-shell-editor.test.ts new file mode 100644 index 00000000..b9be835b --- /dev/null +++ b/packages/sandbox/tests/openai-shell-editor.test.ts @@ -0,0 +1,196 @@ +import type { ApplyPatchOperation } from '@openai/agents'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { Editor, Shell } from '../src/openai/index.ts'; + +const { loggerSpies, createLoggerMock, applyDiffMock } = vi.hoisted(() => { + const logger = { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + child: vi.fn().mockReturnThis() + }; + + return { + loggerSpies: logger, + createLoggerMock: vi.fn(() => logger), + applyDiffMock: vi.fn<(...args: any[]) => string>() + }; +}); + +vi.mock('@repo/shared', () => ({ + createLogger: createLoggerMock +})); + +vi.mock('@openai/agents', () => ({ + applyDiff: applyDiffMock +})); + +describe('Shell', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('runs commands and collects results', async () => { + const execMock = vi.fn().mockResolvedValue({ + stdout: 'hello\n', + stderr: '', + exitCode: 0 + }); + + const shell = new Shell({ exec: execMock } as unknown as any); + + const result = await shell.run({ + commands: ['echo hello'], + timeoutMs: 500 + }); + + expect(execMock).toHaveBeenCalledWith('echo hello', { + timeout: 500, + cwd: '/workspace' + }); + expect(result.output).toHaveLength(1); + expect(result.output[0]).toMatchObject({ + command: 'echo hello', + stdout: 'hello\n', + stderr: '', + outcome: { type: 'exit', exitCode: 0 } + }); + expect(shell.results).toHaveLength(1); + expect(loggerSpies.info).toHaveBeenCalledWith( + 'Command completed successfully', + { command: 'echo hello' } + ); + }); + + it('halts subsequent commands after a timeout error', async () => { + const timeoutError = new Error('Command timed out'); + const execMock = vi.fn().mockRejectedValue(timeoutError); + + const shell = new Shell({ exec: execMock } as unknown as any); + const action = { + commands: ['sleep 1', 'echo never'], + timeoutMs: 25 + }; + + const result = await shell.run(action); + + expect(execMock).toHaveBeenCalledTimes(1); + expect(result.output[0].outcome).toEqual({ type: 'timeout' }); + expect(shell.results[0].exitCode).toBeNull(); + expect(loggerSpies.warn).toHaveBeenCalledWith( + 'Breaking command loop due to timeout' + ); + expect(loggerSpies.error).toHaveBeenCalledWith( + 'Command timed out', + undefined, + expect.objectContaining({ + command: 'sleep 1', + timeout: 25 + }) + ); + }); +}); + +describe('Editor', () => { + beforeEach(() => { + vi.clearAllMocks(); + applyDiffMock.mockReset(); + }); + + it('creates files using applyDiff output', async () => { + applyDiffMock.mockReturnValueOnce('file contents'); + + const sandbox = { + mkdir: vi.fn().mockResolvedValue(undefined), + writeFile: vi.fn().mockResolvedValue(undefined) + }; + + const editor = new Editor(sandbox as unknown as any); + const operation = { + type: 'create_file', + path: 'src/app.ts', + diff: '--- diff ---' + } as Extract; + + await editor.createFile(operation); + + expect(applyDiffMock).toHaveBeenCalledWith('', operation.diff, 'create'); + expect(sandbox.mkdir).toHaveBeenCalledWith('/workspace/src', { + recursive: true + }); + expect(sandbox.writeFile).toHaveBeenCalledWith( + '/workspace/src/app.ts', + 'file contents', + { encoding: 'utf-8' } + ); + expect(editor.results[0]).toMatchObject({ + operation: 'create', + path: 'src/app.ts', + status: 'completed' + }); + expect(loggerSpies.info).toHaveBeenCalledWith( + 'File created successfully', + expect.objectContaining({ path: 'src/app.ts' }) + ); + }); + + it('applies diffs when updating existing files', async () => { + applyDiffMock.mockReturnValueOnce('patched content'); + + const sandbox = { + readFile: vi.fn().mockResolvedValue({ content: 'original content' }), + writeFile: vi.fn().mockResolvedValue(undefined) + }; + + const editor = new Editor(sandbox as unknown as any); + const operation = { + type: 'update_file', + path: 'README.md', + diff: 'patch diff' + } as Extract; + + await editor.updateFile(operation); + + expect(sandbox.readFile).toHaveBeenCalledWith('/workspace/README.md', { + encoding: 'utf-8' + }); + expect(applyDiffMock).toHaveBeenCalledWith( + 'original content', + operation.diff + ); + expect(sandbox.writeFile).toHaveBeenCalledWith( + '/workspace/README.md', + 'patched content', + { encoding: 'utf-8' } + ); + expect(editor.results[0]).toMatchObject({ + operation: 'update', + path: 'README.md', + status: 'completed' + }); + }); + + it('throws descriptive error when attempting to update a missing file', async () => { + const missingError = Object.assign(new Error('not found'), { status: 404 }); + const sandbox = { + readFile: vi.fn().mockRejectedValue(missingError) + }; + + const editor = new Editor(sandbox as unknown as any); + const operation = { + type: 'update_file', + path: 'missing.txt', + diff: 'patch diff' + } as Extract; + + await expect(editor.updateFile(operation)).rejects.toThrow( + 'Cannot update missing file: missing.txt' + ); + expect(loggerSpies.error).toHaveBeenCalledWith( + 'Cannot update missing file', + undefined, + { path: 'missing.txt' } + ); + }); +}); diff --git a/packages/sandbox/tsdown.config.ts b/packages/sandbox/tsdown.config.ts index 61438e48..f15b86bb 100644 --- a/packages/sandbox/tsdown.config.ts +++ b/packages/sandbox/tsdown.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from 'tsdown'; export default defineConfig({ - entry: 'src/index.ts', + entry: ['src/index.ts', 'src/openai/index.ts'], outDir: 'dist', dts: { sourcemap: true, From 126dccd75e2bdcb64c118b6aae9f5291ba64f584 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Mon, 17 Nov 2025 17:26:48 +0000 Subject: [PATCH 09/15] remove logger from example --- examples/openai-agents/src/index.ts | 38 ++++++++++++++------------ examples/openai-agents/src/logger.ts | 41 ---------------------------- 2 files changed, 21 insertions(+), 58 deletions(-) delete mode 100644 examples/openai-agents/src/logger.ts diff --git a/examples/openai-agents/src/index.ts b/examples/openai-agents/src/index.ts index 8faf0fa4..b099a7b9 100644 --- a/examples/openai-agents/src/index.ts +++ b/examples/openai-agents/src/index.ts @@ -4,8 +4,6 @@ export { Sandbox }; // export the Sandbox class for the worker import { Agent, run, shellTool, applyPatchTool } from '@openai/agents'; -import { logger } from './logger'; - // Helper functions for error handling function isErrorWithProperties(error: unknown): error is { message?: string; @@ -33,43 +31,49 @@ function getErrorStack(error: unknown): string | undefined { } async function handleRunRequest(request: Request, env: Env): Promise { - logger.debug('handleRunRequest called', { + console.debug('[openai-example]', 'handleRunRequest called', { method: request.method, url: request.url }); try { // Parse request body - logger.debug('Parsing request body'); + console.debug('[openai-example]', 'Parsing request body'); const body = (await request.json()) as { input?: string }; const input = body.input; if (!input || typeof input !== 'string') { - logger.warn('Invalid or missing input field', { input }); + console.warn('[openai-example]', 'Invalid or missing input field', { + input + }); return new Response( JSON.stringify({ error: 'Missing or invalid input field' }), { status: 400, headers: { 'Content-Type': 'application/json' } } ); } - logger.info('Processing request', { inputLength: input.length }); + console.info('[openai-example]', 'Processing request', { + inputLength: input.length + }); // Get sandbox instance (reused for both shell and editor) - logger.debug('Getting sandbox instance', { + console.debug('[openai-example]', 'Getting sandbox instance', { sessionId: 'workspace-session' }); const sandbox = getSandbox(env.Sandbox, 'workspace-session'); // Create shell (automatically collects results) - logger.debug('Creating SandboxShell'); + console.debug('[openai-example]', 'Creating SandboxShell'); const shell = new Shell(sandbox); // Create workspace editor - logger.debug('Creating WorkspaceEditor', { root: '/workspace' }); + console.debug('[openai-example]', 'Creating WorkspaceEditor', { + root: '/workspace' + }); const editor = new Editor(sandbox, '/workspace'); // Create agent with both shell and patch tools, auto-approval for web API - logger.debug('Creating Agent', { + console.debug('[openai-example]', 'Creating Agent', { name: 'Sandbox Studio', model: 'gpt-5.1' }); @@ -91,9 +95,9 @@ async function handleRunRequest(request: Request, env: Env): Promise { }); // Run the agent - logger.info('Running agent', { input }); + console.info('[openai-example]', 'Running agent', { input }); const result = await run(agent, input); - logger.debug('Agent run completed', { + console.debug('[openai-example]', 'Agent run completed', { hasOutput: !!result.finalOutput, outputLength: result.finalOutput?.length || 0 }); @@ -104,7 +108,7 @@ async function handleRunRequest(request: Request, env: Env): Promise { ...editor.results.map((r) => ({ type: 'file' as const, ...r })) ].sort((a, b) => a.timestamp - b.timestamp); - logger.debug('Results collected', { + console.debug('[openai-example]', 'Results collected', { commandResults: shell.results.length, fileOperations: editor.results.length, totalResults: allResults.length @@ -117,7 +121,7 @@ async function handleRunRequest(request: Request, env: Env): Promise { fileOperations: editor.results.sort((a, b) => a.timestamp - b.timestamp) }; - logger.info('Request completed successfully'); + console.info('[openai-example]', 'Request completed successfully'); return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' @@ -126,7 +130,7 @@ async function handleRunRequest(request: Request, env: Env): Promise { } catch (error: unknown) { const errorMessage = getErrorMessage(error); const errorStack = getErrorStack(error); - logger.error('Error handling run request', { + console.error('[openai-example]', 'Error handling run request', { error: errorMessage, stack: errorStack }); @@ -154,7 +158,7 @@ export default { _ctx: ExecutionContext ): Promise { const url = new URL(request.url); - logger.debug('Fetch handler called', { + console.debug('[openai-example]', 'Fetch handler called', { pathname: url.pathname, method: request.method }); @@ -163,7 +167,7 @@ export default { return handleRunRequest(request, env); } - logger.warn('Route not found', { + console.warn('[openai-example]', 'Route not found', { pathname: url.pathname, method: request.method }); diff --git a/examples/openai-agents/src/logger.ts b/examples/openai-agents/src/logger.ts deleted file mode 100644 index 13e338c1..00000000 --- a/examples/openai-agents/src/logger.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Global flag to enable/disable logging -const DEBUG = true; - -class Logger { - private enabled: boolean; - - constructor(enabled: boolean = DEBUG) { - this.enabled = enabled; - } - - log(message: string, ...args: any[]): void { - if (this.enabled) { - console.log(`[LOG] ${message}`, ...args); - } - } - - info(message: string, ...args: any[]): void { - if (this.enabled) { - console.info(`[INFO] ${message}`, ...args); - } - } - - warn(message: string, ...args: any[]): void { - if (this.enabled) { - console.warn(`[WARN] ${message}`, ...args); - } - } - - error(message: string, ...args: any[]): void { - // Errors are always logged regardless of DEBUG flag - console.error(`[ERROR] ${message}`, ...args); - } - - debug(message: string, ...args: any[]): void { - if (this.enabled) { - console.debug(`[DEBUG] ${message}`, ...args); - } - } -} - -export const logger = new Logger(); From b269f52e5632a075ef2c113ad6f83697f4adf707 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Mon, 17 Nov 2025 19:12:57 +0000 Subject: [PATCH 10/15] Refactor imports and fix useEffect dependencies Reordered import statements for consistency in app.tsx, index.ts, and vite.config.ts. Fixed useEffect dependency array in App component to prevent unnecessary scrolling. Added explicit type to clear history button and improved key usage in list rendering. --- examples/openai-agents/src/app.tsx | 26 +++++++++++++++---- examples/openai-agents/src/index.ts | 6 ++--- examples/openai-agents/vite.config.ts | 4 +-- package-lock.json | 37 +++++++++++++++------------ 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/examples/openai-agents/src/app.tsx b/examples/openai-agents/src/app.tsx index 6b221ff8..45871403 100644 --- a/examples/openai-agents/src/app.tsx +++ b/examples/openai-agents/src/app.tsx @@ -1,5 +1,5 @@ +import { useEffect, useRef, useState } from 'react'; import { createRoot } from 'react-dom/client'; -import { useState, useEffect, useRef } from 'react'; import './index.css'; import type { CommandResult, @@ -72,7 +72,7 @@ function App() { // Scroll to bottom whenever messages change useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); - }, [messages, loading]); + }, []); // Focus input on mount useEffect(() => { @@ -196,7 +196,13 @@ function App() { if (result.type === 'command') { return ( -
+
$ {result.command} @@ -219,7 +225,13 @@ function App() { ); } else { return ( -
+
{result.operation === 'create' && '📄 Create'} @@ -268,7 +280,11 @@ function App() {

Sandbox Studio

{messages.length > 0 && ( - )} diff --git a/examples/openai-agents/src/index.ts b/examples/openai-agents/src/index.ts index b099a7b9..992a3859 100644 --- a/examples/openai-agents/src/index.ts +++ b/examples/openai-agents/src/index.ts @@ -1,8 +1,8 @@ -import { Sandbox, getSandbox } from '@cloudflare/sandbox'; -import { Shell, Editor } from '@cloudflare/sandbox/openai'; +import { getSandbox, Sandbox } from '@cloudflare/sandbox'; +import { Editor, Shell } from '@cloudflare/sandbox/openai'; export { Sandbox }; // export the Sandbox class for the worker -import { Agent, run, shellTool, applyPatchTool } from '@openai/agents'; +import { Agent, applyPatchTool, run, shellTool } from '@openai/agents'; // Helper functions for error handling function isErrorWithProperties(error: unknown): error is { diff --git a/examples/openai-agents/vite.config.ts b/examples/openai-agents/vite.config.ts index 62952875..a32e36dc 100644 --- a/examples/openai-agents/vite.config.ts +++ b/examples/openai-agents/vite.config.ts @@ -1,6 +1,6 @@ -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; import { cloudflare } from '@cloudflare/vite-plugin'; +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; export default defineConfig({ plugins: [react(), cloudflare()] diff --git a/package-lock.json b/package-lock.json index 78645a36..d61877d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -217,6 +217,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1714,6 +1715,7 @@ "integrity": "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.7", @@ -1847,7 +1849,8 @@ "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20251115.0.tgz", "integrity": "sha512-aM7jp7IfKhqKvfSaK1IhVTbSzxB6KQ4gX8e/W29tOuZk+YHlYXuRd/bMm4hWkfd7B1HWNWdsx1GTaEUoZIuVsw==", "dev": true, - "license": "MIT OR Apache-2.0" + "license": "MIT OR Apache-2.0", + "peer": true }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", @@ -3058,6 +3061,7 @@ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -4420,6 +4424,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.5.tgz", "integrity": "sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4556,6 +4561,7 @@ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", @@ -4571,6 +4577,7 @@ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", @@ -4599,6 +4606,7 @@ "integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "fflate": "^0.8.2", @@ -4903,6 +4911,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -6830,7 +6839,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6852,7 +6860,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6874,7 +6881,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6896,7 +6902,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6918,7 +6923,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6940,7 +6944,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6962,7 +6965,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -6984,7 +6986,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7006,7 +7007,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7028,7 +7028,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7050,7 +7049,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7094,7 +7092,6 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "license": "MIT", - "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -8238,7 +8235,6 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8794,6 +8790,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -8814,8 +8811,7 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-katex": { "version": "3.1.0", @@ -9010,6 +9006,7 @@ "integrity": "sha512-JFULvCNl/anKn99eKjOSEubi0lLmNqQDAjyEMME2T4CwezUDL0i6t1O9xZsu2OMehPnV2caNefWpGF+8TnzB6A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@oxc-project/types": "=0.97.0", "@rolldown/pluginutils": "1.0.0-beta.50" @@ -9888,6 +9885,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10070,6 +10068,7 @@ "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -10278,6 +10277,7 @@ "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "pathe": "^2.0.3" } @@ -10519,6 +10519,7 @@ "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -10635,6 +10636,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10648,6 +10650,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -10803,6 +10806,7 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "bin": { "workerd": "bin/workerd" }, @@ -11453,6 +11457,7 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "bin": { "workerd": "bin/workerd" }, From 5df7c3fa5bdf8860e5f28fa41f866954492091b4 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Tue, 18 Nov 2025 10:39:24 +0000 Subject: [PATCH 11/15] Add path traversal protection to Editor operations Introduces strict path normalization and validation in the Editor class to prevent file operations outside the workspace via path traversal (e.g., '../'). Extensive tests were added to verify security against various traversal attempts. The README now includes a security warning about auto-approval and risks. --- examples/openai-agents/README.md | 10 + packages/sandbox/src/openai/index.ts | 29 ++- .../sandbox/tests/openai-shell-editor.test.ts | 235 +++++++++++++++++- 3 files changed, 259 insertions(+), 15 deletions(-) diff --git a/examples/openai-agents/README.md b/examples/openai-agents/README.md index 8de96674..531bca28 100644 --- a/examples/openai-agents/README.md +++ b/examples/openai-agents/README.md @@ -30,3 +30,13 @@ All conversations are saved in your browser's localStorage. ```bash npm run deploy ``` + +## Security Warning + +**This example auto-approves all AI operations without human review.** The AI can: + +- Execute ANY shell command +- Create, modify, or delete ANY file in /workspace +- No safety limits beyond the container itself + +**Do not use in production without proper approval flows and rate limiting.** diff --git a/packages/sandbox/src/openai/index.ts b/packages/sandbox/src/openai/index.ts index 9b353b49..43a49376 100644 --- a/packages/sandbox/src/openai/index.ts +++ b/packages/sandbox/src/openai/index.ts @@ -406,12 +406,35 @@ export class Editor implements OpenAIEeditor { // Remove leading ./ or / if present, then join with root const normalized = relativePath.replace(/^\.\//, '').replace(/^\//, ''); const resolved = normalized ? `${this.root}/${normalized}` : this.root; + + // Normalize path separators first + const pathWithNormalizedSeparators = resolved.replace(/\/+/g, '/'); + + // Normalize .. segments by processing path segments + const segments = pathWithNormalizedSeparators + .split('/') + .filter((s) => s && s !== '.'); + const stack: string[] = []; + + for (const segment of segments) { + if (segment === '..') { + if (stack.length === 0) { + throw new Error(`Operation outside workspace: ${relativePath}`); + } + stack.pop(); + } else { + stack.push(segment); + } + } + + const normalizedPath = `/${stack.join('/')}`; + // Ensure the resolved path is within the workspace - if (!resolved.startsWith(this.root)) { + if (!normalizedPath.startsWith(this.root)) { throw new Error(`Operation outside workspace: ${relativePath}`); } - // Normalize path separators - return resolved.replace(/\/+/g, '/'); + + return normalizedPath; } private getDirname(filePath: string): string { diff --git a/packages/sandbox/tests/openai-shell-editor.test.ts b/packages/sandbox/tests/openai-shell-editor.test.ts index b9be835b..1e042a59 100644 --- a/packages/sandbox/tests/openai-shell-editor.test.ts +++ b/packages/sandbox/tests/openai-shell-editor.test.ts @@ -1,6 +1,15 @@ import type { ApplyPatchOperation } from '@openai/agents'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { Editor, Shell } from '../src/openai/index.ts'; +import type { Sandbox } from '../src/sandbox.ts'; + +interface MockSandbox { + exec?: ReturnType; + mkdir?: ReturnType; + writeFile?: ReturnType; + readFile?: ReturnType; + deleteFile?: ReturnType; +} const { loggerSpies, createLoggerMock, applyDiffMock } = vi.hoisted(() => { const logger = { @@ -38,7 +47,8 @@ describe('Shell', () => { exitCode: 0 }); - const shell = new Shell({ exec: execMock } as unknown as any); + const mockSandbox: MockSandbox = { exec: execMock }; + const shell = new Shell(mockSandbox as unknown as Sandbox); const result = await shell.run({ commands: ['echo hello'], @@ -67,7 +77,8 @@ describe('Shell', () => { const timeoutError = new Error('Command timed out'); const execMock = vi.fn().mockRejectedValue(timeoutError); - const shell = new Shell({ exec: execMock } as unknown as any); + const mockSandbox: MockSandbox = { exec: execMock }; + const shell = new Shell(mockSandbox as unknown as Sandbox); const action = { commands: ['sleep 1', 'echo never'], timeoutMs: 25 @@ -101,12 +112,12 @@ describe('Editor', () => { it('creates files using applyDiff output', async () => { applyDiffMock.mockReturnValueOnce('file contents'); - const sandbox = { + const mockSandbox: MockSandbox = { mkdir: vi.fn().mockResolvedValue(undefined), writeFile: vi.fn().mockResolvedValue(undefined) }; - const editor = new Editor(sandbox as unknown as any); + const editor = new Editor(mockSandbox as unknown as Sandbox); const operation = { type: 'create_file', path: 'src/app.ts', @@ -116,10 +127,10 @@ describe('Editor', () => { await editor.createFile(operation); expect(applyDiffMock).toHaveBeenCalledWith('', operation.diff, 'create'); - expect(sandbox.mkdir).toHaveBeenCalledWith('/workspace/src', { + expect(mockSandbox.mkdir).toHaveBeenCalledWith('/workspace/src', { recursive: true }); - expect(sandbox.writeFile).toHaveBeenCalledWith( + expect(mockSandbox.writeFile).toHaveBeenCalledWith( '/workspace/src/app.ts', 'file contents', { encoding: 'utf-8' } @@ -138,12 +149,12 @@ describe('Editor', () => { it('applies diffs when updating existing files', async () => { applyDiffMock.mockReturnValueOnce('patched content'); - const sandbox = { + const mockSandbox: MockSandbox = { readFile: vi.fn().mockResolvedValue({ content: 'original content' }), writeFile: vi.fn().mockResolvedValue(undefined) }; - const editor = new Editor(sandbox as unknown as any); + const editor = new Editor(mockSandbox as unknown as Sandbox); const operation = { type: 'update_file', path: 'README.md', @@ -152,14 +163,14 @@ describe('Editor', () => { await editor.updateFile(operation); - expect(sandbox.readFile).toHaveBeenCalledWith('/workspace/README.md', { + expect(mockSandbox.readFile).toHaveBeenCalledWith('/workspace/README.md', { encoding: 'utf-8' }); expect(applyDiffMock).toHaveBeenCalledWith( 'original content', operation.diff ); - expect(sandbox.writeFile).toHaveBeenCalledWith( + expect(mockSandbox.writeFile).toHaveBeenCalledWith( '/workspace/README.md', 'patched content', { encoding: 'utf-8' } @@ -173,11 +184,11 @@ describe('Editor', () => { it('throws descriptive error when attempting to update a missing file', async () => { const missingError = Object.assign(new Error('not found'), { status: 404 }); - const sandbox = { + const mockSandbox: MockSandbox = { readFile: vi.fn().mockRejectedValue(missingError) }; - const editor = new Editor(sandbox as unknown as any); + const editor = new Editor(mockSandbox as unknown as Sandbox); const operation = { type: 'update_file', path: 'missing.txt', @@ -193,4 +204,204 @@ describe('Editor', () => { { path: 'missing.txt' } ); }); + + describe('Path traversal security', () => { + it('should reject path traversal attempts with ../', async () => { + const mockSandbox: MockSandbox = { + mkdir: vi.fn(), + writeFile: vi.fn() + }; + + const editor = new Editor(mockSandbox as unknown as Sandbox); + const operation = { + type: 'create_file', + path: '../etc/passwd', + diff: 'malicious content' + } as Extract; + + await expect(editor.createFile(operation)).rejects.toThrow( + 'Operation outside workspace: ../etc/passwd' + ); + expect(mockSandbox.writeFile).not.toHaveBeenCalled(); + }); + + it('should reject path traversal attempts with ../../', async () => { + const mockSandbox: MockSandbox = { + mkdir: vi.fn(), + writeFile: vi.fn() + }; + + const editor = new Editor(mockSandbox as unknown as Sandbox); + const operation = { + type: 'create_file', + path: '../../etc/passwd', + diff: 'malicious content' + } as Extract; + + await expect(editor.createFile(operation)).rejects.toThrow( + 'Operation outside workspace: ../../etc/passwd' + ); + expect(mockSandbox.writeFile).not.toHaveBeenCalled(); + }); + + it('should reject path traversal attempts with mixed paths like src/../../etc/passwd', async () => { + const mockSandbox: MockSandbox = { + mkdir: vi.fn(), + writeFile: vi.fn() + }; + + const editor = new Editor(mockSandbox as unknown as Sandbox); + const operation = { + type: 'create_file', + path: 'src/../../etc/passwd', + diff: 'malicious content' + } as Extract; + + await expect(editor.createFile(operation)).rejects.toThrow( + 'Operation outside workspace: src/../../etc/passwd' + ); + expect(mockSandbox.writeFile).not.toHaveBeenCalled(); + }); + + it('should reject path traversal attempts with leading slash /../../etc/passwd', async () => { + const mockSandbox: MockSandbox = { + mkdir: vi.fn(), + writeFile: vi.fn() + }; + + const editor = new Editor(mockSandbox as unknown as Sandbox); + const operation = { + type: 'create_file', + path: '/../../etc/passwd', + diff: 'malicious content' + } as Extract; + + await expect(editor.createFile(operation)).rejects.toThrow( + 'Operation outside workspace: /../../etc/passwd' + ); + expect(mockSandbox.writeFile).not.toHaveBeenCalled(); + }); + + it('should reject path traversal attempts with leading dot-slash ./../../etc/passwd', async () => { + const mockSandbox: MockSandbox = { + mkdir: vi.fn(), + writeFile: vi.fn() + }; + + const editor = new Editor(mockSandbox as unknown as Sandbox); + const operation = { + type: 'create_file', + path: './../../etc/passwd', + diff: 'malicious content' + } as Extract; + + await expect(editor.createFile(operation)).rejects.toThrow( + 'Operation outside workspace: ./../../etc/passwd' + ); + expect(mockSandbox.writeFile).not.toHaveBeenCalled(); + }); + + it('should reject path traversal in updateFile operations', async () => { + const mockSandbox: MockSandbox = { + readFile: vi.fn() + }; + + const editor = new Editor(mockSandbox as unknown as Sandbox); + const operation = { + type: 'update_file', + path: '../../etc/passwd', + diff: 'patch diff' + } as Extract; + + await expect(editor.updateFile(operation)).rejects.toThrow( + 'Operation outside workspace: ../../etc/passwd' + ); + expect(mockSandbox.readFile).not.toHaveBeenCalled(); + }); + + it('should reject path traversal in deleteFile operations', async () => { + const mockSandbox: MockSandbox = { + deleteFile: vi.fn() + }; + + const editor = new Editor(mockSandbox as unknown as Sandbox); + const operation = { + type: 'delete_file', + path: '../../etc/passwd' + } as Extract; + + await expect(editor.deleteFile(operation)).rejects.toThrow( + 'Operation outside workspace: ../../etc/passwd' + ); + expect(mockSandbox.deleteFile).not.toHaveBeenCalled(); + }); + + it('should allow valid paths that use .. but stay within workspace', async () => { + applyDiffMock.mockReturnValueOnce('file contents'); + + const mockSandbox: MockSandbox = { + mkdir: vi.fn().mockResolvedValue(undefined), + writeFile: vi.fn().mockResolvedValue(undefined) + }; + + const editor = new Editor(mockSandbox as unknown as Sandbox); + const operation = { + type: 'create_file', + path: 'src/subdir/../../file.txt', + diff: '--- diff ---' + } as Extract; + + await editor.createFile(operation); + + // Should resolve to /workspace/file.txt + expect(mockSandbox.writeFile).toHaveBeenCalledWith( + '/workspace/file.txt', + 'file contents', + { encoding: 'utf-8' } + ); + }); + + it('should handle paths with multiple consecutive slashes correctly', async () => { + applyDiffMock.mockReturnValueOnce('file contents'); + + const mockSandbox: MockSandbox = { + mkdir: vi.fn().mockResolvedValue(undefined), + writeFile: vi.fn().mockResolvedValue(undefined) + }; + + const editor = new Editor(mockSandbox as unknown as Sandbox); + const operation = { + type: 'create_file', + path: 'src//subdir///file.txt', + diff: '--- diff ---' + } as Extract; + + await editor.createFile(operation); + + expect(mockSandbox.writeFile).toHaveBeenCalledWith( + '/workspace/src/subdir/file.txt', + 'file contents', + { encoding: 'utf-8' } + ); + }); + + it('should reject deep path traversal attempts', async () => { + const mockSandbox: MockSandbox = { + mkdir: vi.fn(), + writeFile: vi.fn() + }; + + const editor = new Editor(mockSandbox as unknown as Sandbox); + const operation = { + type: 'create_file', + path: 'a/b/c/../../../../etc/passwd', + diff: 'malicious content' + } as Extract; + + await expect(editor.createFile(operation)).rejects.toThrow( + 'Operation outside workspace: a/b/c/../../../../etc/passwd' + ); + expect(mockSandbox.writeFile).not.toHaveBeenCalled(); + }); + }); }); From 5b07e5be0bbb0d9a4e5152d08d48f74134c77c97 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Tue, 18 Nov 2025 10:45:48 +0000 Subject: [PATCH 12/15] Create OPENAI_AGENTS.md --- docs/OPENAI_AGENTS.md | 323 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 docs/OPENAI_AGENTS.md diff --git a/docs/OPENAI_AGENTS.md b/docs/OPENAI_AGENTS.md new file mode 100644 index 00000000..187ad277 --- /dev/null +++ b/docs/OPENAI_AGENTS.md @@ -0,0 +1,323 @@ +# OpenAI Agents Adapter + +The Cloudflare Sandbox SDK provides adapters that integrate with the [OpenAI Agents SDK](https://github.com/openai/agents) to enable AI agents to execute shell commands and perform file operations inside sandboxed environments. + +## Overview + +The OpenAI Agents adapter consists of two main components: + +- **`Shell`**: Implements the OpenAI Agents `Shell` interface, allowing agents to execute shell commands in the sandbox +- **`Editor`**: Implements the OpenAI Agents `Editor` interface, enabling agents to create, update, and delete files using patch operations + +Both adapters automatically collect results from operations, making it easy to track what commands were executed and what files were modified during an agent session. + +## Installation + +The adapters are part of the `@cloudflare/sandbox` package: + +```typescript +import { getSandbox } from '@cloudflare/sandbox'; +import { Shell, Editor } from '@cloudflare/sandbox/openai'; +import { Agent, applyPatchTool, run, shellTool } from '@openai/agents'; +``` + +## Basic Usage + +### Setting Up an Agent + +```typescript +import { getSandbox } from '@cloudflare/sandbox'; +import { Shell, Editor } from '@cloudflare/sandbox/openai'; +import { Agent, applyPatchTool, run, shellTool } from '@openai/agents'; + +export default { + async fetch(request: Request, env: Env): Promise { + // Get a sandbox instance + const sandbox = getSandbox(env.Sandbox, 'workspace-session'); + + // Create shell adapter (executes commands in /workspace by default) + const shell = new Shell(sandbox); + + // Create editor adapter (operates on /workspace by default) + const editor = new Editor(sandbox, '/workspace'); + + // Create an agent with both tools + const agent = new Agent({ + name: 'Sandbox Assistant', + model: 'gpt-4', + instructions: + 'You can execute shell commands and edit files in the workspace.', + tools: [ + shellTool({ shell, needsApproval: false }), + applyPatchTool({ editor, needsApproval: false }) + ] + }); + + // Run the agent with user input + const { input } = await request.json(); + const result = await run(agent, input); + + // Access collected results + const commandResults = shell.results; + const fileOperations = editor.results; + + return new Response( + JSON.stringify({ + naturalResponse: result.finalOutput, + commandResults, + fileOperations + }), + { + headers: { 'Content-Type': 'application/json' } + } + ); + } +}; +``` + +## Shell Adapter + +The `Shell` class adapts Cloudflare Sandbox `exec` calls to the OpenAI Agents `Shell` contract. + +### Features + +- Executes commands sequentially in the sandbox +- Preserves working directory (`/workspace` by default) +- Handles timeouts and errors gracefully +- Collects results with timestamps for each command +- Separates stdout and stderr output + +### Command Results + +Each executed command is automatically collected in `shell.results`: + +```typescript +interface CommandResult { + command: string; // The command that was executed + stdout: string; // Standard output + stderr: string; // Standard error + exitCode: number | null; // Exit code (null for timeouts) + timestamp: number; // Unix timestamp in milliseconds +} +``` + +### Example: Inspecting Workspace + +```typescript +const shell = new Shell(sandbox); + +// Agent can execute commands like: +// - ls -la +// - cat package.json +// - git status +// - npm install + +// After agent execution, access results: +shell.results.forEach((result) => { + console.log(`Command: ${result.command}`); + console.log(`Exit code: ${result.exitCode}`); + console.log(`Output: ${result.stdout}`); +}); +``` + +### Error Handling + +The Shell adapter handles various error scenarios: + +- **Command failures**: Non-zero exit codes are captured in `exitCode` +- **Timeouts**: Commands that exceed the timeout return `exitCode: null` and `outcome.type: 'timeout'` +- **Network errors**: HTTP/network errors are caught and logged + +## Editor Adapter + +The `Editor` class implements file operations using the OpenAI Agents patch-based editing system. + +### Features + +- Creates files with initial content using diffs +- Updates existing files by applying diffs +- Deletes files +- Automatically creates parent directories when needed +- Validates paths to prevent operations outside the workspace +- Collects results with timestamps for each operation + +### File Operation Results + +Each file operation is automatically collected in `editor.results`: + +```typescript +interface FileOperationResult { + operation: 'create' | 'update' | 'delete'; + path: string; // Relative path from workspace root + status: 'completed' | 'failed'; + output: string; // Human-readable status message + error?: string; // Error message if status is 'failed' + timestamp: number; // Unix timestamp in milliseconds +} +``` + +### Path Resolution + +The Editor enforces security by: + +- Resolving relative paths within the workspace root (`/workspace` by default) +- Preventing path traversal attacks (e.g., `../../../etc/passwd`) +- Normalizing path separators and removing redundant segments +- Throwing errors for operations outside the workspace + +### Example: Creating and Editing Files + +```typescript +const editor = new Editor(sandbox, '/workspace'); + +// Agent can use apply_patch tool to: +// - Create new files with content +// - Update existing files with diffs +// - Delete files + +// After agent execution, access results: +editor.results.forEach((result) => { + console.log(`${result.operation}: ${result.path}`); + console.log(`Status: ${result.status}`); + if (result.error) { + console.log(`Error: ${result.error}`); + } +}); +``` + +### Custom Workspace Root + +You can specify a custom workspace root: + +```typescript +// Use a different root directory +const editor = new Editor(sandbox, '/custom/workspace'); +``` + +## Complete Example + +Here's a complete example showing how to integrate the adapters in a Cloudflare Worker: + +```typescript +import { getSandbox } from '@cloudflare/sandbox'; +import { Shell, Editor } from '@cloudflare/sandbox/openai'; +import { Agent, applyPatchTool, run, shellTool } from '@openai/agents'; + +async function handleRunRequest(request: Request, env: Env): Promise { + try { + const { input } = await request.json(); + + if (!input || typeof input !== 'string') { + return new Response( + JSON.stringify({ error: 'Missing or invalid input field' }), + { status: 400, headers: { 'Content-Type': 'application/json' } } + ); + } + + // Get sandbox instance (reused for both shell and editor) + const sandbox = getSandbox(env.Sandbox, 'workspace-session'); + + // Create adapters + const shell = new Shell(sandbox); + const editor = new Editor(sandbox, '/workspace'); + + // Create agent with tools + const agent = new Agent({ + name: 'Sandbox Studio', + model: 'gpt-4', + instructions: ` + You can execute shell commands and edit files in the workspace. + Use shell commands to inspect the repository and the apply_patch tool + to create, update, or delete files. Keep responses concise and include + command output when helpful. + `, + tools: [ + shellTool({ shell, needsApproval: false }), + applyPatchTool({ editor, needsApproval: false }) + ] + }); + + // Run the agent + const result = await run(agent, input); + + // Format response with sorted results + const response = { + naturalResponse: result.finalOutput || null, + commandResults: shell.results.sort((a, b) => a.timestamp - b.timestamp), + fileOperations: editor.results.sort((a, b) => a.timestamp - b.timestamp) + }; + + return new Response(JSON.stringify(response), { + headers: { 'Content-Type': 'application/json' } + }); + } catch (error) { + return new Response( + JSON.stringify({ + error: error instanceof Error ? error.message : 'Internal server error', + naturalResponse: 'An error occurred while processing your request.', + commandResults: [], + fileOperations: [] + }), + { + status: 500, + headers: { 'Content-Type': 'application/json' } + } + ); + } +} + +export default { + async fetch(request: Request, env: Env): Promise { + const url = new URL(request.url); + + if (url.pathname === '/run' && request.method === 'POST') { + return handleRunRequest(request, env); + } + + return new Response('Not found', { status: 404 }); + } +}; +``` + +## Result Tracking + +Both adapters automatically track all operations with timestamps. This makes it easy to: + +- **Audit operations**: See exactly what commands were run and files were modified +- **Debug issues**: Identify which operation failed and when +- **Build UIs**: Display a timeline of agent actions +- **Logging**: Export operation history for analysis + +### Combining Results + +You can combine and sort results from both adapters: + +```typescript +const allResults = [ + ...shell.results.map((r) => ({ type: 'command' as const, ...r })), + ...editor.results.map((r) => ({ type: 'file' as const, ...r })) +].sort((a, b) => a.timestamp - b.timestamp); + +// allResults is now a chronological list of all operations +``` + +## Best Practices + +1. **Reuse sandbox instances**: Create one sandbox instance and share it between Shell and Editor +2. **Set appropriate timeouts**: Configure command timeouts based on expected operation duration +3. **Handle errors gracefully**: Check `status` fields in results and handle `failed` operations +4. **Validate paths**: The Editor already validates paths, but be aware of workspace boundaries +5. **Monitor resource usage**: Large command outputs or file operations may impact performance + +## Limitations + +- **Working directory**: Shell operations always execute in `/workspace` (or the configured root) +- **Path restrictions**: File operations are restricted to the workspace root +- **Sequential execution**: Commands execute sequentially, not in parallel +- **Timeout handling**: Timeouts stop further command execution in a batch + +## See Also + +- [OpenAI Agents SDK Documentation](https://github.com/openai/openai-agents-js/) +- [Session Execution Architecture](./SESSION_EXECUTION.md) - Understanding how commands execute in sandboxes +- [Example Implementation](../examples/openai-agents/src/index.ts) - Full working example From f2a9fc83f4b76f59f37d3e7e3c0cd76501d68ce9 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Wed, 19 Nov 2025 13:51:30 +0000 Subject: [PATCH 13/15] use a seesion id to key the sandbox instance --- examples/openai-agents/package.json | 1 + examples/openai-agents/src/app.tsx | 19 ++++++++++++++++++- examples/openai-agents/src/index.ts | 22 ++++++++++++++++++---- package-lock.json | 2 +- 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/examples/openai-agents/package.json b/examples/openai-agents/package.json index e40e3e54..f9890bfa 100644 --- a/examples/openai-agents/package.json +++ b/examples/openai-agents/package.json @@ -15,6 +15,7 @@ "type": "module", "dependencies": { "@openai/agents": "^0.3.2", + "nanoid": "^3.3.11", "react": "^19.2.0", "react-dom": "^19.2.0" }, diff --git a/examples/openai-agents/src/app.tsx b/examples/openai-agents/src/app.tsx index 45871403..9cff2316 100644 --- a/examples/openai-agents/src/app.tsx +++ b/examples/openai-agents/src/app.tsx @@ -5,6 +5,7 @@ import type { CommandResult, FileOperationResult } from '@cloudflare/sandbox/openai'; +import { nanoid } from 'nanoid'; interface Response { naturalResponse: string | null; @@ -19,6 +20,21 @@ interface Message { timestamp: number; } +/** + * Get or create a session ID for this user. + * The session ID is stored in localStorage and persists across browser sessions. + */ +function getOrCreateSessionId(): string { + let sessionId = localStorage.getItem('session-id'); + + if (!sessionId) { + sessionId = nanoid(8); + localStorage.setItem('session-id', sessionId); + } + + return sessionId; +} + const STORAGE_KEY = 'openai-agents-history'; async function makeApiCall(input: string): Promise { @@ -26,7 +42,8 @@ async function makeApiCall(input: string): Promise { const response = await fetch('/run', { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'X-Session-Id': getOrCreateSessionId() }, body: JSON.stringify({ input }) }); diff --git a/examples/openai-agents/src/index.ts b/examples/openai-agents/src/index.ts index 992a3859..21da90a4 100644 --- a/examples/openai-agents/src/index.ts +++ b/examples/openai-agents/src/index.ts @@ -30,7 +30,11 @@ function getErrorStack(error: unknown): string | undefined { return undefined; } -async function handleRunRequest(request: Request, env: Env): Promise { +async function handleRunRequest( + request: Request, + env: Env, + sessionId: string +): Promise { console.debug('[openai-example]', 'handleRunRequest called', { method: request.method, url: request.url @@ -58,9 +62,9 @@ async function handleRunRequest(request: Request, env: Env): Promise { // Get sandbox instance (reused for both shell and editor) console.debug('[openai-example]', 'Getting sandbox instance', { - sessionId: 'workspace-session' + sandboxId: `session-${sessionId}` }); - const sandbox = getSandbox(env.Sandbox, 'workspace-session'); + const sandbox = getSandbox(env.Sandbox, `session-${sessionId}`); // Create shell (automatically collects results) console.debug('[openai-example]', 'Creating SandboxShell'); @@ -163,8 +167,18 @@ export default { method: request.method }); + const sessionId = request.headers.get('X-Session-Id'); + console.log({ sessionId }); + if (!sessionId) { + return new Response('Missing X-Session-Id header', { status: 400 }); + } + + if (url.pathname === '/.well-known/appspecific/com.chrome.devtools.json') { + return Response.json({}); + } + if (url.pathname === '/run' && request.method === 'POST') { - return handleRunRequest(request, env); + return handleRunRequest(request, env, sessionId); } console.warn('[openai-example]', 'Route not found', { diff --git a/package-lock.json b/package-lock.json index d61877d3..62d521cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -103,6 +103,7 @@ "license": "ISC", "dependencies": { "@openai/agents": "^0.3.2", + "nanoid": "^3.3.11", "react": "^19.2.0", "react-dom": "^19.2.0" }, @@ -8177,7 +8178,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", From ecaa077c89a6a933213af85c008a2f3adea6884d Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Wed, 19 Nov 2025 13:54:40 +0000 Subject: [PATCH 14/15] Update lovely-friends-study.md --- .changeset/lovely-friends-study.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/lovely-friends-study.md b/.changeset/lovely-friends-study.md index 994b24d7..3202773f 100644 --- a/.changeset/lovely-friends-study.md +++ b/.changeset/lovely-friends-study.md @@ -3,3 +3,5 @@ --- Add OpenAI Agents adapters + +Add OpenAI Agents adapters (`Shell` and `Editor`) that integrate Cloudflare Sandbox with the OpenAI Agents SDK. These adapters enable AI agents to execute shell commands and perform file operations (create, update, delete) inside sandboxed environments. Both adapters automatically collect and timestamp results from operations, making it easy to track command execution and file modifications during agent sessions. The adapters are exported from `@cloudflare/sandbox/openai` and implement the OpenAI Agents `Shell` and `Editor` interfaces. From 01445588a5bead7758946b569041c17eafa39bde Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Wed, 19 Nov 2025 14:16:14 +0000 Subject: [PATCH 15/15] Fix path resolution for absolute workspace paths Updated the Editor's resolve method to correctly handle absolute paths that start with the workspace root, preventing duplication of the root in resolved paths. Added a test to verify correct behavior when creating files with absolute paths within the workspace. --- packages/sandbox/src/openai/index.ts | 10 ++++++- .../sandbox/tests/openai-shell-editor.test.ts | 27 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/sandbox/src/openai/index.ts b/packages/sandbox/src/openai/index.ts index 43a49376..a2b5311b 100644 --- a/packages/sandbox/src/openai/index.ts +++ b/packages/sandbox/src/openai/index.ts @@ -403,8 +403,16 @@ export class Editor implements OpenAIEeditor { } private resolve(relativePath: string): string { + // If the path already starts with the root, strip it to get the relative part + let pathToProcess = relativePath; + if (relativePath.startsWith(this.root)) { + pathToProcess = relativePath.slice(this.root.length); + // Remove leading slash if present after stripping root + pathToProcess = pathToProcess.replace(/^\//, ''); + } + // Remove leading ./ or / if present, then join with root - const normalized = relativePath.replace(/^\.\//, '').replace(/^\//, ''); + const normalized = pathToProcess.replace(/^\.\//, '').replace(/^\//, ''); const resolved = normalized ? `${this.root}/${normalized}` : this.root; // Normalize path separators first diff --git a/packages/sandbox/tests/openai-shell-editor.test.ts b/packages/sandbox/tests/openai-shell-editor.test.ts index 1e042a59..7df23f54 100644 --- a/packages/sandbox/tests/openai-shell-editor.test.ts +++ b/packages/sandbox/tests/openai-shell-editor.test.ts @@ -403,5 +403,32 @@ describe('Editor', () => { ); expect(mockSandbox.writeFile).not.toHaveBeenCalled(); }); + + it('should handle absolute paths within workspace', async () => { + applyDiffMock.mockReturnValueOnce('content'); + + const mockSandbox: MockSandbox = { + mkdir: vi.fn().mockResolvedValue(undefined), + writeFile: vi.fn().mockResolvedValue(undefined) + }; + + const editor = new Editor( + mockSandbox as unknown as Sandbox, + '/workspace' + ); + const operation = { + type: 'create_file', + path: '/workspace/file.txt', + diff: 'content' + } as Extract; + + await editor.createFile(operation); + + expect(mockSandbox.writeFile).toHaveBeenCalledWith( + '/workspace/file.txt', // Not /workspace/workspace/file.txt + 'content', + { encoding: 'utf-8' } + ); + }); }); });