Comandos¶
sbx.commands ejecuta procesos dentro del sandbox con /bin/bash -l -c
(un shell de login), como el usuario user (uid 1000): en primer plano (esperas el resultado) o en
segundo plano (recibes un handle y sigues).
Cuándo usarlo¶
- Instalar paquetes, ejecutar scripts, compilar o lanzar tests.
- Arrancar un servidor en segundo plano y llegar a él con
get_host(). - Cuándo no: para código Python con estado entre llamadas, usa
run_code; para una sesión interactiva, una terminal (PTY).
Ejemplo rápido¶
from rayito import CommandExitException, Sandbox
with Sandbox.create() as sbx:
result = sbx.commands.run("echo hola")
print(result.stdout, result.exit_code) # "hola\n" 0
server = sbx.commands.run("python3 -m http.server 3000", background=True, timeout=None)
print(server.pid)
server.kill()
try:
sbx.commands.run("exit 3")
except CommandExitException as error: # (1)!
print(error.exit_code) # 3
- Un código de salida distinto de cero es una excepción, como en E2B.
import asyncio
from rayito import AsyncSandbox, CommandExitException
async def main() -> None:
async with await AsyncSandbox.create() as sbx:
result = await sbx.commands.run("echo hola")
print(result.stdout, result.exit_code) # "hola\n" 0
server = await sbx.commands.run("python3 -m http.server 3000", background=True, timeout=None)
print(server.pid)
await server.kill()
try:
await sbx.commands.run("exit 3")
except CommandExitException as error:
print(error.exit_code) # 3
asyncio.run(main())
import { CommandExitError, Sandbox } from "rayito";
await using sbx = await Sandbox.create();
const result = await sbx.commands.run("echo hola");
console.log(result.stdout, result.exitCode); // "hola\n" 0
const server = await sbx.commands.run("python3 -m http.server 3000", {
background: true,
timeoutMs: 0, // (1)!
});
console.log(server.pid);
await server.kill();
try {
await sbx.commands.run("exit 3");
} catch (error) {
if (error instanceof CommandExitError) console.log(error.exitCode); // 3
}
0es "sin límite" en TypeScript; en Python,timeout=None.
Paso a paso¶
Primer plano¶
commands.run(cmd) espera a que el proceso termine y devuelve un
CommandResult con stdout, stderr, exit_code y truncated. El
timeout (60 s por defecto) lo impone el agente dentro del sandbox: al vencer
manda SIGTERM al grupo del proceso, SIGKILL 5 s después, y lanza
TimeoutException. Una pausa no consume ese plazo.
Lo que el comando saque de su grupo (setsid, nohup ... & con un doble
fork, un servidor que se demoniza) no recibe esas señales. Con
kill_tree=True (TypeScript: killTree: true) sí: el agente hace al proceso
child subreaper de sus descendientes, de modo que siguen colgando de él
aunque su padre salga; el SIGTERM del timeout llega a todo el árbol y, tras
los 5 s de gracia, el agente congela (SIGSTOP) y mata (SIGKILL) a los que
queden. El fin del comando no llega hasta que no queda ninguno (si todos
salen antes, la gracia acaba antes). kill() hace lo mismo sin gracia: congela
el árbol, lo mata y después mata el grupo. Lo que un comando deja vivo al
terminar por sí mismo no se toca, con o sin kill_tree; nunca se señala un
proceso ajeno al árbol.
Para ver la salida mientras llega, pasa callbacks:
Segundo plano, stdin y reconexión¶
Con background=True recibes un CommandHandle en cuanto el proceso
arranca. Con él puedes esperar (wait()), matar (kill()), escribir en su
entrada (send_stdin(), con stdin=True al lanzarlo) o soltarlo
(disconnect()): el proceso sigue vivo y otro proceso puede volver a
engancharse con commands.connect(pid).
from rayito import Sandbox
with Sandbox.create() as sbx:
handle = sbx.commands.run("cat", background=True, stdin=True)
handle.send_stdin("hola\n")
handle.close_stdin() # (1)!
print(handle.wait().stdout) # "hola\n"
worker = sbx.commands.run("sleep 30; echo listo", background=True)
worker.disconnect() # (2)!
again = sbx.commands.connect(worker.pid)
print(again.wait().stdout) # "listo\n"
print([p.pid for p in sbx.commands.list()]) # los procesos vivos
- Cierra la entrada estándar:
cattermina. - Suelta el stream; el proceso sigue corriendo dentro del sandbox.
import asyncio
from rayito import AsyncSandbox
async def main() -> None:
async with await AsyncSandbox.create() as sbx:
handle = await sbx.commands.run("cat", background=True, stdin=True)
await handle.send_stdin("hola\n")
await handle.close_stdin()
print((await handle.wait()).stdout) # "hola\n"
worker = await sbx.commands.run("sleep 30; echo listo", background=True)
worker.disconnect()
again = await sbx.commands.connect(worker.pid)
print((await again.wait()).stdout) # "listo\n"
asyncio.run(main())
import { Sandbox } from "rayito";
await using sbx = await Sandbox.create();
const handle = await sbx.commands.run("cat", { background: true, stdin: true });
await handle.sendStdin("hola\n");
await handle.closeStdin();
console.log((await handle.wait()).stdout); // "hola\n"
const worker = await sbx.commands.run("sleep 30; echo listo", { background: true });
worker.disconnect();
const again = await sbx.commands.connect(worker.pid);
console.log((await again.wait()).stdout); // "listo\n"
console.log((await sbx.commands.list()).map((p) => p.pid));
También puedes iterar el handle para procesar la salida a trozos:
for stdout, stderr, _ in handle: en Python (async for en
AsyncSandbox) y for await (const chunk of handle) en TypeScript. El
handle expone además pid, stdout y stderr acumulados hasta ahora,
exit_code (None/undefined mientras corre), error, reconnects y
last_seq (TypeScript: exitCode, lastSeq).
commands.connect(pid) acepta from_seq= (reengancharse desde una
posición concreta de la salida, 0 es desde el principio de lo retenido),
on_stdout=/on_stderr=, timeout= y max_output_bytes= (TypeScript:
fromSeq, onStdout, timeoutMs, maxOutputBytes). handle.wait() acepta
también los callbacks en Python: handle.wait(on_stdout=print).
Entorno, usuario y directorio¶
envs=, user= y cwd= se aplican a un comando concreto. El entorno del
proceso se construye desde cero: sólo lleva las variables de la imagen, las
de create(envs=) y las del comando (más las de secrets=, si lo usas).
tag= pone una etiqueta libre al proceso para encontrarlo después en
commands.list() (ProcessInfo.tag).
from rayito import Sandbox
with Sandbox.create(envs={"APP_ENV": "dev"}) as sbx:
out = sbx.commands.run("echo $APP_ENV $MODE; pwd", envs={"MODE": "test"}, cwd="/tmp")
print(out.stdout) # "dev test\n/tmp\n"
user="root" sólo funciona si la imagen lo permite (RAYITO_ALLOW_ROOT=1
en la imagen); por defecto todo corre como user.
Después de una pausa o un corte¶
Si el sandbox se suspende o el proxy corta la conexión, cada handle se
reengancha solo la próxima vez que lo lees, sin perder salida
(Connect(from_seq)). La salida se retiene en el agente: hasta 64 trozos de
32 KiB por suscriptor; si nadie lee en 30 s y se llena, el comando termina
con output_truncated.
Opciones de commands.run¶
| Python | TypeScript | Por defecto | Qué hace |
|---|---|---|---|
cmd |
cmd |
— | la orden, ejecutada con /bin/bash -l -c |
background |
background |
False |
devuelve un CommandHandle sin esperar |
envs |
envs |
— | variables de entorno de este proceso |
user |
user |
"user" |
usuario que ejecuta el proceso |
cwd |
cwd |
el HOME del usuario |
directorio de trabajo |
on_stdout, on_stderr |
onStdout, onStderr |
— | callbacks con cada trozo de salida |
stdin |
stdin |
False |
deja la entrada estándar abierta para send_stdin |
timeout |
timeoutMs |
60 s / 60 000 ms | límite en el servidor; None / 0 sin límite |
request_timeout |
requestTimeoutMs |
el del sandbox (60 s) | plazo de cada llamada al agente |
tag |
tag |
— | etiqueta libre que devuelve commands.list() |
kill_tree |
killTree |
False |
el timeout y kill() alcanzan también a los descendientes que salieron del grupo (ver Primer plano) |
secrets |
secrets |
— | secretos como variables de entorno (opcional, con coste) |
max_output_bytes |
maxOutputBytes |
64 MiB | tope de la salida guardada por descriptor; ver Salida guardada |
| — | signal |
— | AbortSignal que cancela la llamada |
Salida guardada¶
El SDK guarda en memoria la salida de cada comando (la de result.stdout
y la del handle) hasta max_output_bytes/maxOutputBytes por descriptor:
64 MiB por defecto (COMMAND_OUTPUT_MAX_BYTES). Si un proceso escribe más,
se queda con el final, descarta lo más antiguo y marca el resultado con
truncated (también CommandExitException/CommandExitError). Los
callbacks reciben siempre todo. Con 0 no guarda nada: útil cuando ya
consumes la salida por callbacks. Así un comando del sandbox que escribe sin
parar no agota la memoria de tu proceso; las PTY usan el mismo tope.
Los demás métodos: commands.list() (una lista de ProcessInfo con pid,
cmd, args, envs, cwd, tag y kind, que incluye las PTY),
commands.kill(pid) (SIGKILL al grupo, y antes a todo el árbol si se
lanzó con kill_tree; False si el proceso ya no existe), commands.send_stdin(pid, data), commands.close_stdin(pid) y
commands.connect(pid) (TypeScript: sendStdin, closeStdin).
Errores y solución de problemas¶
| Python | TypeScript | Cuándo | Qué hacer |
|---|---|---|---|
CommandExitException |
CommandExitError |
el proceso salió con código distinto de cero (exit_code, stdout, stderr) |
captúrala si el fallo es esperable |
TimeoutException |
TimeoutError |
venció el timeout del comando |
sube timeout o usa background=True |
RateLimitException |
RateLimitError |
demasiados procesos y PTYs vivos en el sandbox (Límites) | mata procesos que ya no uses |
SandboxException con output_truncated |
SandboxError |
nadie leyó la salida en 30 s y se llenó el búfer | consume el handle o redirige a un fichero |
NotFoundException |
NotFoundError |
connect(pid) o send_stdin(pid) de un proceso que ya no existe (kill(pid) devuelve False) |
— |
Tabla completa: Errores.
Diferencias con E2B¶
- En TypeScript, el shim usa el mismo
Commandsnativo. En Python,rayito.e2blo envuelve con las firmas posicionales de E2B 2.x (run(cmd, background, envs, user, cwd, ...)) y devuelve los mismosCommandResult/CommandHandle;tag=,secrets=ymax_output_bytes=sólo están en el nativo,sbx.native.commands. - Un comando en segundo plano no despierta a un sandbox suspendido al leerlo: espera a que algo lo reanude.
Ver también¶
- Terminal (PTY)
- Ejecutar código
- Puertos y host
- Referencia: Python y TypeScript