This commit is contained in:
Nathan Flurry 2026-03-15 12:37:42 -07:00 committed by GitHub
parent 57a07f6a0a
commit 284fe66be4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 259 additions and 116 deletions

View file

@ -13,6 +13,7 @@
- Never mention "ACP" in user-facing docs (`docs/**/*.mdx`) except in docs that are specifically about ACP itself (e.g. `docs/acp-http-client.mdx`).
- Never expose underlying protocol method names (e.g. `session/request_permission`, `session/create`, `_sandboxagent/session/detach`) in non-ACP docs. Describe the behavior in user-facing terms instead.
- Do not describe the underlying protocol implementation in docs. Only document the SDK surface (methods, types, options). ACP protocol details belong exclusively in ACP-specific pages.
- Do not use em dashes (`—`) in docs. Use commas, periods, or parentheses instead.
### Docs Source Of Truth (HTTP/CLI)

View file

@ -1,115 +0,0 @@
---
title: "Credentials"
description: "How Sandbox Agent discovers and uses provider credentials."
---
Sandbox Agent discovers API credentials from environment variables and local agent config files.
These credentials are passed through to underlying agent runtimes.
## Credential sources
Credentials are discovered in priority order.
### Environment variables (highest priority)
API keys first:
| Variable | Provider |
|----------|----------|
| `ANTHROPIC_API_KEY` | Anthropic |
| `CLAUDE_API_KEY` | Anthropic fallback |
| `OPENAI_API_KEY` | OpenAI |
| `CODEX_API_KEY` | OpenAI fallback |
OAuth tokens (used when OAuth extraction is enabled):
| Variable | Provider |
|----------|----------|
| `CLAUDE_CODE_OAUTH_TOKEN` | Anthropic |
| `ANTHROPIC_AUTH_TOKEN` | Anthropic fallback |
### Agent config files
| Agent | Config path | Provider |
|-------|-------------|----------|
| Amp | `~/.amp/config.json` | Anthropic |
| Claude Code | `~/.claude.json`, `~/.claude/.credentials.json` | Anthropic |
| Codex | `~/.codex/auth.json` | OpenAI |
| OpenCode | `~/.local/share/opencode/auth.json` | Anthropic/OpenAI |
## Provider requirements by agent
| Agent | Required provider |
|-------|-------------------|
| Claude Code | Anthropic |
| Amp | Anthropic |
| Codex | OpenAI |
| OpenCode | Anthropic or OpenAI |
| Mock | None |
## Error handling behavior
Credential extraction is best-effort:
- Missing or malformed files are skipped.
- Discovery continues to later sources.
- Missing credentials mark providers unavailable instead of failing server startup.
When prompting, Sandbox Agent does not pre-validate provider credentials. Agent-native authentication errors surface through session events/output.
## Checking credential status
### API
`sdk.listAgents()` includes `credentialsAvailable` per agent.
```json
{
"agents": [
{
"id": "claude",
"installed": true,
"credentialsAvailable": true
},
{
"id": "codex",
"installed": true,
"credentialsAvailable": false
}
]
}
```
### TypeScript SDK
```typescript
const result = await sdk.listAgents();
for (const agent of result.agents) {
console.log(`${agent.id}: ${agent.credentialsAvailable ? "authenticated" : "no credentials"}`);
}
```
## Passing credentials explicitly
Set environment variables before starting Sandbox Agent:
```bash
export ANTHROPIC_API_KEY=sk-ant-...
export OPENAI_API_KEY=sk-...
sandbox-agent daemon start
```
Or with SDK-managed local spawn:
```typescript
import { SandboxAgent } from "sandbox-agent";
const sdk = await SandboxAgent.start({
spawn: {
env: {
ANTHROPIC_API_KEY: process.env.MY_ANTHROPIC_KEY,
},
},
});
```

View file

@ -51,6 +51,7 @@
"pages": [
"quickstart",
"sdk-overview",
"llm-credentials",
"react-components",
{
"group": "Deploy",
@ -90,7 +91,6 @@
{
"group": "More",
"pages": [
"credentials",
"daemon",
"cors",
"session-restoration",

250
docs/llm-credentials.mdx Normal file
View file

@ -0,0 +1,250 @@
---
title: "LLM Credentials"
description: "Strategies for providing LLM provider credentials to agents."
icon: "key"
---
Sandbox Agent needs LLM provider credentials (Anthropic, OpenAI, etc.) to run agent sessions.
## Configuration
Pass credentials via `spawn.env` when starting a sandbox. Each call to `SandboxAgent.start()` can use different credentials:
```typescript
import { SandboxAgent } from "sandbox-agent";
const sdk = await SandboxAgent.start({
spawn: {
env: {
ANTHROPIC_API_KEY: "sk-ant-...",
OPENAI_API_KEY: "sk-...",
},
},
});
```
Each agent requires credentials from a specific provider. Sandbox Agent checks environment variables (including those passed via `spawn.env`) and host config files:
| Agent | Provider | Environment variables | Config files |
|-------|----------|----------------------|--------------|
| Claude Code | Anthropic | `ANTHROPIC_API_KEY`, `CLAUDE_API_KEY` | `~/.claude.json`, `~/.claude/.credentials.json` |
| Amp | Anthropic | `ANTHROPIC_API_KEY`, `CLAUDE_API_KEY` | `~/.amp/config.json` |
| Codex | OpenAI | `OPENAI_API_KEY`, `CODEX_API_KEY` | `~/.codex/auth.json` |
| OpenCode | Anthropic or OpenAI | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` | `~/.local/share/opencode/auth.json` |
| Mock | None | - | - |
## Credential strategies
LLM credentials are passed into the sandbox as environment variables. The agent and everything inside the sandbox has access to the token, so it's important to choose the right strategy for how you provision and scope these credentials.
| Strategy | Who pays | Cost attribution | Best for |
|----------|----------|-----------------|----------|
| **Per-tenant gateway** (recommended) | Your organization, billed back per tenant | Per-tenant keys with budgets | Multi-tenant SaaS, usage-based billing |
| **Bring your own key** | Each user (usage-based) | Per-user by default | Dev environments, internal tools |
| **Shared API key** | Your organization | None (single bill) | Single-tenant apps, internal platforms |
| **Personal subscription** | Each user (existing subscription) | Per-user by default | Local dev, internal tools where users have Claude or Codex subscriptions |
### Per-tenant gateway (recommended)
Route LLM traffic through a gateway that mints per-tenant API keys, each with its own spend tracking and budget limits.
```mermaid
graph LR
B[Your Backend] -->|tenant key| S[Sandbox]
S -->|LLM requests| G[Gateway]
G -->|scoped key| P[LLM Provider]
```
Your backend issues a scoped key per tenant, then passes it to the sandbox. This is the typical pattern when using sandbox providers (E2B, Daytona, Docker).
```typescript expandable
import { SandboxAgent } from "sandbox-agent";
async function createTenantSandbox(tenantId: string) {
// Issue a scoped key for this tenant via OpenRouter
const res = await fetch("https://openrouter.ai/api/v1/keys", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_PROVISIONING_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: `tenant-${tenantId}`,
limit: 50,
limitResetType: "monthly",
}),
});
const { key } = await res.json();
// Start a sandbox with the tenant's scoped key
const sdk = await SandboxAgent.start({
spawn: {
env: {
OPENAI_API_KEY: key, // OpenRouter uses OpenAI-compatible endpoints
},
},
});
const session = await sdk.createSession({
agent: "claude",
sessionInit: { cwd: "/workspace" },
});
return { sdk, session };
}
```
#### Security
Recommended for multi-tenant applications. Each tenant gets a scoped key with its own budget, so exfiltration only exposes that tenant's allowance.
#### Use cases
- **Multi-tenant SaaS**: per-tenant spend tracking and budget limits
- **Production apps**: exposed to end users who need isolated credentials
- **Usage-based billing**: each tenant pays for their own consumption
#### Choosing a gateway
<AccordionGroup>
<Accordion title="OpenRouter provisioned keys" icon="cloud">
Managed service, zero infrastructure. [OpenRouter](https://openrouter.ai/docs/features/provisioning-api-keys) provides per-tenant API keys with spend tracking and budget limits via their Provisioning API. Pass the tenant key to Sandbox Agent as `OPENAI_API_KEY` (OpenRouter uses OpenAI-compatible endpoints).
```bash
# Create a key for a tenant with a $50/month budget
curl https://openrouter.ai/api/v1/keys \
-H "Authorization: Bearer $PROVISIONING_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "tenant-acme",
"limit": 50,
"limitResetType": "monthly"
}'
```
Easiest to set up but not open-source. See [OpenRouter pricing](https://openrouter.ai/docs/framework/pricing) for details.
</Accordion>
<Accordion title="LiteLLM proxy" icon="server">
Self-hosted, open-source (MIT). [LiteLLM](https://github.com/BerriAI/litellm) is an OpenAI-compatible proxy with hierarchical budgets (org, team, user, key), virtual keys, and spend tracking. Requires Python + PostgreSQL.
```bash
# Create a team (tenant) with a $500 budget
curl http://litellm:4000/team/new \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"team_alias": "tenant-acme",
"max_budget": 500
}'
# Generate a key for that team
curl http://litellm:4000/key/generate \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"team_id": "team-abc123",
"max_budget": 100
}'
```
Full control with no vendor lock-in. Organization-level features require an enterprise license.
</Accordion>
<Accordion title="Portkey gateway" icon="code-branch">
Self-hosted, open-source (Apache 2.0). [Portkey](https://github.com/Portkey-AI/gateway) is a lightweight OpenAI-compatible gateway supporting 200+ providers. Single binary, no database required. Create virtual keys with per-tenant budget limits and pass them to Sandbox Agent.
Lightest operational footprint of the self-hosted options. Observability and analytics require the managed platform or your own tooling.
</Accordion>
</AccordionGroup>
To bill tenants for LLM usage, use [Stripe token billing](https://docs.stripe.com/billing/token-billing) (integrates natively with OpenRouter) or query your gateway's spend API and feed usage into your billing system.
### Bring your own key
Each user provides their own API key. Users are billed directly by the LLM provider with no additional infrastructure needed.
Pass the user's key via `spawn.env`:
```typescript
const sdk = await SandboxAgent.start({
spawn: {
env: {
ANTHROPIC_API_KEY: userProvidedKey,
},
},
});
```
#### Security
API keys are typically long-lived. The key is visible to the agent and anything running inside the sandbox, so exfiltration is possible. This is usually acceptable for developer-facing tools where the user owns the key.
#### Use cases
- **Developer tools**: each user manages their own API key
- **Internal platforms**: users already have LLM provider accounts
- **Per-user billing**: no extra infrastructure needed
### Shared credentials
A single organization-wide API key is used for all sessions. All token usage appears on one bill with no per-user or per-tenant cost attribution.
```typescript
const sdk = await SandboxAgent.start({
spawn: {
env: {
ANTHROPIC_API_KEY: process.env.ORG_ANTHROPIC_KEY!,
OPENAI_API_KEY: process.env.ORG_OPENAI_KEY!,
},
},
});
```
If you need to track or limit spend per tenant, use a per-tenant gateway instead.
#### Security
Not recommended for anything other than internal tooling. A single exfiltrated key exposes your organization's entire LLM budget. If you need org-paid credentials for external users, use a per-tenant gateway with scoped keys instead.
#### Use cases
- **Single-tenant apps**: small number of users, one bill
- **Prototyping**: cost attribution not needed yet
- **Simplicity over security**: acceptable when exfiltration risk is low
### Personal subscription
If the user is signed into Claude Code or Codex on the host machine, Sandbox Agent automatically picks up their OAuth tokens. No configuration is needed.
#### Remote sandboxes
Extract credentials locally and pass them to a remote sandbox via `spawn.env`:
```bash
$ sandbox-agent credentials extract-env
ANTHROPIC_API_KEY=sk-ant-...
CLAUDE_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
CODEX_API_KEY=sk-...
```
Use `-e` to prefix with `export` for shell sourcing.
#### Security
Personal subscriptions use OAuth tokens with a limited lifespan. These are the same credentials used when running an agent normally on the host. If a token is exfiltrated from the sandbox, the exposure window is short.
#### Use cases
- **Local development**: users are already signed into Claude Code or Codex
- **Internal tools**: every user has their own subscription
- **Prototyping**: no key management needed

View file

@ -77,6 +77,9 @@ icon: "rocket"
<Accordion title="Testing without API keys">
Use the `mock` agent for SDK and integration testing without provider credentials.
</Accordion>
<Accordion title="Multi-tenant and per-user billing">
For per-tenant token tracking, budget enforcement, or usage-based billing, see [LLM Credentials](/llm-credentials) for gateway options like OpenRouter, LiteLLM, and Portkey.
</Accordion>
</AccordionGroup>
</Step>

View file

@ -225,3 +225,7 @@ Parameters:
- `fetch` (optional): Custom fetch implementation used by SDK HTTP and session calls
- `waitForHealth` (optional, defaults to enabled): waits for `/v1/health` before HTTP helpers and session setup proceed; pass `false` to disable or `{ timeoutMs }` to bound the wait
- `signal` (optional): aborts the startup `/v1/health` wait used by `connect()`
## LLM credentials
Sandbox Agent supports personal API keys, shared organization keys, and per-tenant gateway keys with budget enforcement. See [LLM Credentials](/llm-credentials) for setup details.