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/
*.tsbuildinfo
.turbo/
# Environment
.env

View file

@ -11,5 +11,15 @@ Documentation lives in `docs/` (Mintlify). Start with:
Quickstart (local dev):
```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
```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.
@ -22,7 +22,7 @@ sandbox-agent --token "$SANDBOX_TOKEN" --host 127.0.0.1 --port 8787
<summary><strong>agents list</strong></summary>
```bash
sandbox-agent agents list --endpoint http://127.0.0.1:8787
sandbox-agent agents list --endpoint http://127.0.0.1:2468
```
</details>
@ -30,7 +30,7 @@ sandbox-agent agents list --endpoint http://127.0.0.1:8787
<summary><strong>agents install</strong></summary>
```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>
@ -38,7 +38,7 @@ sandbox-agent agents install claude --reinstall --endpoint http://127.0.0.1:8787
<summary><strong>agents modes</strong></summary>
```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>
@ -52,7 +52,7 @@ sandbox-agent sessions create my-session \
--agent claude \
--agent-mode build \
--permission-mode default \
--endpoint http://127.0.0.1:8787
--endpoint http://127.0.0.1:2468
```
</details>
@ -62,7 +62,7 @@ sandbox-agent sessions create my-session \
```bash
sandbox-agent sessions send-message my-session \
--message "Summarize the repository" \
--endpoint http://127.0.0.1:8787
--endpoint http://127.0.0.1:2468
```
</details>
@ -70,7 +70,7 @@ sandbox-agent sessions send-message my-session \
<summary><strong>sessions events</strong></summary>
```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>
@ -78,7 +78,7 @@ sandbox-agent sessions events my-session --offset 0 --limit 50 --endpoint http:/
<summary><strong>sessions events-sse</strong></summary>
```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>
@ -88,7 +88,7 @@ sandbox-agent sessions events-sse my-session --offset 0 --endpoint http://127.0.
```bash
sandbox-agent sessions reply-question my-session QUESTION_ID \
--answers "yes" \
--endpoint http://127.0.0.1:8787
--endpoint http://127.0.0.1:2468
```
</details>
@ -96,7 +96,7 @@ sandbox-agent sessions reply-question my-session QUESTION_ID \
<summary><strong>sessions reject-question</strong></summary>
```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>
@ -106,6 +106,6 @@ sandbox-agent sessions reject-question my-session QUESTION_ID --endpoint http://
```bash
sandbox-agent sessions reply-permission my-session PERMISSION_ID \
--reply once \
--endpoint http://127.0.0.1:8787
--endpoint http://127.0.0.1:2468
```
</details>

View file

@ -15,7 +15,7 @@ export SANDBOX_TOKEN="..."
cargo run -p sandbox-agent -- \
--token "$SANDBOX_TOKEN" \
--host 0.0.0.0 \
--port 8787
--port 2468
```
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.
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
export SANDBOX_TOKEN="..."
@ -15,7 +15,7 @@ export SANDBOX_TOKEN="..."
cargo run -p sandbox-agent -- \
--token "$SANDBOX_TOKEN" \
--host 0.0.0.0 \
--port 8787
--port 2468
```
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
```bash
docker run --rm -p 8787:8787 \
docker run --rm -p 2468:2468 \
-v "$PWD/artifacts:/artifacts" \
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 -- \
--token "$SANDBOX_TOKEN" \
--host 0.0.0.0 \
--port 8787
--port 2468
```
4. Configure your client to connect to the sandbox endpoint.

View file

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

View file

@ -14,7 +14,7 @@ pnpm --filter @sandbox-agent/web dev
The UI expects:
- Endpoint (e.g. `http://127.0.0.1:8787`)
- Endpoint (e.g. `http://127.0.0.1:2468`)
- Optional token
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`.
## Health
<details>
<summary><strong>GET /v1/health</strong> - Connectivity check</summary>
Response:
```json
{ "status": "ok" }
```
</details>
## Sessions
<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:
```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:
```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 "Content-Type: application/json" \
-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 "Content-Type: application/json" \
-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.
```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):
```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)
@ -35,7 +35,7 @@ sandbox-agent \
## 2. Create a session
```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 "Content-Type: application/json" \
-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
```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 "Content-Type: application/json" \
-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
```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"
```
For streaming output, use SSE:
```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"
```
@ -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:
```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
```bash
pnpm --filter @sandbox-agent/typescript-sdk generate
pnpm --filter sandbox-agent generate
```
This runs:
@ -19,10 +19,10 @@ This runs:
## Usage
```ts
import { SandboxDaemonClient } from "@sandbox-agent/typescript-sdk";
import { SandboxDaemonClient } from "sandbox-agent";
const client = new SandboxDaemonClient({
baseUrl: "http://127.0.0.1:8787",
baseUrl: "http://127.0.0.1:2468",
token: process.env.SANDBOX_TOKEN,
});

View file

@ -98,6 +98,9 @@ enum AgentsCommand {
enum CredentialsCommand {
/// Extract credentials using local discovery rules.
Extract(CredentialsExtractArgs),
/// Output credentials as environment variable assignments.
#[command(name = "extract-env")]
ExtractEnv(CredentialsExtractEnvArgs),
}
#[derive(Subcommand, Debug)]
@ -239,6 +242,17 @@ struct CredentialsExtractArgs {
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)]
enum CliError {
#[error("missing --token or --no-token for server mode")]
@ -455,6 +469,33 @@ fn run_credentials(command: &CredentialsCommand) -> Result<(), CliError> {
write_stdout_line(&pretty)?;
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 mut v1_router = Router::new()
.route("/health", get(get_health))
.route("/agents", get(list_agents))
.route("/agents/:agent/install", post(install_agent))
.route("/agents/:agent/modes", get(get_agent_modes))
@ -107,6 +108,7 @@ pub fn build_router(state: AppState) -> Router {
#[derive(OpenApi)]
#[openapi(
paths(
get_health,
install_agent,
get_agent_modes,
list_agents,
@ -125,6 +127,7 @@ pub fn build_router(state: AppState) -> Router {
AgentModesResponse,
AgentInfo,
AgentListResponse,
HealthResponse,
CreateSessionRequest,
CreateSessionResponse,
MessageRequest,
@ -153,6 +156,7 @@ pub fn build_router(state: AppState) -> Router {
)
),
tags(
(name = "meta", description = "Service metadata"),
(name = "agents", description = "Agent 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
}
@ -1249,6 +1247,12 @@ pub struct AgentListResponse {
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)]
#[serde(rename_all = "camelCase")]
pub struct CreateSessionRequest {
@ -1390,6 +1394,18 @@ async fn get_agent_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(
get,
path = "/v1/agents",

File diff suppressed because it is too large Load diff

View file

@ -6,11 +6,11 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "pnpm --filter @sandbox-agent/typescript-sdk build && vite build",
"build": "pnpm --filter sandbox-agent build && vite build",
"preview": "vite preview"
},
"devDependencies": {
"@sandbox-agent/typescript-sdk": "workspace:*",
"sandbox-agent": "workspace:*",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@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 headers: string[] = [];
if (token) {
headers.push(`-H 'x-sandbox-token: ${escapeSingleQuotes(token)}'`);
headers.push(`-H 'Authorization: Bearer ${escapeSingleQuotes(token)}'`);
}
if (body) {
headers.push(`-H 'Content-Type: application/json'`);
@ -180,7 +180,7 @@ const formatTime = (value: string) => {
};
export default function App() {
const [endpoint, setEndpoint] = useState("http://localhost:8787");
const [endpoint, setEndpoint] = useState("http://localhost:2468");
const [token, setToken] = useState("");
const [connected, setConnected] = useState(false);
const [connecting, setConnecting] = useState(false);
@ -242,7 +242,7 @@ export default function App() {
headers["Content-Type"] = "application/json";
}
if (token) {
headers["x-sandbox-token"] = token;
headers.Authorization = `Bearer ${token}`;
}
const curl = buildCurl(method, url, bodyText, token);
const logId = logIdRef.current++;
@ -289,13 +289,9 @@ export default function App() {
setConnecting(true);
setConnectError(null);
try {
const data = await apiFetch(`${API_PREFIX}/agents`);
const list = (data as { agents?: AgentInfo[] })?.agents ?? [];
setAgents(list);
if (list.length > 0) {
setAgentId(list[0]?.id ?? "claude");
}
await apiFetch(`${API_PREFIX}/health`);
setConnected(true);
await refreshAgents();
} catch (error) {
const message = error instanceof Error ? error.message : "Unable to connect";
setConnectError(message);
@ -606,8 +602,9 @@ export default function App() {
one place.
</div>
<div className="callout mono">
sandbox-agent --host 0.0.0.0 --port 8787 --token &lt;token&gt; --cors-allowed-origin
http://localhost:5173 --cors-allowed-methods GET,POST --cors-allowed-headers Authorization,x-sandbox-token
sandbox-agent --host 0.0.0.0 --port 2468 --token &lt;token&gt; --cors-allow-origin
http://localhost:5173 --cors-allow-method GET --cors-allow-method POST --cors-allow-header Authorization
--cors-allow-header Content-Type
</div>
<div className="tag-list">
<span className="pill">CORS required for browser access</span>
@ -630,7 +627,7 @@ export default function App() {
<span className="label">Endpoint</span>
<input
className="input"
placeholder="http://localhost:8787"
placeholder="http://localhost:2468"
value={endpoint}
onChange={(event) => setEndpoint(event.target.value)}
/>
@ -639,7 +636,7 @@ export default function App() {
<span className="label">Token (optional)</span>
<input
className="input"
placeholder="x-sandbox-token"
placeholder="token"
value={token}
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",
"license": "Apache-2.0",
"scripts": {
"extract": "tsx ../../src/agents/index.ts",
"extract:opencode": "tsx ../../src/agents/index.ts --agent=opencode",
"extract:claude": "tsx ../../src/agents/index.ts --agent=claude",
"extract:codex": "tsx ../../src/agents/index.ts --agent=codex",
"extract:amp": "tsx ../../src/agents/index.ts --agent=amp"
"extract": "tsx src/index.ts",
"extract:opencode": "tsx src/index.ts --agent=opencode",
"extract:claude": "tsx src/index.ts --agent=claude",
"extract:codex": "tsx src/index.ts --agent=codex",
"extract:amp": "tsx src/index.ts --agent=amp"
},
"dependencies": {
"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",
"private": true,
"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 AgentModeInfo = components["schemas"]["AgentModeInfo"];

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,286 @@
/* 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 {
"/v1/agents": {
get: {
get: operations["list_agents"];
};
"/v1/agents/{agent}/install": {
post: operations["install_agent"];
};
"/v1/agents/{agent}/modes": {
get: operations["get_agent_modes"];
};
"/v1/sessions/{session_id}": {
post: operations["create_session"];
};
"/v1/sessions/{session_id}/events": {
get: operations["get_events"];
};
"/v1/sessions/{session_id}/events/sse": {
get: operations["get_events_sse"];
};
"/v1/sessions/{session_id}/messages": {
post: operations["post_message"];
};
"/v1/sessions/{session_id}/permissions/{permission_id}/reply": {
post: operations["reply_permission"];
};
"/v1/sessions/{session_id}/questions/{question_id}/reject": {
post: operations["reject_question"];
};
"/v1/sessions/{session_id}/questions/{question_id}/reply": {
post: operations["reply_question"];
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
AgentError: {
agent?: string | null;
details?: unknown;
message: string;
session_id?: string | null;
type: components["schemas"]["ErrorType"];
};
AgentInfo: {
id: string;
installed: boolean;
path?: string | null;
version?: string | null;
};
AgentInstallRequest: {
reinstall?: boolean | null;
};
AgentListResponse: {
agents: components["schemas"]["AgentInfo"][];
};
AgentModeInfo: {
description: string;
id: string;
name: string;
};
AgentModesResponse: {
modes: components["schemas"]["AgentModeInfo"][];
};
AttachmentSource: {
path: string;
/** @enum {string} */
type: "path";
} | {
/** @enum {string} */
type: "url";
url: string;
} | ({
data: string;
encoding?: string | null;
/** @enum {string} */
type: "data";
});
CrashInfo: {
details?: unknown;
kind?: string | null;
message: string;
};
CreateSessionRequest: {
agent: string;
agentMode?: string | null;
agentVersion?: string | null;
model?: string | null;
permissionMode?: string | null;
variant?: string | null;
};
CreateSessionResponse: {
agentSessionId?: string | null;
error?: components["schemas"]["AgentError"] | null;
healthy: boolean;
};
/** @enum {string} */
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";
EventsQuery: {
/** Format: int64 */
limit?: number | null;
/** Format: int64 */
offset?: number | null;
};
EventsResponse: {
events: components["schemas"]["UniversalEvent"][];
hasMore: boolean;
};
MessageRequest: {
message: string;
};
/** @enum {string} */
PermissionReply: "once" | "always" | "reject";
PermissionReplyRequest: {
reply: components["schemas"]["PermissionReply"];
};
PermissionRequest: {
always: string[];
id: string;
metadata?: {
[key: string]: unknown;
};
patterns: string[];
permission: string;
sessionId: string;
tool?: components["schemas"]["PermissionToolRef"] | null;
};
PermissionToolRef: {
callId: string;
messageId: string;
};
ProblemDetails: {
detail?: string | null;
instance?: string | null;
/** Format: int32 */
status: number;
title: string;
type: string;
[key: string]: unknown;
};
QuestionInfo: {
custom?: boolean | null;
header?: string | null;
multiSelect?: boolean | null;
options: components["schemas"]["QuestionOption"][];
question: string;
};
QuestionOption: {
description?: string | null;
label: string;
};
QuestionReplyRequest: {
answers: string[][];
};
QuestionRequest: {
id: string;
questions: components["schemas"]["QuestionInfo"][];
sessionId: string;
tool?: components["schemas"]["QuestionToolRef"] | null;
};
QuestionToolRef: {
callId: string;
messageId: string;
};
Started: {
details?: unknown;
message?: string | null;
};
UniversalEvent: {
agent: string;
agentSessionId?: string | null;
data: components["schemas"]["UniversalEventData"];
/** Format: int64 */
id: number;
sessionId: string;
timestamp: string;
};
UniversalEventData: {
message: components["schemas"]["UniversalMessage"];
} | {
started: components["schemas"]["Started"];
} | {
error: components["schemas"]["CrashInfo"];
} | {
questionAsked: components["schemas"]["QuestionRequest"];
} | {
permissionAsked: components["schemas"]["PermissionRequest"];
} | {
raw: unknown;
};
UniversalMessage: OneOf<[components["schemas"]["UniversalMessageParsed"], {
error?: string | null;
raw: unknown;
}]>;
UniversalMessageParsed: {
id?: string | null;
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";
};
};
responses: never;
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
}
export type $defs = Record<string, never>;
export type external = Record<string, never>;
export interface operations {
list_agents: {
responses: {
200: {
content: {
@ -12,11 +289,10 @@ export interface paths {
};
};
};
};
"/v1/agents/{agent}/install": {
post: {
install_agent: {
parameters: {
path: {
/** @description Agent id */
agent: string;
};
};
@ -26,8 +302,9 @@ export interface paths {
};
};
responses: {
/** @description Agent installed */
204: {
description: string;
content: never;
};
400: {
content: {
@ -46,11 +323,10 @@ export interface paths {
};
};
};
};
"/v1/agents/{agent}/modes": {
get: {
get_agent_modes: {
parameters: {
path: {
/** @description Agent id */
agent: string;
};
};
@ -67,11 +343,10 @@ export interface paths {
};
};
};
};
"/v1/sessions/{session_id}": {
post: {
create_session: {
parameters: {
path: {
/** @description Client session id */
session_id: string;
};
};
@ -98,41 +373,18 @@ export interface paths {
};
};
};
};
"/v1/sessions/{session_id}/messages": {
post: {
get_events: {
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": {
get: {
parameters: {
path: {
session_id: string;
};
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: {
@ -147,40 +399,40 @@ export interface paths {
};
};
};
};
"/v1/sessions/{session_id}/events/sse": {
get: {
get_events_sse: {
parameters: {
path: {
session_id: string;
};
query?: {
/** @description Last seen event id (exclusive) */
offset?: number | null;
};
path: {
/** @description Session id */
session_id: string;
};
};
responses: {
/** @description SSE event stream */
200: {
description: string;
content: never;
};
};
};
};
"/v1/sessions/{session_id}/questions/{question_id}/reply": {
post: {
post_message: {
parameters: {
path: {
/** @description Session id */
session_id: string;
question_id: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["QuestionReplyRequest"];
"application/json": components["schemas"]["MessageRequest"];
};
};
responses: {
/** @description Message accepted */
204: {
description: string;
content: never;
};
404: {
content: {
@ -189,32 +441,12 @@ export interface paths {
};
};
};
};
"/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": {
post: {
reply_permission: {
parameters: {
path: {
/** @description Session id */
session_id: string;
/** @description Permission id */
permission_id: string;
};
};
@ -224,8 +456,9 @@ export interface paths {
};
};
responses: {
/** @description Permission reply accepted */
204: {
description: string;
content: never;
};
404: {
content: {
@ -234,237 +467,51 @@ export interface paths {
};
};
};
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"];
};
};
};
};
}
export interface components {
schemas: {
AgentError: {
type: components["schemas"]["ErrorType"];
message: string;
agent?: string | null;
session_id?: string | null;
details?: unknown | null;
};
AgentInfo: {
id: string;
installed: boolean;
path?: string | null;
version?: string | null;
};
AgentInstallRequest: {
reinstall?: boolean | null;
};
AgentListResponse: {
agents: components["schemas"]["AgentInfo"][];
};
AgentModeInfo: {
id: string;
name: string;
description: string;
};
AgentModesResponse: {
modes: components["schemas"]["AgentModeInfo"][];
};
AttachmentSource:
| {
type: "path";
path: string;
}
| {
type: "url";
url: string;
}
| {
type: "data";
data: string;
encoding?: string | null;
};
CrashInfo: {
message: string;
kind?: string | null;
details?: unknown | null;
};
CreateSessionRequest: {
agent: string;
agentMode?: string | null;
agentVersion?: string | null;
model?: string | null;
permissionMode?: string | null;
token?: string | null;
validateToken?: boolean | null;
variant?: string | null;
};
CreateSessionResponse: {
healthy: boolean;
error?: components["schemas"]["AgentError"] | null;
agentSessionId?: string | null;
};
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";
EventsQuery: {
offset?: number | null;
limit?: number | null;
};
EventsResponse: {
events: components["schemas"]["UniversalEvent"][];
hasMore: boolean;
};
MessageRequest: {
message: string;
};
PermissionReply: "once" | "always" | "reject";
PermissionReplyRequest: {
reply: components["schemas"]["PermissionReply"];
};
PermissionRequest: {
id: string;
sessionId: string;
permission: string;
patterns: string[];
always: string[];
metadata?: Record<string, unknown>;
tool?: components["schemas"]["PermissionToolRef"] | null;
};
PermissionToolRef: {
messageId: string;
callId: string;
};
ProblemDetails: {
type: string;
title: string;
status: number;
detail?: string | null;
instance?: string | null;
[key: string]: unknown;
};
QuestionInfo: {
question: string;
options: components["schemas"]["QuestionOption"][];
header?: string | null;
multiSelect?: boolean | null;
custom?: boolean | null;
};
QuestionOption: {
label: string;
description?: string | null;
};
QuestionReplyRequest: {
answers: string[][];
};
QuestionRequest: {
id: string;
sessionId: string;
questions: components["schemas"]["QuestionInfo"][];
tool?: components["schemas"]["QuestionToolRef"] | null;
};
QuestionToolRef: {
messageId: string;
callId: string;
};
Started: {
message?: string | null;
details?: unknown | null;
};
UniversalEvent: {
id: number;
timestamp: string;
sessionId: string;
agent: string;
agentSessionId?: string | null;
data: components["schemas"]["UniversalEventData"];
};
UniversalEventData:
| { message: components["schemas"]["UniversalMessage"] }
| { started: components["schemas"]["Started"] }
| { error: components["schemas"]["CrashInfo"] }
| { questionAsked: components["schemas"]["QuestionRequest"] }
| { permissionAsked: components["schemas"]["PermissionRequest"] }
| { raw: unknown };
UniversalMessage:
| components["schemas"]["UniversalMessageParsed"]
| {
raw: unknown;
error?: string | null;
};
UniversalMessageParsed: {
role: string;
parts: components["schemas"]["UniversalMessagePart"][];
id?: string | null;
metadata?: Record<string, 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;
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
}
export type webhooks = never;

View file

@ -1,4 +1,4 @@
export { SandboxDaemonClient, SandboxDaemonError, createSandboxDaemonClient } from "./client";
export { SandboxDaemonClient, SandboxDaemonError, createSandboxDaemonClient } from "./client.js";
export type {
AgentInfo,
AgentInstallRequest,
@ -15,5 +15,5 @@ export type {
ProblemDetails,
QuestionReplyRequest,
UniversalEvent,
} from "./client";
export type { components, paths } from "./generated/openapi";
} from "./client.js";
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
}
- 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 = what the agent DOES (behavior, system prompt)

View file

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