feat: refresh web console theme

This commit is contained in:
Nathan Flurry 2026-01-25 03:33:34 -08:00
parent 0fbf6272b1
commit 1fcae6ed76
34 changed files with 5037 additions and 748 deletions

1
.gitignore vendored
View file

@ -13,6 +13,7 @@ yarn.lock
# Cache # Cache
.cache/ .cache/
*.tsbuildinfo *.tsbuildinfo
.turbo/
# Environment # Environment
.env .env

View file

@ -11,5 +11,15 @@ Documentation lives in `docs/` (Mintlify). Start with:
Quickstart (local dev): Quickstart (local dev):
```bash ```bash
sandbox-agent --token "$SANDBOX_TOKEN" --host 127.0.0.1 --port 8787 sandbox-agent --token "$SANDBOX_TOKEN" --host 127.0.0.1 --port 2468
```
Extract API keys from local agent configs (Claude Code, Codex, OpenCode, Amp):
```bash
# Print env vars
sandbox-agent credentials extract-env
# Export to current shell
eval "$(sandbox-agent credentials extract-env --export)"
``` ```

View file

@ -8,7 +8,7 @@ The `sandbox-agent` CLI mirrors the HTTP API so you can script everything withou
## Server flags ## Server flags
```bash ```bash
sandbox-agent --token "$SANDBOX_TOKEN" --host 127.0.0.1 --port 8787 sandbox-agent --token "$SANDBOX_TOKEN" --host 127.0.0.1 --port 2468
``` ```
- `--token`: global token for all requests. - `--token`: global token for all requests.
@ -22,7 +22,7 @@ sandbox-agent --token "$SANDBOX_TOKEN" --host 127.0.0.1 --port 8787
<summary><strong>agents list</strong></summary> <summary><strong>agents list</strong></summary>
```bash ```bash
sandbox-agent agents list --endpoint http://127.0.0.1:8787 sandbox-agent agents list --endpoint http://127.0.0.1:2468
``` ```
</details> </details>
@ -30,7 +30,7 @@ sandbox-agent agents list --endpoint http://127.0.0.1:8787
<summary><strong>agents install</strong></summary> <summary><strong>agents install</strong></summary>
```bash ```bash
sandbox-agent agents install claude --reinstall --endpoint http://127.0.0.1:8787 sandbox-agent agents install claude --reinstall --endpoint http://127.0.0.1:2468
``` ```
</details> </details>
@ -38,7 +38,7 @@ sandbox-agent agents install claude --reinstall --endpoint http://127.0.0.1:8787
<summary><strong>agents modes</strong></summary> <summary><strong>agents modes</strong></summary>
```bash ```bash
sandbox-agent agents modes claude --endpoint http://127.0.0.1:8787 sandbox-agent agents modes claude --endpoint http://127.0.0.1:2468
``` ```
</details> </details>
@ -52,7 +52,7 @@ sandbox-agent sessions create my-session \
--agent claude \ --agent claude \
--agent-mode build \ --agent-mode build \
--permission-mode default \ --permission-mode default \
--endpoint http://127.0.0.1:8787 --endpoint http://127.0.0.1:2468
``` ```
</details> </details>
@ -62,7 +62,7 @@ sandbox-agent sessions create my-session \
```bash ```bash
sandbox-agent sessions send-message my-session \ sandbox-agent sessions send-message my-session \
--message "Summarize the repository" \ --message "Summarize the repository" \
--endpoint http://127.0.0.1:8787 --endpoint http://127.0.0.1:2468
``` ```
</details> </details>
@ -70,7 +70,7 @@ sandbox-agent sessions send-message my-session \
<summary><strong>sessions events</strong></summary> <summary><strong>sessions events</strong></summary>
```bash ```bash
sandbox-agent sessions events my-session --offset 0 --limit 50 --endpoint http://127.0.0.1:8787 sandbox-agent sessions events my-session --offset 0 --limit 50 --endpoint http://127.0.0.1:2468
``` ```
</details> </details>
@ -78,7 +78,7 @@ sandbox-agent sessions events my-session --offset 0 --limit 50 --endpoint http:/
<summary><strong>sessions events-sse</strong></summary> <summary><strong>sessions events-sse</strong></summary>
```bash ```bash
sandbox-agent sessions events-sse my-session --offset 0 --endpoint http://127.0.0.1:8787 sandbox-agent sessions events-sse my-session --offset 0 --endpoint http://127.0.0.1:2468
``` ```
</details> </details>
@ -88,7 +88,7 @@ sandbox-agent sessions events-sse my-session --offset 0 --endpoint http://127.0.
```bash ```bash
sandbox-agent sessions reply-question my-session QUESTION_ID \ sandbox-agent sessions reply-question my-session QUESTION_ID \
--answers "yes" \ --answers "yes" \
--endpoint http://127.0.0.1:8787 --endpoint http://127.0.0.1:2468
``` ```
</details> </details>
@ -96,7 +96,7 @@ sandbox-agent sessions reply-question my-session QUESTION_ID \
<summary><strong>sessions reject-question</strong></summary> <summary><strong>sessions reject-question</strong></summary>
```bash ```bash
sandbox-agent sessions reject-question my-session QUESTION_ID --endpoint http://127.0.0.1:8787 sandbox-agent sessions reject-question my-session QUESTION_ID --endpoint http://127.0.0.1:2468
``` ```
</details> </details>
@ -106,6 +106,6 @@ sandbox-agent sessions reject-question my-session QUESTION_ID --endpoint http://
```bash ```bash
sandbox-agent sessions reply-permission my-session PERMISSION_ID \ sandbox-agent sessions reply-permission my-session PERMISSION_ID \
--reply once \ --reply once \
--endpoint http://127.0.0.1:8787 --endpoint http://127.0.0.1:2468
``` ```
</details> </details>

View file

@ -15,7 +15,7 @@ export SANDBOX_TOKEN="..."
cargo run -p sandbox-agent -- \ cargo run -p sandbox-agent -- \
--token "$SANDBOX_TOKEN" \ --token "$SANDBOX_TOKEN" \
--host 0.0.0.0 \ --host 0.0.0.0 \
--port 8787 --port 2468
``` ```
4. Connect your client to the sandbox endpoint. 4. Connect your client to the sandbox endpoint.

View file

@ -7,7 +7,7 @@ description: "Run the daemon in a Daytona workspace."
1. Create a Daytona workspace with Rust and curl available. 1. Create a Daytona workspace with Rust and curl available.
2. Install or build the sandbox-agent binary. 2. Install or build the sandbox-agent binary.
3. Start the daemon and expose port `8787` (or your preferred port). 3. Start the daemon and expose port `2468` (or your preferred port).
```bash ```bash
export SANDBOX_TOKEN="..." export SANDBOX_TOKEN="..."
@ -15,7 +15,7 @@ export SANDBOX_TOKEN="..."
cargo run -p sandbox-agent -- \ cargo run -p sandbox-agent -- \
--token "$SANDBOX_TOKEN" \ --token "$SANDBOX_TOKEN" \
--host 0.0.0.0 \ --host 0.0.0.0 \
--port 8787 --port 2468
``` ```
4. Use your Daytona port forwarding to reach the daemon from your client. 4. Use your Daytona port forwarding to reach the daemon from your client.

View file

@ -18,10 +18,10 @@ The binary will be written to `./artifacts/sandbox-agent-x86_64-unknown-linux-mu
## Run the daemon ## Run the daemon
```bash ```bash
docker run --rm -p 8787:8787 \ docker run --rm -p 2468:2468 \
-v "$PWD/artifacts:/artifacts" \ -v "$PWD/artifacts:/artifacts" \
debian:bookworm-slim \ debian:bookworm-slim \
/artifacts/sandbox-agent-x86_64-unknown-linux-musl --token "$SANDBOX_TOKEN" --host 0.0.0.0 --port 8787 /artifacts/sandbox-agent-x86_64-unknown-linux-musl --token "$SANDBOX_TOKEN" --host 0.0.0.0 --port 2468
``` ```
You can now access the API at `http://localhost:8787`. You can now access the API at `http://localhost:2468`.

View file

@ -19,7 +19,7 @@ export SANDBOX_TOKEN="..."
cargo run -p sandbox-agent -- \ cargo run -p sandbox-agent -- \
--token "$SANDBOX_TOKEN" \ --token "$SANDBOX_TOKEN" \
--host 0.0.0.0 \ --host 0.0.0.0 \
--port 8787 --port 2468
``` ```
4. Configure your client to connect to the sandbox endpoint. 4. Configure your client to connect to the sandbox endpoint.

View file

@ -15,7 +15,7 @@ export SANDBOX_TOKEN="..."
cargo run -p sandbox-agent -- \ cargo run -p sandbox-agent -- \
--token "$SANDBOX_TOKEN" \ --token "$SANDBOX_TOKEN" \
--host 0.0.0.0 \ --host 0.0.0.0 \
--port 8787 --port 2468
``` ```
4. Configure your client to use the sandbox URL. 4. Configure your client to use the sandbox URL.

View file

@ -14,7 +14,7 @@ pnpm --filter @sandbox-agent/web dev
The UI expects: The UI expects:
- Endpoint (e.g. `http://127.0.0.1:8787`) - Endpoint (e.g. `http://127.0.0.1:2468`)
- Optional token - Optional token
If you see CORS errors, enable CORS on the daemon with `--cors-allow-origin` and related flags. If you see CORS errors, enable CORS on the daemon with `--cors-allow-origin` and related flags.

View file

@ -5,6 +5,18 @@ description: "Endpoint reference for the sandbox agent daemon."
All endpoints are under `/v1`. Authentication uses the daemon-level token via `Authorization: Bearer <token>` or `x-sandbox-token`. All endpoints are under `/v1`. Authentication uses the daemon-level token via `Authorization: Bearer <token>` or `x-sandbox-token`.
## Health
<details>
<summary><strong>GET /v1/health</strong> - Connectivity check</summary>
Response:
```json
{ "status": "ok" }
```
</details>
## Sessions ## Sessions
<details> <details>

View file

@ -18,18 +18,18 @@ Sandbox Agent SDK is a universal API and daemon for running coding agents inside
Run the daemon locally: Run the daemon locally:
```bash ```bash
sandbox-agent --token "$SANDBOX_TOKEN" --host 127.0.0.1 --port 8787 sandbox-agent --token "$SANDBOX_TOKEN" --host 127.0.0.1 --port 2468
``` ```
Send a message: Send a message:
```bash ```bash
curl -X POST "http://127.0.0.1:8787/v1/sessions/my-session" \ curl -X POST "http://127.0.0.1:2468/v1/sessions/my-session" \
-H "Authorization: Bearer $SANDBOX_TOKEN" \ -H "Authorization: Bearer $SANDBOX_TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"agent":"claude"}' -d '{"agent":"claude"}'
curl -X POST "http://127.0.0.1:8787/v1/sessions/my-session/messages" \ curl -X POST "http://127.0.0.1:2468/v1/sessions/my-session/messages" \
-H "Authorization: Bearer $SANDBOX_TOKEN" \ -H "Authorization: Bearer $SANDBOX_TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"message":"Explain the repo structure."}' -d '{"message":"Explain the repo structure."}'

View file

@ -8,13 +8,13 @@ description: "Start the daemon and send your first message."
Use the installed binary, or `cargo run` in development. Use the installed binary, or `cargo run` in development.
```bash ```bash
sandbox-agent --token "$SANDBOX_TOKEN" --host 127.0.0.1 --port 8787 sandbox-agent --token "$SANDBOX_TOKEN" --host 127.0.0.1 --port 2468
``` ```
If you want to run without auth (local dev only): If you want to run without auth (local dev only):
```bash ```bash
sandbox-agent --no-token --host 127.0.0.1 --port 8787 sandbox-agent --no-token --host 127.0.0.1 --port 2468
``` ```
### CORS (frontend usage) ### CORS (frontend usage)
@ -35,7 +35,7 @@ sandbox-agent \
## 2. Create a session ## 2. Create a session
```bash ```bash
curl -X POST "http://127.0.0.1:8787/v1/sessions/my-session" \ curl -X POST "http://127.0.0.1:2468/v1/sessions/my-session" \
-H "Authorization: Bearer $SANDBOX_TOKEN" \ -H "Authorization: Bearer $SANDBOX_TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"agent":"claude","agentMode":"build","permissionMode":"default"}' -d '{"agent":"claude","agentMode":"build","permissionMode":"default"}'
@ -44,7 +44,7 @@ curl -X POST "http://127.0.0.1:8787/v1/sessions/my-session" \
## 3. Send a message ## 3. Send a message
```bash ```bash
curl -X POST "http://127.0.0.1:8787/v1/sessions/my-session/messages" \ curl -X POST "http://127.0.0.1:2468/v1/sessions/my-session/messages" \
-H "Authorization: Bearer $SANDBOX_TOKEN" \ -H "Authorization: Bearer $SANDBOX_TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"message":"Summarize the repository and suggest next steps."}' -d '{"message":"Summarize the repository and suggest next steps."}'
@ -53,14 +53,14 @@ curl -X POST "http://127.0.0.1:8787/v1/sessions/my-session/messages" \
## 4. Read events ## 4. Read events
```bash ```bash
curl "http://127.0.0.1:8787/v1/sessions/my-session/events?offset=0&limit=50" \ curl "http://127.0.0.1:2468/v1/sessions/my-session/events?offset=0&limit=50" \
-H "Authorization: Bearer $SANDBOX_TOKEN" -H "Authorization: Bearer $SANDBOX_TOKEN"
``` ```
For streaming output, use SSE: For streaming output, use SSE:
```bash ```bash
curl "http://127.0.0.1:8787/v1/sessions/my-session/events/sse?offset=0" \ curl "http://127.0.0.1:2468/v1/sessions/my-session/events/sse?offset=0" \
-H "Authorization: Bearer $SANDBOX_TOKEN" -H "Authorization: Bearer $SANDBOX_TOKEN"
``` ```
@ -69,7 +69,7 @@ curl "http://127.0.0.1:8787/v1/sessions/my-session/events/sse?offset=0" \
The CLI mirrors the HTTP API: The CLI mirrors the HTTP API:
```bash ```bash
sandbox-agent sessions create my-session --agent claude --endpoint http://127.0.0.1:8787 --token "$SANDBOX_TOKEN" sandbox-agent sessions create my-session --agent claude --endpoint http://127.0.0.1:2468 --token "$SANDBOX_TOKEN"
sandbox-agent sessions send-message my-session --message "Hello" --endpoint http://127.0.0.1:8787 --token "$SANDBOX_TOKEN" sandbox-agent sessions send-message my-session --message "Hello" --endpoint http://127.0.0.1:2468 --token "$SANDBOX_TOKEN"
``` ```

View file

@ -8,7 +8,7 @@ The TypeScript SDK is generated from the OpenAPI spec produced by the Rust serve
## Generate types ## Generate types
```bash ```bash
pnpm --filter @sandbox-agent/typescript-sdk generate pnpm --filter sandbox-agent generate
``` ```
This runs: This runs:
@ -19,10 +19,10 @@ This runs:
## Usage ## Usage
```ts ```ts
import { SandboxDaemonClient } from "@sandbox-agent/typescript-sdk"; import { SandboxDaemonClient } from "sandbox-agent";
const client = new SandboxDaemonClient({ const client = new SandboxDaemonClient({
baseUrl: "http://127.0.0.1:8787", baseUrl: "http://127.0.0.1:2468",
token: process.env.SANDBOX_TOKEN, token: process.env.SANDBOX_TOKEN,
}); });

View file

@ -98,6 +98,9 @@ enum AgentsCommand {
enum CredentialsCommand { enum CredentialsCommand {
/// Extract credentials using local discovery rules. /// Extract credentials using local discovery rules.
Extract(CredentialsExtractArgs), Extract(CredentialsExtractArgs),
/// Output credentials as environment variable assignments.
#[command(name = "extract-env")]
ExtractEnv(CredentialsExtractEnvArgs),
} }
#[derive(Subcommand, Debug)] #[derive(Subcommand, Debug)]
@ -239,6 +242,17 @@ struct CredentialsExtractArgs {
reveal: bool, reveal: bool,
} }
#[derive(Args, Debug)]
struct CredentialsExtractEnvArgs {
/// Prefix each line with "export " for shell sourcing.
#[arg(long, short = 'e')]
export: bool,
#[arg(long, short = 'd')]
home_dir: Option<PathBuf>,
#[arg(long, short = 'n')]
no_oauth: bool,
}
#[derive(Debug, Error)] #[derive(Debug, Error)]
enum CliError { enum CliError {
#[error("missing --token or --no-token for server mode")] #[error("missing --token or --no-token for server mode")]
@ -455,6 +469,33 @@ fn run_credentials(command: &CredentialsCommand) -> Result<(), CliError> {
write_stdout_line(&pretty)?; write_stdout_line(&pretty)?;
Ok(()) Ok(())
} }
CredentialsCommand::ExtractEnv(args) => {
let mut options = CredentialExtractionOptions::new();
if let Some(home_dir) = args.home_dir.clone() {
options.home_dir = Some(home_dir);
}
if args.no_oauth {
options.include_oauth = false;
}
let credentials = extract_all_credentials(&options);
let prefix = if args.export { "export " } else { "" };
if let Some(cred) = &credentials.anthropic {
write_stdout_line(&format!("{}ANTHROPIC_API_KEY={}", prefix, cred.api_key))?;
write_stdout_line(&format!("{}CLAUDE_API_KEY={}", prefix, cred.api_key))?;
}
if let Some(cred) = &credentials.openai {
write_stdout_line(&format!("{}OPENAI_API_KEY={}", prefix, cred.api_key))?;
write_stdout_line(&format!("{}CODEX_API_KEY={}", prefix, cred.api_key))?;
}
for (provider, cred) in &credentials.other {
let var_name = format!("{}_API_KEY", provider.to_uppercase().replace('-', "_"));
write_stdout_line(&format!("{}{}={}", prefix, var_name, cred.api_key))?;
}
Ok(())
}
} }
} }

View file

@ -76,6 +76,7 @@ pub fn build_router(state: AppState) -> Router {
let shared = Arc::new(state); let shared = Arc::new(state);
let mut v1_router = Router::new() let mut v1_router = Router::new()
.route("/health", get(get_health))
.route("/agents", get(list_agents)) .route("/agents", get(list_agents))
.route("/agents/:agent/install", post(install_agent)) .route("/agents/:agent/install", post(install_agent))
.route("/agents/:agent/modes", get(get_agent_modes)) .route("/agents/:agent/modes", get(get_agent_modes))
@ -107,6 +108,7 @@ pub fn build_router(state: AppState) -> Router {
#[derive(OpenApi)] #[derive(OpenApi)]
#[openapi( #[openapi(
paths( paths(
get_health,
install_agent, install_agent,
get_agent_modes, get_agent_modes,
list_agents, list_agents,
@ -125,6 +127,7 @@ pub fn build_router(state: AppState) -> Router {
AgentModesResponse, AgentModesResponse,
AgentInfo, AgentInfo,
AgentListResponse, AgentListResponse,
HealthResponse,
CreateSessionRequest, CreateSessionRequest,
CreateSessionResponse, CreateSessionResponse,
MessageRequest, MessageRequest,
@ -153,6 +156,7 @@ pub fn build_router(state: AppState) -> Router {
) )
), ),
tags( tags(
(name = "meta", description = "Service metadata"),
(name = "agents", description = "Agent management"), (name = "agents", description = "Agent management"),
(name = "sessions", description = "Session management") (name = "sessions", description = "Session management")
) )
@ -1202,12 +1206,6 @@ fn extract_token(headers: &HeaderMap) -> Option<String> {
} }
} }
if let Some(value) = headers.get("x-sandbox-token") {
if let Ok(value) = value.to_str() {
return Some(value.to_string());
}
}
None None
} }
@ -1249,6 +1247,12 @@ pub struct AgentListResponse {
pub agents: Vec<AgentInfo>, pub agents: Vec<AgentInfo>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct HealthResponse {
pub status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)] #[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CreateSessionRequest { pub struct CreateSessionRequest {
@ -1390,6 +1394,18 @@ async fn get_agent_modes(
Ok(Json(AgentModesResponse { modes })) Ok(Json(AgentModesResponse { modes }))
} }
#[utoipa::path(
get,
path = "/v1/health",
responses((status = 200, body = HealthResponse)),
tag = "meta"
)]
async fn get_health() -> Json<HealthResponse> {
Json(HealthResponse {
status: "ok".to_string(),
})
}
#[utoipa::path( #[utoipa::path(
get, get,
path = "/v1/agents", path = "/v1/agents",

File diff suppressed because it is too large Load diff

View file

@ -6,11 +6,11 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "pnpm --filter @sandbox-agent/typescript-sdk build && vite build", "build": "pnpm --filter sandbox-agent build && vite build",
"preview": "vite preview" "preview": "vite preview"
}, },
"devDependencies": { "devDependencies": {
"@sandbox-agent/typescript-sdk": "workspace:*", "sandbox-agent": "workspace:*",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",

View file

@ -152,7 +152,7 @@ const escapeSingleQuotes = (value: string) => value.replace(/'/g, `'\\''`);
const buildCurl = (method: string, url: string, body?: string, token?: string) => { const buildCurl = (method: string, url: string, body?: string, token?: string) => {
const headers: string[] = []; const headers: string[] = [];
if (token) { if (token) {
headers.push(`-H 'x-sandbox-token: ${escapeSingleQuotes(token)}'`); headers.push(`-H 'Authorization: Bearer ${escapeSingleQuotes(token)}'`);
} }
if (body) { if (body) {
headers.push(`-H 'Content-Type: application/json'`); headers.push(`-H 'Content-Type: application/json'`);
@ -180,7 +180,7 @@ const formatTime = (value: string) => {
}; };
export default function App() { export default function App() {
const [endpoint, setEndpoint] = useState("http://localhost:8787"); const [endpoint, setEndpoint] = useState("http://localhost:2468");
const [token, setToken] = useState(""); const [token, setToken] = useState("");
const [connected, setConnected] = useState(false); const [connected, setConnected] = useState(false);
const [connecting, setConnecting] = useState(false); const [connecting, setConnecting] = useState(false);
@ -242,7 +242,7 @@ export default function App() {
headers["Content-Type"] = "application/json"; headers["Content-Type"] = "application/json";
} }
if (token) { if (token) {
headers["x-sandbox-token"] = token; headers.Authorization = `Bearer ${token}`;
} }
const curl = buildCurl(method, url, bodyText, token); const curl = buildCurl(method, url, bodyText, token);
const logId = logIdRef.current++; const logId = logIdRef.current++;
@ -289,13 +289,9 @@ export default function App() {
setConnecting(true); setConnecting(true);
setConnectError(null); setConnectError(null);
try { try {
const data = await apiFetch(`${API_PREFIX}/agents`); await apiFetch(`${API_PREFIX}/health`);
const list = (data as { agents?: AgentInfo[] })?.agents ?? [];
setAgents(list);
if (list.length > 0) {
setAgentId(list[0]?.id ?? "claude");
}
setConnected(true); setConnected(true);
await refreshAgents();
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : "Unable to connect"; const message = error instanceof Error ? error.message : "Unable to connect";
setConnectError(message); setConnectError(message);
@ -606,8 +602,9 @@ export default function App() {
one place. one place.
</div> </div>
<div className="callout mono"> <div className="callout mono">
sandbox-agent --host 0.0.0.0 --port 8787 --token &lt;token&gt; --cors-allowed-origin sandbox-agent --host 0.0.0.0 --port 2468 --token &lt;token&gt; --cors-allow-origin
http://localhost:5173 --cors-allowed-methods GET,POST --cors-allowed-headers Authorization,x-sandbox-token http://localhost:5173 --cors-allow-method GET --cors-allow-method POST --cors-allow-header Authorization
--cors-allow-header Content-Type
</div> </div>
<div className="tag-list"> <div className="tag-list">
<span className="pill">CORS required for browser access</span> <span className="pill">CORS required for browser access</span>
@ -630,7 +627,7 @@ export default function App() {
<span className="label">Endpoint</span> <span className="label">Endpoint</span>
<input <input
className="input" className="input"
placeholder="http://localhost:8787" placeholder="http://localhost:2468"
value={endpoint} value={endpoint}
onChange={(event) => setEndpoint(event.target.value)} onChange={(event) => setEndpoint(event.target.value)}
/> />
@ -639,7 +636,7 @@ export default function App() {
<span className="label">Token (optional)</span> <span className="label">Token (optional)</span>
<input <input
className="input" className="input"
placeholder="x-sandbox-token" placeholder="token"
value={token} value={token}
onChange={(event) => setToken(event.target.value)} onChange={(event) => setToken(event.target.value)}
/> />

2333
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -4,11 +4,11 @@
"type": "module", "type": "module",
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": { "scripts": {
"extract": "tsx ../../src/agents/index.ts", "extract": "tsx src/index.ts",
"extract:opencode": "tsx ../../src/agents/index.ts --agent=opencode", "extract:opencode": "tsx src/index.ts --agent=opencode",
"extract:claude": "tsx ../../src/agents/index.ts --agent=claude", "extract:claude": "tsx src/index.ts --agent=claude",
"extract:codex": "tsx ../../src/agents/index.ts --agent=codex", "extract:codex": "tsx src/index.ts --agent=codex",
"extract:amp": "tsx ../../src/agents/index.ts --agent=amp" "extract:amp": "tsx src/index.ts --agent=amp"
}, },
"dependencies": { "dependencies": {
"ts-json-schema-generator": "^2.4.0", "ts-json-schema-generator": "^2.4.0",

View file

@ -1,5 +1,5 @@
{ {
"name": "@sandbox-agent/typescript-sdk", "name": "sandbox-agent",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"license": "Apache-2.0", "license": "Apache-2.0",

View file

@ -1,4 +1,4 @@
import type { components } from "./generated/openapi"; import type { components } from "./generated/openapi.js";
export type AgentInstallRequest = components["schemas"]["AgentInstallRequest"]; export type AgentInstallRequest = components["schemas"]["AgentInstallRequest"];
export type AgentModeInfo = components["schemas"]["AgentModeInfo"]; export type AgentModeInfo = components["schemas"]["AgentModeInfo"];

File diff suppressed because it is too large Load diff

View file

@ -1,250 +1,57 @@
/* eslint-disable */ /**
// This file is generated by openapi-typescript. Do not edit by hand. * This file was auto-generated by openapi-typescript.
* Do not make direct changes to the file.
*/
/** OneOf type helpers */
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
type OneOf<T extends any[]> = T extends [infer Only] ? Only : T extends [infer A, infer B, ...infer Rest] ? OneOf<[XOR<A, B>, ...Rest]> : never;
export interface paths { export interface paths {
"/v1/agents": { "/v1/agents": {
get: { get: operations["list_agents"];
responses: {
200: {
content: {
"application/json": components["schemas"]["AgentListResponse"];
};
};
};
};
}; };
"/v1/agents/{agent}/install": { "/v1/agents/{agent}/install": {
post: { post: operations["install_agent"];
parameters: {
path: {
agent: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["AgentInstallRequest"];
};
};
responses: {
204: {
description: string;
};
400: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
500: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
}; };
"/v1/agents/{agent}/modes": { "/v1/agents/{agent}/modes": {
get: { get: operations["get_agent_modes"];
parameters: {
path: {
agent: string;
};
};
responses: {
200: {
content: {
"application/json": components["schemas"]["AgentModesResponse"];
};
};
400: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
}; };
"/v1/sessions/{session_id}": { "/v1/sessions/{session_id}": {
post: { post: operations["create_session"];
parameters: {
path: {
session_id: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["CreateSessionRequest"];
};
};
responses: {
200: {
content: {
"application/json": components["schemas"]["CreateSessionResponse"];
};
};
400: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
409: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
};
"/v1/sessions/{session_id}/messages": {
post: {
parameters: {
path: {
session_id: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["MessageRequest"];
};
};
responses: {
204: {
description: string;
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
}; };
"/v1/sessions/{session_id}/events": { "/v1/sessions/{session_id}/events": {
get: { get: operations["get_events"];
parameters: {
path: {
session_id: string;
};
query?: {
offset?: number | null;
limit?: number | null;
};
};
responses: {
200: {
content: {
"application/json": components["schemas"]["EventsResponse"];
};
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
}; };
"/v1/sessions/{session_id}/events/sse": { "/v1/sessions/{session_id}/events/sse": {
get: { get: operations["get_events_sse"];
parameters: {
path: {
session_id: string;
};
query?: {
offset?: number | null;
};
};
responses: {
200: {
description: string;
};
};
};
}; };
"/v1/sessions/{session_id}/questions/{question_id}/reply": { "/v1/sessions/{session_id}/messages": {
post: { post: operations["post_message"];
parameters: {
path: {
session_id: string;
question_id: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["QuestionReplyRequest"];
};
};
responses: {
204: {
description: string;
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
};
"/v1/sessions/{session_id}/questions/{question_id}/reject": {
post: {
parameters: {
path: {
session_id: string;
question_id: string;
};
};
responses: {
204: {
description: string;
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
}; };
"/v1/sessions/{session_id}/permissions/{permission_id}/reply": { "/v1/sessions/{session_id}/permissions/{permission_id}/reply": {
post: { post: operations["reply_permission"];
parameters: { };
path: { "/v1/sessions/{session_id}/questions/{question_id}/reject": {
session_id: string; post: operations["reject_question"];
permission_id: string; };
}; "/v1/sessions/{session_id}/questions/{question_id}/reply": {
}; post: operations["reply_question"];
requestBody: {
content: {
"application/json": components["schemas"]["PermissionReplyRequest"];
};
};
responses: {
204: {
description: string;
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
}; };
} }
export type webhooks = Record<string, never>;
export interface components { export interface components {
schemas: { schemas: {
AgentError: { AgentError: {
type: components["schemas"]["ErrorType"];
message: string;
agent?: string | null; agent?: string | null;
details?: unknown;
message: string;
session_id?: string | null; session_id?: string | null;
details?: unknown | null; type: components["schemas"]["ErrorType"];
}; };
AgentInfo: { AgentInfo: {
id: string; id: string;
@ -259,31 +66,31 @@ export interface components {
agents: components["schemas"]["AgentInfo"][]; agents: components["schemas"]["AgentInfo"][];
}; };
AgentModeInfo: { AgentModeInfo: {
description: string;
id: string; id: string;
name: string; name: string;
description: string;
}; };
AgentModesResponse: { AgentModesResponse: {
modes: components["schemas"]["AgentModeInfo"][]; modes: components["schemas"]["AgentModeInfo"][];
}; };
AttachmentSource: AttachmentSource: {
| { path: string;
type: "path"; /** @enum {string} */
path: string; type: "path";
} } | {
| { /** @enum {string} */
type: "url"; type: "url";
url: string; url: string;
} } | ({
| { data: string;
type: "data"; encoding?: string | null;
data: string; /** @enum {string} */
encoding?: string | null; type: "data";
}; });
CrashInfo: { CrashInfo: {
message: string; details?: unknown;
kind?: string | null; kind?: string | null;
details?: unknown | null; message: string;
}; };
CreateSessionRequest: { CreateSessionRequest: {
agent: string; agent: string;
@ -291,31 +98,20 @@ export interface components {
agentVersion?: string | null; agentVersion?: string | null;
model?: string | null; model?: string | null;
permissionMode?: string | null; permissionMode?: string | null;
token?: string | null;
validateToken?: boolean | null;
variant?: string | null; variant?: string | null;
}; };
CreateSessionResponse: { CreateSessionResponse: {
healthy: boolean;
error?: components["schemas"]["AgentError"] | null;
agentSessionId?: string | null; agentSessionId?: string | null;
error?: components["schemas"]["AgentError"] | null;
healthy: boolean;
}; };
ErrorType: /** @enum {string} */
| "invalid_request" ErrorType: "invalid_request" | "unsupported_agent" | "agent_not_installed" | "install_failed" | "agent_process_exited" | "token_invalid" | "permission_denied" | "session_not_found" | "session_already_exists" | "mode_not_supported" | "stream_error" | "timeout";
| "unsupported_agent"
| "agent_not_installed"
| "install_failed"
| "agent_process_exited"
| "token_invalid"
| "permission_denied"
| "session_not_found"
| "session_already_exists"
| "mode_not_supported"
| "stream_error"
| "timeout";
EventsQuery: { EventsQuery: {
offset?: number | null; /** Format: int64 */
limit?: number | null; limit?: number | null;
/** Format: int64 */
offset?: number | null;
}; };
EventsResponse: { EventsResponse: {
events: components["schemas"]["UniversalEvent"][]; events: components["schemas"]["UniversalEvent"][];
@ -324,141 +120,152 @@ export interface components {
MessageRequest: { MessageRequest: {
message: string; message: string;
}; };
/** @enum {string} */
PermissionReply: "once" | "always" | "reject"; PermissionReply: "once" | "always" | "reject";
PermissionReplyRequest: { PermissionReplyRequest: {
reply: components["schemas"]["PermissionReply"]; reply: components["schemas"]["PermissionReply"];
}; };
PermissionRequest: { PermissionRequest: {
id: string;
sessionId: string;
permission: string;
patterns: string[];
always: string[]; always: string[];
metadata?: Record<string, unknown>; id: string;
metadata?: {
[key: string]: unknown;
};
patterns: string[];
permission: string;
sessionId: string;
tool?: components["schemas"]["PermissionToolRef"] | null; tool?: components["schemas"]["PermissionToolRef"] | null;
}; };
PermissionToolRef: { PermissionToolRef: {
messageId: string;
callId: string; callId: string;
messageId: string;
}; };
ProblemDetails: { ProblemDetails: {
type: string;
title: string;
status: number;
detail?: string | null; detail?: string | null;
instance?: string | null; instance?: string | null;
/** Format: int32 */
status: number;
title: string;
type: string;
[key: string]: unknown; [key: string]: unknown;
}; };
QuestionInfo: { QuestionInfo: {
question: string; custom?: boolean | null;
options: components["schemas"]["QuestionOption"][];
header?: string | null; header?: string | null;
multiSelect?: boolean | null; multiSelect?: boolean | null;
custom?: boolean | null; options: components["schemas"]["QuestionOption"][];
question: string;
}; };
QuestionOption: { QuestionOption: {
label: string;
description?: string | null; description?: string | null;
label: string;
}; };
QuestionReplyRequest: { QuestionReplyRequest: {
answers: string[][]; answers: string[][];
}; };
QuestionRequest: { QuestionRequest: {
id: string; id: string;
sessionId: string;
questions: components["schemas"]["QuestionInfo"][]; questions: components["schemas"]["QuestionInfo"][];
sessionId: string;
tool?: components["schemas"]["QuestionToolRef"] | null; tool?: components["schemas"]["QuestionToolRef"] | null;
}; };
QuestionToolRef: { QuestionToolRef: {
messageId: string;
callId: string; callId: string;
messageId: string;
}; };
Started: { Started: {
details?: unknown;
message?: string | null; message?: string | null;
details?: unknown | null;
}; };
UniversalEvent: { UniversalEvent: {
id: number;
timestamp: string;
sessionId: string;
agent: string; agent: string;
agentSessionId?: string | null; agentSessionId?: string | null;
data: components["schemas"]["UniversalEventData"]; data: components["schemas"]["UniversalEventData"];
/** Format: int64 */
id: number;
sessionId: string;
timestamp: string;
}; };
UniversalEventData: UniversalEventData: {
| { message: components["schemas"]["UniversalMessage"] } message: components["schemas"]["UniversalMessage"];
| { started: components["schemas"]["Started"] } } | {
| { error: components["schemas"]["CrashInfo"] } started: components["schemas"]["Started"];
| { questionAsked: components["schemas"]["QuestionRequest"] } } | {
| { permissionAsked: components["schemas"]["PermissionRequest"] } error: components["schemas"]["CrashInfo"];
| { raw: unknown }; } | {
UniversalMessage: questionAsked: components["schemas"]["QuestionRequest"];
| components["schemas"]["UniversalMessageParsed"] } | {
| { permissionAsked: components["schemas"]["PermissionRequest"];
raw: unknown; } | {
error?: string | null; raw: unknown;
}; };
UniversalMessage: OneOf<[components["schemas"]["UniversalMessageParsed"], {
error?: string | null;
raw: unknown;
}]>;
UniversalMessageParsed: { UniversalMessageParsed: {
role: string;
parts: components["schemas"]["UniversalMessagePart"][];
id?: string | null; id?: string | null;
metadata?: Record<string, unknown>; metadata?: {
[key: string]: unknown;
};
parts: components["schemas"]["UniversalMessagePart"][];
role: string;
};
UniversalMessagePart: {
text: string;
/** @enum {string} */
type: "text";
} | ({
id?: string | null;
input: unknown;
name: string;
/** @enum {string} */
type: "tool_call";
}) | ({
id?: string | null;
is_error?: boolean | null;
name?: string | null;
output: unknown;
/** @enum {string} */
type: "tool_result";
}) | ({
arguments: unknown;
id?: string | null;
name?: string | null;
raw?: unknown;
/** @enum {string} */
type: "function_call";
}) | ({
id?: string | null;
is_error?: boolean | null;
name?: string | null;
raw?: unknown;
result: unknown;
/** @enum {string} */
type: "function_result";
}) | ({
filename?: string | null;
mime_type?: string | null;
raw?: unknown;
source: components["schemas"]["AttachmentSource"];
/** @enum {string} */
type: "file";
}) | ({
alt?: string | null;
mime_type?: string | null;
raw?: unknown;
source: components["schemas"]["AttachmentSource"];
/** @enum {string} */
type: "image";
}) | {
message: string;
/** @enum {string} */
type: "error";
} | {
raw: unknown;
/** @enum {string} */
type: "unknown";
}; };
UniversalMessagePart:
| {
type: "text";
text: string;
}
| {
type: "tool_call";
name: string;
input: unknown;
id?: string | null;
}
| {
type: "tool_result";
output: unknown;
id?: string | null;
name?: string | null;
is_error?: boolean | null;
}
| {
type: "function_call";
arguments: unknown;
id?: string | null;
name?: string | null;
raw?: unknown | null;
}
| {
type: "function_result";
result: unknown;
id?: string | null;
name?: string | null;
is_error?: boolean | null;
raw?: unknown | null;
}
| {
type: "file";
source: components["schemas"]["AttachmentSource"];
mime_type?: string | null;
filename?: string | null;
raw?: unknown | null;
}
| {
type: "image";
source: components["schemas"]["AttachmentSource"];
mime_type?: string | null;
alt?: string | null;
raw?: unknown | null;
}
| {
type: "error";
message: string;
}
| {
type: "unknown";
raw: unknown;
};
}; };
responses: never; responses: never;
parameters: never; parameters: never;
@ -467,4 +274,244 @@ export interface components {
pathItems: never; pathItems: never;
} }
export type webhooks = never; export type $defs = Record<string, never>;
export type external = Record<string, never>;
export interface operations {
list_agents: {
responses: {
200: {
content: {
"application/json": components["schemas"]["AgentListResponse"];
};
};
};
};
install_agent: {
parameters: {
path: {
/** @description Agent id */
agent: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["AgentInstallRequest"];
};
};
responses: {
/** @description Agent installed */
204: {
content: never;
};
400: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
500: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
get_agent_modes: {
parameters: {
path: {
/** @description Agent id */
agent: string;
};
};
responses: {
200: {
content: {
"application/json": components["schemas"]["AgentModesResponse"];
};
};
400: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
create_session: {
parameters: {
path: {
/** @description Client session id */
session_id: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["CreateSessionRequest"];
};
};
responses: {
200: {
content: {
"application/json": components["schemas"]["CreateSessionResponse"];
};
};
400: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
409: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
get_events: {
parameters: {
query?: {
/** @description Last seen event id (exclusive) */
offset?: number | null;
/** @description Max events to return */
limit?: number | null;
};
path: {
/** @description Session id */
session_id: string;
};
};
responses: {
200: {
content: {
"application/json": components["schemas"]["EventsResponse"];
};
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
get_events_sse: {
parameters: {
query?: {
/** @description Last seen event id (exclusive) */
offset?: number | null;
};
path: {
/** @description Session id */
session_id: string;
};
};
responses: {
/** @description SSE event stream */
200: {
content: never;
};
};
};
post_message: {
parameters: {
path: {
/** @description Session id */
session_id: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["MessageRequest"];
};
};
responses: {
/** @description Message accepted */
204: {
content: never;
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
reply_permission: {
parameters: {
path: {
/** @description Session id */
session_id: string;
/** @description Permission id */
permission_id: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["PermissionReplyRequest"];
};
};
responses: {
/** @description Permission reply accepted */
204: {
content: never;
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
reject_question: {
parameters: {
path: {
/** @description Session id */
session_id: string;
/** @description Question id */
question_id: string;
};
};
responses: {
/** @description Question rejected */
204: {
content: never;
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
reply_question: {
parameters: {
path: {
/** @description Session id */
session_id: string;
/** @description Question id */
question_id: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["QuestionReplyRequest"];
};
};
responses: {
/** @description Question answered */
204: {
content: never;
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
}

View file

@ -1,4 +1,4 @@
export { SandboxDaemonClient, SandboxDaemonError, createSandboxDaemonClient } from "./client"; export { SandboxDaemonClient, SandboxDaemonError, createSandboxDaemonClient } from "./client.js";
export type { export type {
AgentInfo, AgentInfo,
AgentInstallRequest, AgentInstallRequest,
@ -15,5 +15,5 @@ export type {
ProblemDetails, ProblemDetails,
QuestionReplyRequest, QuestionReplyRequest,
UniversalEvent, UniversalEvent,
} from "./client"; } from "./client.js";
export type { components, paths } from "./generated/openapi"; export type { components, paths } from "./generated/openapi.js";

View file

@ -89,7 +89,7 @@ POST /v1/sessions/{} (will install agent if not already installed)
agentSessionId?: string agentSessionId?: string
} }
- The client-provided session id is primary; `agentSessionId` is the underlying agent id (may be unknown until first prompt). - The client-provided session id is primary; `agentSessionId` is the underlying agent id (may be unknown until first prompt).
- Auth uses the daemon-level token (`Authorization` / `x-sandbox-token`); per-session tokens are not supported. - Auth uses the daemon-level token (`Authorization`); per-session tokens are not supported.
// agentMode vs permissionMode: // agentMode vs permissionMode:
// - agentMode = what the agent DOES (behavior, system prompt) // - agentMode = what the agent DOES (behavior, system prompt)

View file

@ -75,6 +75,7 @@
- [x] Add instructions to run sandbox-agent (including CORS) - [x] Add instructions to run sandbox-agent (including CORS)
- [x] Implement full agent UI covering all features - [x] Implement full agent UI covering all features
- [x] Add HTTP request log with copyable curl command - [x] Add HTTP request log with copyable curl command
- [x] Add Content-Type header to CORS callout command
## TypeScript SDK ## TypeScript SDK
- [x] Generate OpenAPI from utoipa and run `openapi-typescript` - [x] Generate OpenAPI from utoipa and run `openapi-typescript`