Agent providers¶
Since 0.10.0 (What's new in 0.10.0).
The sbx.agent agent calls its model only
through the secrets gateway:
the key lives in Secrets Manager, rayd injects it into the header and the
runtime (OpenCode or deepagents) only sees a marker. Besides Bedrock,
Anthropic and openai_compatible_gateway, there is one preset per provider
with a static API key. Each preset fixes the upstream, the header and the
allowed paths, and is combined with an AgentModel.
Everything is configured in three steps: you store the key in Secrets
Manager, pass the preset in gateways= when creating the sandbox and point
the AgentModel at that gateway (example).
Which one do I choose?¶
| Your situation | Choose |
|---|---|
| You already work on AWS and want a single bill | Bedrock (bedrock_gateway): the gateway also limits which models are called, and it is the most tested path |
| You have an OpenAI key | OpenAI |
| Your organization uses Azure | Azure OpenAI: the model is a deployment of your resource, with its quota |
| You want Gemini | Gemini: as with Bedrock, the gateway limits the models |
| You want to try many models with a single key | OpenRouter |
| You look for speed or a low price | Groq, DeepSeek or Mistral |
| You have an xAI (Grok) key | xAI |
| You already have a LiteLLM proxy, or want local models | LiteLLM: virtual keys with a budget and any model the proxy serves |
| You have a consumer subscription (ChatGPT, Claude Pro/Max, Copilot, SuperGrok) | not supported: Unsupported providers |
If in doubt, start with Bedrock or with LiteLLM in front of Bedrock: they are the paths tested with a real model (What has been tested).
Catalog¶
| Provider | Preset (Python / TypeScript) | Upstream | Header (secret value) | Allowed paths | AgentModel |
|---|---|---|---|---|---|
| OpenAI | openai_gateway(secret) / openaiGateway(secret) |
https://api.openai.com |
authorization (Bearer sk-…) |
POST /v1/responses, POST /v1/chat/completions |
provider="openai" |
| Gemini (AI Studio) | gemini_gateway(secret, models=[…]) / geminiGateway(secret, { models }) |
https://generativelanguage.googleapis.com |
x-goog-api-key (the key as is) |
per model: POST /v1beta/models/<m>:streamGenerateContent and :generateContent |
provider="google" |
| Azure OpenAI v1 | azure_openai_gateway(secret, resource="…") / azureOpenaiGateway(secret, { resource }) |
https://<resource>.openai.azure.com |
api-key (the key as is) |
POST /openai/v1/responses, POST /openai/v1/chat/completions |
provider="azure" |
| OpenRouter | openrouter_gateway(secret) / openrouterGateway(secret) |
https://openrouter.ai |
authorization (Bearer …) |
POST /api/v1/chat/completions |
provider="openai-compatible", base_path="/api/v1" |
| Groq | groq_gateway(secret) / groqGateway(secret) |
https://api.groq.com |
authorization (Bearer …) |
POST /openai/v1/chat/completions |
provider="openai-compatible", base_path="/openai/v1" |
| Mistral | mistral_gateway(secret) / mistralGateway(secret) |
https://api.mistral.ai |
authorization (Bearer …) |
POST /v1/chat/completions |
provider="openai-compatible", base_path="/v1" |
| DeepSeek | deepseek_gateway(secret) / deepseekGateway(secret) |
https://api.deepseek.com |
authorization (Bearer …) |
POST /chat/completions |
provider="openai-compatible" without base_path |
| xAI | xai_gateway(secret) / xaiGateway(secret) |
https://api.x.ai |
authorization (Bearer xai-…) |
POST /v1/responses, POST /v1/chat/completions |
provider="openai" |
| LiteLLM (your own proxy) | litellm_gateway(secret, upstream="https://…", base_path="/v1") / litellmGateway(secret, { upstream, basePath }) |
the upstream you pass (https://host) |
authorization (Bearer <virtual key>) |
POST <base_path>/responses, POST <base_path>/chat/completions |
provider="openai-compatible", base_path equal to the preset's |
All of them accept rate_per_minute / ratePerMinute. The exact values are
in testdata/agent/provider-catalogue.json, the file that both SDKs' tests
check.
Notes per provider:
- Gemini.
modelsis required and takes the id without themodels/prefix (gemini-2.5-flash). Include the agent'ssmall_modeltoo: OpenCode uses it for titles and the gateway rejects any model that is not in the list. - Azure OpenAI.
resourceis the resource subdomain (1-63 characters[a-z0-9-], no hyphen at the start or the end). TheAgentModel'sidis the deployment name. Both runtimes use the Responses API (/openai/v1/responses) with the key inapi-key: OpenCode with@ai-sdk/azureand deepagents withAzureChatOpenAI. - xAI uses the
openaiprovider: OpenCode and deepagents talk to it with the Responses API as they do to OpenAI, against another upstream. - LiteLLM. The proxy has to be behind HTTPS and reachable from the
sandbox's VPC. A
localhoston your laptop is not: the preset rejectslocalhost, loopback (127.0.0.0/8),0.0.0.0and link-local (169.254.0.0/16). - vLLM, LM Studio, Ollama, Together, Fireworks… still go through
openai_compatible_gateway, with the same conditions as LiteLLM.
Example¶
The secret holds the full value of the header (Bearer … for OpenAI-style
APIs). With OpenAI:
from rayito import AgentModel, AgentSpec, Sandbox, SecretStore, openai_gateway
SecretStore().create("openai-key", "Bearer <OpenAI API key>")
spec = AgentSpec(model=AgentModel(provider="openai", id="gpt-5-mini", gateway="openai"))
with Sandbox.create(
template="rayito-agent",
allow_internet_access=False,
gateways={"openai": openai_gateway("openai-key", rate_per_minute=60)},
) as sbx:
result = sbx.agent.run("Summarize the README.", spec=spec)
print(result.text)
from rayito import AgentModel, AgentSpec, AsyncSandbox, gemini_gateway
MODELS = ["gemini-2.5-flash", "gemini-2.5-flash-lite"]
async def main() -> None:
spec = AgentSpec(
model=AgentModel(provider="google", id=MODELS[0], gateway="gemini"),
small_model=MODELS[1],
)
async with await AsyncSandbox.create(
template="rayito-agent",
allow_internet_access=False,
gateways={"gemini": gemini_gateway("gemini-key", models=MODELS)},
) as sbx:
result = await sbx.agent.run("Summarize the README.", spec=spec)
print(result.text)
import { AgentModel, AgentSpec, Sandbox, openrouterGateway } from "rayito";
const spec = new AgentSpec({
model: new AgentModel({
provider: "openai-compatible",
id: "anthropic/claude-haiku-4.5",
gateway: "openrouter",
basePath: "/api/v1",
}),
});
await using sbx = await Sandbox.create({
template: "rayito-agent",
allowInternetAccess: false,
gateways: { openrouter: openrouterGateway("openrouter-key", { ratePerMinute: 60 }) },
});
const result = await sbx.agent.run("Summarize the README.", { spec });
console.log(result.text);
Cost and activation
- Activates: passing the preset in
gateways=/gatewaysofSandbox.createand using it from anAgentModel. Building a preset does not call AWS or load any optional peer. - AWS resources and calls: those of the secrets gateway: one Secrets
Manager read (
GetSecretValue) per header and cache TTL when the gateway is applied. No new resources. - Approximate cost: $0.05 per 10,000 reads plus $0.40 per month per secret (us-east-1, checked on 2026-10-07, Secrets Manager pricing). The model dominates it, which the provider bills per token.
- IAM:
secretsmanager:GetSecretValueon the secret (RayitoSecretsReader). - How to turn it off: do not pass the gateway or call
sbx.agent.
Configure each provider¶
Each section creates the secret (once, from your machine or your backend), builds the preset and the AgentModel. Both are passed as in the example: the preset in gateways= and the model in AgentSpec. The key you use in gateways= (Python) or gateways (TypeScript) is the name AgentModel.gateway points to: for example gateways={"openai": gateway} with AgentModel(gateway="openai"); if they do not match, sbx.agent.run() fails with InvalidArgumentException. The model ids are examples: use the ones your account offers.
OpenAI¶
Create the key in a project of your own on the OpenAI platform and set the monthly budget and the allowed models there. Reference: platform.openai.com.
Gemini (AI Studio)¶
The secret holds the key as is, without Bearer. It is the only non-Bedrock preset that limits the model at the gateway: put the small_model in models too. Also set the quota and billing of the Google project. Reference: ai.google.dev.
import { AgentModel, SecretStore, geminiGateway } from "rayito";
await new SecretStore().create("gemini-key", "<Gemini API key>");
const gateway = geminiGateway("gemini-key", { models: ["gemini-2.5-flash", "gemini-2.5-flash-lite"] });
const model = new AgentModel({ provider: "google", id: "gemini-2.5-flash", gateway: "gemini" });
Azure OpenAI¶
The secret holds the key as is (api-key header). The id is the deployment name. Create only the deployments you need and limit their quota (TPM). It works with OpenCode and with deepagents. Reference: learn.microsoft.com.
import { AgentModel, SecretStore, azureOpenaiGateway } from "rayito";
await new SecretStore().create("azure-openai-key", "<Azure OpenAI resource key>");
const gateway = azureOpenaiGateway("azure-openai-key", { resource: "my-resource" });
const model = new AgentModel({ provider: "azure", id: "my-deployment", gateway: "azure" });
OpenRouter¶
Put a credit limit on the key: OpenRouter's documentation warns that a runaway agent can spend the whole balance. Reference: openrouter.ai.
from rayito import AgentModel, SecretStore, openrouter_gateway
SecretStore().create("openrouter-key", "Bearer <OpenRouter key>")
gateway = openrouter_gateway("openrouter-key")
model = AgentModel(provider="openai-compatible", id="anthropic/claude-haiku-4.5", gateway="openrouter", base_path="/api/v1")
import { AgentModel, SecretStore, openrouterGateway } from "rayito";
await new SecretStore().create("openrouter-key", "Bearer <OpenRouter key>");
const gateway = openrouterGateway("openrouter-key");
const model = new AgentModel({ provider: "openai-compatible", id: "anthropic/claude-haiku-4.5", gateway: "openrouter", basePath: "/api/v1" });
Groq¶
Review the organization's usage limits in the Groq console. Reference: console.groq.com.
Mistral¶
Set the workspace spending limits in the Mistral console. Reference: docs.mistral.ai.
import { AgentModel, SecretStore, mistralGateway } from "rayito";
await new SecretStore().create("mistral-key", "Bearer <Mistral key>");
const gateway = mistralGateway("mistral-key");
const model = new AgentModel({ provider: "openai-compatible", id: "<Mistral model>", gateway: "mistral", basePath: "/v1" });
DeepSeek¶
No base_path: the API hangs off the root. DeepSeek is prepaid: the balance you load is the cap. Reference: api-docs.deepseek.com.
xAI¶
It uses the openai provider against another upstream. Set the team's spending limits in the xAI console. Reference: docs.x.ai.
LiteLLM (your own proxy)¶
If you already have a LiteLLM proxy, the agent can use any model that proxy serves (from any provider or local) with a single virtual key. Conditions:
- The proxy listens on HTTPS and is reachable from the sandbox's VPC.
upstreamishttps://hostorhttps://host:port, with no path. The preset rejectshttp://,localhost, loopback,0.0.0.0and link-local. - The secret holds
Bearer <virtual key>. Create the virtual key with a budget (max_budget) and a list of models: it is the only real cap, because the gateway does not see the model. base_pathis/v1by default; theAgentModel's has to be the same.POST <base_path>/chat/completionsandPOST <base_path>/responsesare allowed.
from rayito import AgentModel, SecretStore, litellm_gateway
SecretStore().create("litellm-key", "Bearer <LiteLLM virtual key>")
gateway = litellm_gateway("litellm-key", upstream="https://llm.example.com")
model = AgentModel(provider="openai-compatible", id="<proxy model>", gateway="litellm", base_path="/v1")
import { AgentModel, SecretStore, litellmGateway } from "rayito";
await new SecretStore().create("litellm-key", "Bearer <LiteLLM virtual key>");
const gateway = litellmGateway("litellm-key", { upstream: "https://llm.example.com" });
const model = new AgentModel({ provider: "openai-compatible", id: "<proxy model>", gateway: "litellm", basePath: "/v1" });
Local models¶
vLLM, LM Studio, Ollama and the like work if you publish them behind
HTTPS at an address the sandbox's VPC can reach (an internal load
balancer, for example). You can point at them directly with
openai_compatible_gateway(secret, upstream=, base_path=) /
openaiCompatibleGateway(secret, { upstream, basePath }), or put them
behind LiteLLM to get virtual keys and budgets. The localhost on your
laptop is never reachable from the microVM.
Model chosen by your code¶
With a gateway that does not filter by model (all of them except bedrock and
gemini) and a runtime with an entrypoint (or CustomRuntime), the model id
is optional: your code reads it from its own configuration. Name the variables
it reads and Rayito points them at the gateway, with the placeholder as key:
Without an id, ctx.model is None (undefined in Node) and LLM_MODEL is
not set. A runtime that builds the model (opencode, hermes, deepagents or
strands without an entrypoint) still needs the id. More in
Deploy your agent.
OpenCode without a project file¶
Since 0.17.0.
An OpenCode project that only has opencode.json (or opencode.jsonc) and
AGENTS.md deploys without rayito-agent.toml. Detection fills the [agent]
table with two things:
- The root
AGENTS.mdbecomesagent.instructions_file. The served agent reads it as itsAGENTS.md. An emptyAGENTS.mdis ignored. - The
permissionblock becomesagent.permissions."*"is thedefault, each tool getsallowordeny, andbashaccepts an object of patterns.opencode.jsoncis read without comments or trailing commas.
{
"model": "amazon-bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0",
"permission": {
"edit": "deny",
"bash": { "*": "ask", "git status": "allow" }
}
}
The run is unattended and nobody answers a question, so ask is applied as
deny. A key that is not a tool, or a value that is not allow, ask or
deny, is ignored. Both cases give the warning opencode_permission_ignored.
In the example, the agent can only run git status.
The rayito agent detect report lists the evidence (file:AGENTS.md and
config:permission), never the text of the files. With --write,
rayito-agent.json stores the resolved instructions and permissions.
What rayito-agent.toml says wins, key by key. [agent] instructions or
instructions_file win over AGENTS.md, and [agent.permissions] wins over
the permission block. If the file declares another [runtime] kind, nothing
is applied.
Provider-side limits¶
Only Bedrock and Gemini carry the model in the path, so the gateway's
allowlist limits it. With Anthropic (anthropic_gateway, only
POST /v1/messages) and with OpenAI-style APIs (OpenAI, Azure OpenAI, xAI,
OpenRouter, Groq, Mistral, DeepSeek, LiteLLM) the model goes in the
body, which the gateway forwards without reading: the code in the
sandbox can ask for any model the key allows, outside the SDK's token
budget (SECURITY.md T29).
Set the cap at the provider:
- OpenAI: a project of its own for the key, with a monthly budget and allowed models.
- OpenRouter: a credit limit on the key. Its own documentation warns that a runaway agent can spend the whole balance.
- Azure OpenAI: only the deployments you need and their quota (TPM).
- Anthropic: a workspace of its own for the key, with a spending limit.
- LiteLLM: budget and allowed models per virtual key.
- Groq, Mistral, DeepSeek, xAI: spending or usage limits on the account or the team.
rate_per_minute also helps: it limits the requests that go through the
gateway, whether the agent or any other code in the sandbox makes them.
What has been tested¶
| Path | How it was tested |
|---|---|
Bedrock (bedrock_gateway), OpenCode and deepagents |
with a real model, on real AWS (MicroVMs), from Python and TypeScript, in each release's acceptance |
Bedrock's OpenAI-compatible API (bedrock-runtime /openai/v1 and bedrock-mantle) through openai_compatible_gateway |
with a real model, in the local environment with egress closed: tools, session and token usage |
provider="openai" (Responses API) against bedrock-mantle |
with a real model, locally, one turn without tools |
LiteLLM (litellm_gateway) in front of Bedrock |
with a real model, locally: tools, session and token usage |
| OpenAI, Gemini, Azure OpenAI, OpenRouter, Groq, Mistral, DeepSeek and xAI | only against a fake upstream: each preset with OpenCode and deepagents, from Python and TypeScript, completes a turn, the key arrives in its header, the marker never leaves and a path outside the list receives 403 |
The last eight have not been tested with a real provider key. With a key of your own, the smoke test does a short turn against the real API for less than 0.01 USD per provider.
Unsupported providers¶
Rayito only supports API keys. These forms of access were reviewed (checked on 2026-10-07) and are not supported:
| Option | Decision | Why |
|---|---|---|
| ChatGPT plan with the OAuth that ships with OpenCode | Rejected | It uses another application's client_id and ChatGPT's backend-api. OpenAI expressly asks not to point at those endpoints (models-and-inference). It also requires the refresh token inside the sandbox, where the agent could read it. |
| ChatGPT plan with "Sign in with ChatGPT" | Deferred | OpenAI documents it for open-source and locally hosted apps; for a remotely hosted app it asks you to fill in an interest form (overview). It is pending OpenAI confirming this case. For programmatic use, OpenAI recommends an API key (Codex auth). |
| Claude Pro/Max | Rejected | Anthropic's consumer terms and Claude Code's legal and compliance page do not allow third parties to route requests with consumer-plan credentials. OpenCode removed that access (provider docs). Use an Anthropic or Bedrock API key. |
| GitHub Copilot | Rejected | GitHub supports Copilot in OpenCode for interactive use (changelog), but there are no terms for unattended fleets (its acceptable use policy limits mass automated activity), and it would require reusing OpenCode's OAuth. |
| SuperGrok | Rejected | xAI offers it in OpenCode (announcement), but there are no published terms for automation. Use the xAI API key. |
Nor can they sneak in some other way: provider, enabled_providers and
plugin are reserved keys of raw_config (so a Copilot, Codex or Gemini
OAuth plugin is not loaded), no SDK type has a credential field, and the
OpenCode adapter never writes an auth.json or the auth or plugin
keys.