Skip to content

Commit 65633b7

Browse files
authored
Update sdk run as sandbox (#71)
* runAs update * udpate parameter passing * remove uv lock * bump version * dedupe
1 parent 6456c73 commit 65633b7

16 files changed

Lines changed: 882 additions & 467 deletions

File tree

hyperbrowser/client/managers/async_manager/sandbox.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -217,16 +217,24 @@ async def create_runtime_session(
217217
)
218218
return _copy_model(self._runtime_session)
219219

220-
async def exec(self, input: Union[str, SandboxExecParams]):
221-
if isinstance(input, str):
222-
params = SandboxExecParams(command=input)
223-
else:
224-
if not isinstance(input, SandboxExecParams):
225-
raise TypeError(
226-
"input must be a command string or SandboxExecParams instance"
227-
)
228-
params = input
229-
return await self.processes.exec(params)
220+
async def exec(
221+
self,
222+
input: Union[str, SandboxExecParams],
223+
*,
224+
cwd: Optional[str] = None,
225+
env: Optional[Dict[str, str]] = None,
226+
timeout_ms: Optional[int] = None,
227+
timeout_sec: Optional[int] = None,
228+
run_as: Optional[str] = None,
229+
):
230+
return await self.processes.exec(
231+
input,
232+
cwd=cwd,
233+
env=env,
234+
timeout_ms=timeout_ms,
235+
timeout_sec=timeout_sec,
236+
run_as=run_as,
237+
)
230238

231239
async def get_process(self, process_id: str) -> SandboxProcessHandle:
232240
return await self.processes.get(process_id)

hyperbrowser/client/managers/async_manager/sandboxes/sandbox_files.py

Lines changed: 105 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import json
66
import socket
77
from datetime import datetime
8-
from typing import AsyncIterator, Callable, List, Optional, Union
8+
from typing import Any, AsyncIterator, Callable, Dict, List, Optional, Union
99
from urllib.parse import urlencode
1010

1111
from websockets.asyncio.client import connect as async_ws_connect
@@ -249,10 +249,21 @@ def __init__(
249249
transport: RuntimeTransport,
250250
get_connection_info,
251251
runtime_proxy_override: Optional[str] = None,
252+
default_run_as: Optional[str] = None,
252253
):
253254
self._transport = transport
254255
self._get_connection_info = get_connection_info
255256
self._runtime_proxy_override = runtime_proxy_override
257+
self._default_run_as = default_run_as.strip() if default_run_as else None
258+
259+
def with_run_as(self, run_as: Optional[str]):
260+
normalized = run_as.strip() if run_as else None
261+
return SandboxFilesApi(
262+
self._transport,
263+
self._get_connection_info,
264+
self._runtime_proxy_override,
265+
default_run_as=normalized,
266+
)
256267

257268
async def list(
258269
self,
@@ -266,17 +277,19 @@ async def list(
266277

267278
payload = await self._transport.request_json(
268279
"/sandbox/files",
269-
params={
270-
"path": path,
271-
"depth": depth,
272-
},
280+
params=self._with_run_as_params(
281+
{
282+
"path": path,
283+
"depth": depth,
284+
}
285+
),
273286
)
274287
return [_normalize_file_info(entry) for entry in payload.get("entries", [])]
275288

276289
async def get_info(self, path: str) -> SandboxFileInfo:
277290
payload = await self._transport.request_json(
278291
"/sandbox/files/stat",
279-
params={"path": path},
292+
params=self._with_run_as_params({"path": path}),
280293
)
281294
return _normalize_file_info(payload["file"])
282295

@@ -357,10 +370,12 @@ async def write(
357370
payload = await self._transport.request_json(
358371
"/sandbox/files/write",
359372
method="POST",
360-
json_body={
361-
"path": path_or_files,
362-
**_encode_write_data(data),
363-
},
373+
json_body=self._with_run_as_body(
374+
{
375+
"path": path_or_files,
376+
**_encode_write_data(data),
377+
}
378+
),
364379
headers={"content-type": "application/json"},
365380
)
366381
return _normalize_write_info(payload["files"][0])
@@ -377,7 +392,7 @@ async def write(
377392
payload = await self._transport.request_json(
378393
"/sandbox/files/write",
379394
method="POST",
380-
json_body={"files": encoded_files},
395+
json_body=self._with_run_as_body({"files": encoded_files}),
381396
headers={"content-type": "application/json"},
382397
)
383398
return [_normalize_write_info(entry) for entry in payload.get("files", [])]
@@ -419,15 +434,15 @@ async def upload(self, path: str, data: Union[str, bytes, bytearray]):
419434
payload = await self._transport.request_json(
420435
"/sandbox/files/upload",
421436
method="PUT",
422-
params={"path": path},
437+
params=self._with_run_as_params({"path": path}),
423438
content=body,
424439
)
425440
return SandboxFileTransferResult(**payload)
426441

427442
async def download(self, path: str) -> bytes:
428443
return await self._transport.request_bytes(
429444
"/sandbox/files/download",
430-
params={"path": path},
445+
params=self._with_run_as_params({"path": path}),
431446
)
432447

433448
async def make_dir(
@@ -440,11 +455,13 @@ async def make_dir(
440455
payload = await self._transport.request_json(
441456
"/sandbox/files/mkdir",
442457
method="POST",
443-
json_body={
444-
"path": path,
445-
"parents": parents,
446-
"mode": mode,
447-
},
458+
json_body=self._with_run_as_body(
459+
{
460+
"path": path,
461+
"parents": parents,
462+
"mode": mode,
463+
}
464+
),
448465
headers={"content-type": "application/json"},
449466
)
450467
return bool(payload.get("created"))
@@ -475,7 +492,7 @@ async def rename(
475492
payload = await self._transport.request_json(
476493
"/sandbox/files/move",
477494
method="POST",
478-
json_body=payload,
495+
json_body=self._with_run_as_body(payload),
479496
headers={"content-type": "application/json"},
480497
)
481498
return _normalize_file_info(payload["entry"])
@@ -493,10 +510,12 @@ async def remove(self, path: str, *, recursive: Optional[bool] = None) -> None:
493510
await self._transport.request_json(
494511
"/sandbox/files/delete",
495512
method="POST",
496-
json_body=SandboxFileDeleteParams(
497-
path=path,
498-
recursive=recursive,
499-
).model_dump(exclude_none=True),
513+
json_body=self._with_run_as_body(
514+
SandboxFileDeleteParams(
515+
path=path,
516+
recursive=recursive,
517+
).model_dump(exclude_none=True)
518+
),
500519
headers={"content-type": "application/json"},
501520
)
502521

@@ -527,12 +546,14 @@ async def copy(
527546
payload = await self._transport.request_json(
528547
"/sandbox/files/copy",
529548
method="POST",
530-
json_body={
531-
"from": normalized.source,
532-
"to": normalized.destination,
533-
"recursive": normalized.recursive,
534-
"overwrite": normalized.overwrite,
535-
},
549+
json_body=self._with_run_as_body(
550+
{
551+
"from": normalized.source,
552+
"to": normalized.destination,
553+
"recursive": normalized.recursive,
554+
"overwrite": normalized.overwrite,
555+
}
556+
),
536557
headers={"content-type": "application/json"},
537558
)
538559
return _normalize_file_info(payload["entry"])
@@ -558,7 +579,7 @@ async def chmod(
558579
await self._transport.request_json(
559580
"/sandbox/files/chmod",
560581
method="POST",
561-
json_body=normalized.model_dump(exclude_none=True),
582+
json_body=self._with_run_as_body(normalized.model_dump(exclude_none=True)),
562583
headers={"content-type": "application/json"},
563584
)
564585

@@ -585,18 +606,20 @@ async def chown(
585606
await self._transport.request_json(
586607
"/sandbox/files/chown",
587608
method="POST",
588-
json_body=normalized.model_dump(exclude_none=True),
609+
json_body=self._with_run_as_body(normalized.model_dump(exclude_none=True)),
589610
headers={"content-type": "application/json"},
590611
)
591612

592613
async def watch(self, path: str, *, recursive: Optional[bool] = None):
593614
payload = await self._transport.request_json(
594615
"/sandbox/files/watch",
595616
method="POST",
596-
json_body={
597-
"path": path,
598-
"recursive": recursive,
599-
},
617+
json_body=self._with_run_as_body(
618+
{
619+
"path": path,
620+
"recursive": recursive,
621+
}
622+
),
600623
headers={"content-type": "application/json"},
601624
)
602625
return SandboxFileWatchHandle(
@@ -646,11 +669,13 @@ async def upload_url(
646669
payload = await self._transport.request_json(
647670
"/sandbox/files/presign-upload",
648671
method="POST",
649-
json_body=SandboxPresignFileParams(
650-
path=path,
651-
expires_in_seconds=expires_in_seconds,
652-
one_time=one_time,
653-
).model_dump(exclude_none=True, by_alias=True),
672+
json_body=self._with_run_as_body(
673+
SandboxPresignFileParams(
674+
path=path,
675+
expires_in_seconds=expires_in_seconds,
676+
one_time=one_time,
677+
).model_dump(exclude_none=True, by_alias=True)
678+
),
654679
headers={"content-type": "application/json"},
655680
)
656681
return SandboxPresignedUrl(**payload)
@@ -665,11 +690,13 @@ async def download_url(
665690
payload = await self._transport.request_json(
666691
"/sandbox/files/presign-download",
667692
method="POST",
668-
json_body=SandboxPresignFileParams(
669-
path=path,
670-
expires_in_seconds=expires_in_seconds,
671-
one_time=one_time,
672-
).model_dump(exclude_none=True, by_alias=True),
693+
json_body=self._with_run_as_body(
694+
SandboxPresignFileParams(
695+
path=path,
696+
expires_in_seconds=expires_in_seconds,
697+
one_time=one_time,
698+
).model_dump(exclude_none=True, by_alias=True)
699+
),
673700
headers={"content-type": "application/json"},
674701
)
675702
return SandboxPresignedUrl(**payload)
@@ -685,12 +712,14 @@ async def _read_wire(
685712
payload = await self._transport.request_json(
686713
"/sandbox/files/read",
687714
method="POST",
688-
json_body={
689-
"path": path,
690-
"offset": offset,
691-
"length": length,
692-
"encoding": encoding,
693-
},
715+
json_body=self._with_run_as_body(
716+
{
717+
"path": path,
718+
"offset": offset,
719+
"length": length,
720+
"encoding": encoding,
721+
}
722+
),
694723
headers={"content-type": "application/json"},
695724
)
696725
return SandboxFileReadResult(**payload)
@@ -707,13 +736,31 @@ async def _write_single(
707736
payload = await self._transport.request_json(
708737
"/sandbox/files/write",
709738
method="POST",
710-
json_body={
711-
"path": path,
712-
"data": data,
713-
"append": append,
714-
"mode": mode,
715-
"encoding": encoding,
716-
},
739+
json_body=self._with_run_as_body(
740+
{
741+
"path": path,
742+
"data": data,
743+
"append": append,
744+
"mode": mode,
745+
"encoding": encoding,
746+
}
747+
),
717748
headers={"content-type": "application/json"},
718749
)
719750
return _normalize_write_info(payload["files"][0])
751+
752+
def _with_run_as_params(
753+
self, params: Dict[str, Union[str, int, bool, None]]
754+
) -> Dict[str, Union[str, int, bool, None]]:
755+
if not self._default_run_as:
756+
return params
757+
enriched = dict(params)
758+
enriched["runAs"] = self._default_run_as
759+
return enriched
760+
761+
def _with_run_as_body(self, body: Dict[str, Any]) -> Dict[str, Any]:
762+
if not self._default_run_as:
763+
return body
764+
enriched = dict(body)
765+
enriched["runAs"] = self._default_run_as
766+
return enriched

0 commit comments

Comments
 (0)