Saltar a contenido

Novedades de 0.10.0

Publicada el 2026-10-08: SDK Python rayito 0.10.0, SDK TypeScript rayito 0.10.0 y rayd 0.10.0.

0.10.0 trae tres cosas para quien usa el agente en el sandbox (sbx.agent):

  1. Más proveedores para el modelo. Hasta 0.9 el agente hablaba con Bedrock, Anthropic o una API compatible con OpenAI que configurabas a mano. Ahora hay un preset listo para OpenAI, Gemini, Azure OpenAI, OpenRouter, Groq, Mistral, DeepSeek, xAI y tu propio proxy de LiteLLM.
  2. El timeout y abort() paran de verdad. Antes, un proceso que el agente lanzaba con su herramienta de shell (un sleep, un servidor) podía sobrevivir al timeout. Ahora rayd para todo lo que lanzó la ejecución.
  3. deepagents funciona fuera de Bedrock y cuenta sus tokens.

No hay cambios que rompan la API. Para el punto 2 y para deepagents hay que reconstruir la imagen: ver Cómo actualizar desde 0.9.x.

De un vistazo

Cambio Python TypeScript Necesita imagen nueva
Nueve presets de proveedor y tres valores nuevos de AgentModel.provider openai_gateway()… litellm_gateway(); "openai", "google", "azure" openaiGateway()… litellmGateway(); los mismos provider sí, la del agente (AgentTemplate)
El timeout y abort() del agente paran todo su árbol de procesos sbx.agent, commands.run(kill_tree=) sbx.agent, commands.run({ killTree }) sí (rayd 0.10.0)
deepagents con modelos que no son de Bedrock (faltaba socksio) runtime deepagents runtime deepagents sí, la del agente
deepagents cuenta los tokens de las APIs compatibles con OpenAI result.usage, AgentLimits.max_total_tokens result.usage, AgentLimits.maxTotalTokens sí, la del agente
rayito doctor conoce la serie 0.10 CLI — —

Más proveedores para el modelo del agente

El agente nunca ve la clave del modelo. La clave vive en Secrets Manager y la pasarela de secretos de rayd la pone en la cabecera de cada petición. Un preset es esa pasarela ya configurada para un proveedor: sabe a qué dirección llamar, en qué cabecera va la clave y qué rutas se permiten.

¿Cuál elijo?

Tu situación Elige Por qué
Ya trabajas en AWS y quieres una sola factura Bedrock (bedrock_gateway) la pasarela limita también qué modelos se pueden llamar; es la ruta más probada
Ya tienes clave de OpenAI OpenAI (openai_gateway) Responses API nativa en OpenCode y deepagents
Tu empresa usa Azure Azure OpenAI (azure_openai_gateway) el modelo es un despliegue de tu recurso, con su propia cuota
Quieres Gemini Gemini (gemini_gateway) como en Bedrock, la pasarela limita los modelos
Quieres probar muchos modelos con una sola clave OpenRouter (openrouter_gateway) un catálogo grande detrás de una API
Quieres velocidad o precio bajo Groq, DeepSeek o Mistral APIs compatibles con OpenAI
Ya tienes Grok xAI (xai_gateway) habla la misma Responses API que OpenAI
Ya tienes un proxy de LiteLLM, o quieres modelos locales LiteLLM (litellm_gateway) claves virtuales con presupuesto y cualquier modelo que el proxy sirva

Si dudas, empieza por Bedrock o por LiteLLM delante de Bedrock: son las rutas que se han probado con un modelo real (ver qué se probó de verdad).

Configurarlo en tres pasos

  1. Guarda la clave en Secrets Manager, una sola vez, desde tu máquina o tu backend. Para las APIs al estilo de OpenAI el valor es la cabecera completa: Bearer <clave>. Para Gemini y Azure, la clave tal cual.
  2. Pasa el preset en gateways= al crear el sandbox. El nombre que le des (aquí "openai") es el que usará el modelo.
  3. Apunta el AgentModel a esa pasarela con el provider del proveedor y su id de modelo, y ejecuta.
from rayito import AgentModel, AgentSpec, Sandbox, SecretStore, openai_gateway

# 1. La clave, una sola vez
SecretStore().create("openai-key", "Bearer <clave de API de OpenAI>")

# 2. El preset, con el nombre "openai"
gateways = {"openai": openai_gateway("openai-key", rate_per_minute=60)}

# 3. El modelo apunta a esa pasarela
spec = AgentSpec(model=AgentModel(provider="openai", id="gpt-5-mini", gateway="openai"))

with Sandbox.create(template="rayito-agent", allow_internet_access=False, gateways=gateways) as sbx:
    print(sbx.agent.run("Resume el README.", spec=spec).text)
from rayito import AgentModel, AgentSpec, AsyncSandbox, openai_gateway


async def main() -> None:
    gateways = {"openai": openai_gateway("openai-key", rate_per_minute=60)}
    spec = AgentSpec(model=AgentModel(provider="openai", id="gpt-5-mini", gateway="openai"))
    async with await AsyncSandbox.create(
        template="rayito-agent", allow_internet_access=False, gateways=gateways
    ) as sbx:
        result = await sbx.agent.run("Resume el README.", spec=spec)
        print(result.text)
import { AgentModel, AgentSpec, Sandbox, SecretStore, openaiGateway } from "rayito";

// 1. La clave, una sola vez
await new SecretStore().create("openai-key", "Bearer <clave de API de OpenAI>");

// 2. El preset, con el nombre "openai"
const gateways = { openai: openaiGateway("openai-key", { ratePerMinute: 60 }) };

// 3. El modelo apunta a esa pasarela
const spec = new AgentSpec({
  model: new AgentModel({ provider: "openai", id: "gpt-5-mini", gateway: "openai" }),
});

await using sbx = await Sandbox.create({ template: "rayito-agent", allowInternetAccess: false, gateways });
console.log((await sbx.agent.run("Resume el README.", { spec })).text);

Para otro proveedor sólo cambian el preset y el AgentModel:

Proveedor Valor del secreto Preset (Python) AgentModel
OpenAI Bearer sk-… openai_gateway(secret) provider="openai"
Gemini la clave tal cual gemini_gateway(secret, models=[…]) provider="google"
Azure OpenAI la clave tal cual azure_openai_gateway(secret, resource="mi-recurso") provider="azure", id = nombre del despliegue
OpenRouter Bearer … openrouter_gateway(secret) provider="openai-compatible", base_path="/api/v1"
Groq Bearer … groq_gateway(secret) provider="openai-compatible", base_path="/openai/v1"
Mistral Bearer … mistral_gateway(secret) provider="openai-compatible", base_path="/v1"
DeepSeek Bearer … deepseek_gateway(secret) provider="openai-compatible", sin base_path
xAI Bearer xai-… xai_gateway(secret) provider="openai"
LiteLLM Bearer <clave virtual> litellm_gateway(secret, upstream="https://…") provider="openai-compatible", base_path="/v1"

En TypeScript los presets se llaman igual en camelCase (openaiGateway, geminiGateway, azureOpenaiGateway, openrouterGateway, groqGateway, mistralGateway, deepseekGateway, xaiGateway, litellmGateway) y las opciones van en un objeto ({ models }, { resource }, { upstream }). Cada proveedor, con su ejemplo completo en los dos lenguajes y el enlace a su consola, está en Proveedores del agente.

Fija el tope de gasto en el proveedor

En Bedrock y Gemini el modelo va en la ruta y la pasarela sólo deja pasar los modelos que listes. En las APIs al estilo de OpenAI (OpenAI, Azure OpenAI, xAI, OpenRouter, Groq, Mistral, DeepSeek, LiteLLM) el modelo va en el cuerpo, que la pasarela no lee: cualquier código del sandbox puede pedir cualquier modelo que permita la clave. Pon el tope en el proveedor (presupuesto del proyecto, límite de crédito de la clave, presupuesto de la clave virtual de LiteLLM) y usa rate_per_minute / ratePerMinute. Detalle en Límites del lado del proveedor.

Tu propio proxy de LiteLLM

Si ya tienes un proxy de LiteLLM, el agente puede usar cualquier modelo que sirva (de cualquier proveedor, o uno local como vLLM u Ollama) con una sola clave virtual. Tres condiciones:

  • El proxy escucha en HTTPS y la VPC del sandbox lo alcanza. El localhost de tu portátil no vale: el preset rechaza http://, localhost, loopback, 0.0.0.0 y enlace local.
  • La clave virtual tiene presupuesto (max_budget) y lista de modelos. Es tu único tope real de gasto.
  • El base_path del AgentModel es el mismo que el del preset (/v1 por defecto).
from rayito import AgentModel, SecretStore, litellm_gateway

SecretStore().create("litellm-key", "Bearer <clave virtual de LiteLLM>")
gateway = litellm_gateway("litellm-key", upstream="https://llm.example.com")
model = AgentModel(provider="openai-compatible", id="<modelo del proxy>", gateway="litellm", base_path="/v1")
import { AgentModel, SecretStore, litellmGateway } from "rayito";

await new SecretStore().create("litellm-key", "Bearer <clave virtual de LiteLLM>");
const gateway = litellmGateway("litellm-key", { upstream: "https://llm.example.com" });
const model = new AgentModel({ provider: "openai-compatible", id: "<modelo del proxy>", gateway: "litellm", basePath: "/v1" });

Qué se probó de verdad

Ruta Cómo se probó
Bedrock (bedrock_gateway), OpenCode y deepagents con modelo real, en AWS real (MicroVMs) en la aceptación de esta release, desde Python y TypeScript
API compatible con OpenAI de Bedrock (bedrock-runtime /openai/v1 y bedrock-mantle) por openai_compatible_gateway con modelo real, en el entorno local, con el egress cerrado: herramientas, sesión y uso de tokens
provider="openai" (Responses API) contra bedrock-mantle con modelo real, en local, una vuelta sin herramientas (bedrock-mantle se aparta de la API de OpenAI con herramientas)
LiteLLM (litellm_gateway) delante de Bedrock con modelo real, en local: herramientas, sesión y uso de tokens
OpenAI, Gemini, Azure OpenAI, OpenRouter, Groq, Mistral, DeepSeek y xAI sólo contra un upstream falso en local: los nueve presets × OpenCode y deepagents × Python y TypeScript completan una vuelta, la clave llega en su cabecera, el marcador nunca sale y una ruta fuera de la lista recibe 403

Esas ocho rutas no se han probado con una clave real del proveedor. Si tienes una, la prueba de humo (make local-providers-smoke) hace una vuelta corta contra la API real por menos de 0,01 USD por proveedor.

Proveedores que no se admiten, y por qué

Rayito sólo admite claves de API. Las suscripciones de consumo se revisaron y no se admiten:

Opción Por qué
Plan de ChatGPT (OAuth de OpenCode) usa el client_id de otra aplicación y endpoints que OpenAI pide no usar (models-and-inference); además pondría el refresh token al alcance del agente
Plan de ChatGPT con "Sign in with ChatGPT" aplazada: para una app alojada en remoto OpenAI pide un formulario de interés (overview)
Claude Pro/Max los términos de consumo de Anthropic no permiten que terceros enruten peticiones con esas credenciales; usa una clave de Anthropic o Bedrock
GitHub Copilot sin términos para flotas desatendidas (política de uso aceptable)
SuperGrok sin términos para automatización; usa la clave de API de xAI

La explicación completa está en Proveedores no admitidos.

El timeout y abort() del agente paran todo lo que lanzó

OpenCode y deepagents corren cada orden de su herramienta de shell en una sesión propia (setsid). Hasta 0.9, el timeout sólo mataba el proceso del runtime: si el agente había lanzado sleep 600 o un servidor, ese proceso seguía vivo, y el siguiente run() encontraba el agente ocupado (reason="busy").

Desde 0.10.0, con una imagen de rayd 0.10.0, esto queda garantizado:

  • Al vencer el timeout (timeout_seconds / timeoutMs), rayd pide a todo el árbol que pare (SIGTERM) y, si a los 5 s queda algo, lo congela y lo mata. El stream acaba con reason="timeout" sólo cuando no queda nada vivo.
  • abort(), un AbortSignal y los límites del SDK (max_steps, max_total_tokens) matan el árbol entero sin esperar.
  • Nada más se toca: ni otros comandos, ni la terminal, ni el kernel de run_code. Y si la ejecución termina por sí sola, lo que dejó corriendo a propósito (un servidor) sigue vivo.

No tienes que cambiar tu código:

from rayito import AgentLimits, AgentSpec, Sandbox


def run_with_deadline(sbx: Sandbox, spec: AgentSpec) -> None:
    result = sbx.agent.run(
        "Arranca el servidor de desarrollo y prueba la API.",
        spec=spec,
        limits=AgentLimits(timeout_seconds=120),
    )
    print(result.text)
import { AgentLimits, type AgentSpec, type Sandbox } from "rayito";

export async function runWithDeadline(sbx: Sandbox, spec: AgentSpec): Promise<void> {
  const controller = new AbortController();
  setTimeout(() => controller.abort(), 60_000);
  const result = await sbx.agent.run("Arranca el servidor de desarrollo y prueba la API.", {
    spec,
    limits: new AgentLimits({ timeoutMs: 120_000 }),
    signal: controller.signal,
  });
  console.log(result.text);
}

La misma garantía está disponible para cualquier comando con commands.run(..., kill_tree=True) / commands.run(cmd, { killTree: true }) (Comandos). Con una imagen anterior el campo se ignora y el timeout sólo alcanza al grupo del runtime. Detalle en Qué garantizan el timeout y abort().

Correcciones de deepagents

  • Funciona con los modelos que no son de Bedrock. rayd exporta ALL_PROXY=socks5h://… en el sandbox, y el cliente HTTP de deepagents fallaba al arrancar (AgentFailed con ImportError) con OpenAI, xAI, Azure OpenAI, Gemini, Anthropic y las APIs compatibles con OpenAI. La imagen del agente lleva ahora socksio.
  • Cuenta los tokens de las APIs compatibles con OpenAI. Con provider="openai-compatible", cada paso llegaba con 0 tokens: result.usage quedaba a cero y max_total_tokens / maxTotalTokens no cortaba nunca. Ahora se pide el uso en streaming, como ya hacía OpenCode.

Las dos necesitan reconstruir la imagen del agente con AgentTemplate de 0.10.0.

Otros cambios

  • rayito doctor conoce la serie 0.10: exige el rayd del tag rayd-v0.10.0 para el SDK 0.10 (Compatibilidad).
  • rayd 0.10.0: StartRequest.kill_tree (nuevo, false por defecto) hace al proceso child subreaper y lleva el timeout y las señales a todo su árbol.

Cómo actualizar desde 0.9.x

  1. Actualiza el SDK.

    pip install -U "rayito[cli]"
    
    pnpm add rayito@latest
    
  2. Republica tu imagen sobre rayd 0.10.0 (Imágenes). rayito doctor exige el rayd del tag rayd-v0.10.0 para el SDK 0.10 y da FAIL en la comprobación compatibility con una imagen 0.9.

  3. Si usas el agente, reconstruye su imagen con AgentTemplate o rayito agent template build: la nueva lleva rayd 0.10.0, socksio y el runner de deepagents corregido.

No hay cambios incompatibles en la API pública de 0.9.x.