@@ -40,6 +40,7 @@ class ModalRuntime(ActionExecutionClient):
40
40
41
41
container_name_prefix = 'openhands-sandbox-'
42
42
sandbox : modal .Sandbox | None
43
+ sid : str
43
44
44
45
def __init__ (
45
46
self ,
@@ -57,6 +58,7 @@ def __init__(
57
58
58
59
self .config = config
59
60
self .sandbox = None
61
+ self .sid = sid
60
62
61
63
self .modal_client = modal .Client .from_credentials (
62
64
config .modal_api_token_id .get_secret_value (),
@@ -75,6 +77,8 @@ def __init__(
75
77
76
78
# This value is arbitrary as it's private to the container
77
79
self .container_port = 3000
80
+ self ._vscode_port = 4445
81
+ self ._vscode_url : str | None = None
78
82
79
83
self .status_callback = status_callback
80
84
self .base_container_image_id = self .config .sandbox .base_container_image
@@ -140,6 +144,7 @@ async def connect(self):
140
144
141
145
if not self .attach_to_existing :
142
146
self .send_status_message (' ' )
147
+ self ._runtime_initialized = True
143
148
144
149
def _get_action_execution_server_host (self ):
145
150
return self .api_url
@@ -208,6 +213,7 @@ def _init_sandbox(
208
213
environment : dict [str , str | None ] = {
209
214
'port' : str (self .container_port ),
210
215
'PYTHONUNBUFFERED' : '1' ,
216
+ 'VSCODE_PORT' : str (self ._vscode_port ),
211
217
}
212
218
if self .config .debug :
213
219
environment ['DEBUG' ] = 'true'
@@ -225,7 +231,7 @@ def _init_sandbox(
225
231
* sandbox_start_cmd ,
226
232
secrets = [env_secret ],
227
233
workdir = '/openhands/code' ,
228
- encrypted_ports = [self .container_port ],
234
+ encrypted_ports = [self .container_port , self . _vscode_port ],
229
235
image = self .image ,
230
236
app = self .app ,
231
237
client = self .modal_client ,
@@ -248,3 +254,27 @@ def close(self):
248
254
249
255
if not self .attach_to_existing and self .sandbox :
250
256
self .sandbox .terminate ()
257
+
258
+ @property
259
+ def vscode_url (self ) -> str | None :
260
+ if self ._vscode_url is not None : # cached value
261
+ self .log ('debug' , f'VSCode URL: { self ._vscode_url } ' )
262
+ return self ._vscode_url
263
+ token = super ().get_vscode_token ()
264
+ if not token :
265
+ self .log ('error' , 'VSCode token not found' )
266
+ return None
267
+ if not self .sandbox :
268
+ self .log ('error' , 'Sandbox not initialized' )
269
+ return None
270
+
271
+ tunnel = self .sandbox .tunnels ()[self ._vscode_port ]
272
+ tunnel_url = tunnel .url
273
+ self ._vscode_url = tunnel_url + f'/?tkn={ token } &folder={ self .config .workspace_mount_path_in_sandbox } '
274
+
275
+ self .log (
276
+ 'debug' ,
277
+ f'VSCode URL: { self ._vscode_url } ' ,
278
+ )
279
+
280
+ return self ._vscode_url
0 commit comments