From 6aa591bd915752de8d820bf1efb311cdb06a41b0 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Wed, 28 Jan 2026 01:13:50 -0800 Subject: [PATCH] chore: sync workspace changes --- CLAUDE.md | 24 +++---- docs/sdks/typescript.mdx | 21 ++++++ examples/daytona/package.json | 4 +- examples/daytona/src/daytona-fallback.ts | 78 +++++++++++++++++++++ examples/daytona/src/daytona.ts | 19 +++-- examples/daytona/tsconfig.json | 16 +++++ examples/shared/package.json | 7 ++ examples/shared/src/sandbox-agent-client.ts | 17 ++++- examples/shared/tsconfig.json | 16 +++++ frontend/packages/inspector/src/App.tsx | 18 ++++- pnpm-lock.yaml | 9 ++- sdks/typescript/src/index.ts | 2 + sdks/typescript/src/inspector.ts | 32 +++++++++ 13 files changed, 237 insertions(+), 26 deletions(-) create mode 100644 examples/daytona/src/daytona-fallback.ts create mode 100644 examples/daytona/tsconfig.json create mode 100644 examples/shared/tsconfig.json create mode 100644 sdks/typescript/src/inspector.ts diff --git a/CLAUDE.md b/CLAUDE.md index 21d2ceb..c747930 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -49,18 +49,18 @@ Universal schema guidance: ### CLI ⇄ HTTP endpoint map (keep in sync) -- `sandbox-agent agents list` ↔ `GET /v1/agents` -- `sandbox-agent agents install` ↔ `POST /v1/agents/{agent}/install` -- `sandbox-agent agents modes` ↔ `GET /v1/agents/{agent}/modes` -- `sandbox-agent sessions list` ↔ `GET /v1/sessions` -- `sandbox-agent sessions create` ↔ `POST /v1/sessions/{sessionId}` -- `sandbox-agent sessions send-message` ↔ `POST /v1/sessions/{sessionId}/messages` -- `sandbox-agent sessions send-message-stream` ↔ `POST /v1/sessions/{sessionId}/messages/stream` -- `sandbox-agent sessions events` / `get-messages` ↔ `GET /v1/sessions/{sessionId}/events` -- `sandbox-agent sessions events-sse` ↔ `GET /v1/sessions/{sessionId}/events/sse` -- `sandbox-agent sessions reply-question` ↔ `POST /v1/sessions/{sessionId}/questions/{questionId}/reply` -- `sandbox-agent sessions reject-question` ↔ `POST /v1/sessions/{sessionId}/questions/{questionId}/reject` -- `sandbox-agent sessions reply-permission` ↔ `POST /v1/sessions/{sessionId}/permissions/{permissionId}/reply` +- `sandbox-agent api agents list` ↔ `GET /v1/agents` +- `sandbox-agent api agents install` ↔ `POST /v1/agents/{agent}/install` +- `sandbox-agent api agents modes` ↔ `GET /v1/agents/{agent}/modes` +- `sandbox-agent api sessions list` ↔ `GET /v1/sessions` +- `sandbox-agent api sessions create` ↔ `POST /v1/sessions/{sessionId}` +- `sandbox-agent api sessions send-message` ↔ `POST /v1/sessions/{sessionId}/messages` +- `sandbox-agent api sessions send-message-stream` ↔ `POST /v1/sessions/{sessionId}/messages/stream` +- `sandbox-agent api sessions events` / `get-messages` ↔ `GET /v1/sessions/{sessionId}/events` +- `sandbox-agent api sessions events-sse` ↔ `GET /v1/sessions/{sessionId}/events/sse` +- `sandbox-agent api sessions reply-question` ↔ `POST /v1/sessions/{sessionId}/questions/{questionId}/reply` +- `sandbox-agent api sessions reject-question` ↔ `POST /v1/sessions/{sessionId}/questions/{questionId}/reject` +- `sandbox-agent api sessions reply-permission` ↔ `POST /v1/sessions/{sessionId}/permissions/{permissionId}/reply` ## Git Commits diff --git a/docs/sdks/typescript.mdx b/docs/sdks/typescript.mdx index 82472ff..7c5974d 100644 --- a/docs/sdks/typescript.mdx +++ b/docs/sdks/typescript.mdx @@ -118,6 +118,27 @@ try { } ``` +## Inspector URL + +Build a URL to open the sandbox-agent Inspector UI with pre-filled connection settings: + +```ts +import { buildInspectorUrl } from "sandbox-agent"; + +const url = buildInspectorUrl({ + baseUrl: "https://your-sandbox-agent.example.com", + token: "optional-bearer-token", + headers: { "X-Custom-Header": "value" }, +}); +console.log(url); +// https://inspect.sandboxagent.dev?url=https%3A%2F%2Fyour-sandbox-agent.example.com&token=...&headers=... +``` + +Parameters: +- `baseUrl` (required): The sandbox-agent server URL +- `token` (optional): Bearer token for authentication +- `headers` (optional): Extra headers to pass to the server (JSON-encoded in the URL) + ## Types The SDK exports OpenAPI-derived types for events, items, and capabilities: diff --git a/examples/daytona/package.json b/examples/daytona/package.json index ea9f37a..6ba5ebd 100644 --- a/examples/daytona/package.json +++ b/examples/daytona/package.json @@ -3,7 +3,9 @@ "private": true, "type": "module", "scripts": { - "start": "tsx src/daytona.ts" + "start": "tsx src/daytona-fallback.ts", + "start:cli": "tsx src/daytona.ts", + "typecheck": "tsc --noEmit" }, "dependencies": { "@daytonaio/sdk": "latest", diff --git a/examples/daytona/src/daytona-fallback.ts b/examples/daytona/src/daytona-fallback.ts new file mode 100644 index 0000000..b2a1d98 --- /dev/null +++ b/examples/daytona/src/daytona-fallback.ts @@ -0,0 +1,78 @@ +import { Daytona, Image } from "@daytonaio/sdk"; +import { logInspectorUrl, runPrompt } from "@sandbox-agent/example-shared"; + +if ( + !process.env.DAYTONA_API_KEY || + (!process.env.OPENAI_API_KEY && !process.env.ANTHROPIC_API_KEY) +) { + throw new Error( + "DAYTONA_API_KEY and (OPENAI_API_KEY or ANTHROPIC_API_KEY) required", + ); +} + +const SNAPSHOT = "sandbox-agent-ready"; +const BINARY = "/usr/local/bin/sandbox-agent"; +const AGENT_BIN_DIR = "/root/.local/share/sandbox-agent/bin"; + +const daytona = new Daytona(); + +const hasSnapshot = await daytona.snapshot.get(SNAPSHOT).then( + () => true, + () => false, +); +if (!hasSnapshot) { + console.log(`Creating snapshot '${SNAPSHOT}' (one-time setup, ~2-3min)...`); + await daytona.snapshot.create( + { + name: SNAPSHOT, + image: Image.base("ubuntu:22.04").runCommands( + // Install dependencies + "apt-get update && apt-get install -y curl ca-certificates", + // Download sandbox-agent + `curl -fsSL -o ${BINARY} https://releases.rivet.dev/sandbox-agent/latest/binaries/sandbox-agent-x86_64-unknown-linux-musl && chmod +x ${BINARY}`, + // Create agent bin directory + `mkdir -p ${AGENT_BIN_DIR}`, + // Install Claude: get latest version, download binary + `CLAUDE_VERSION=$(curl -fsSL https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/latest) && ` + + `curl -fsSL -o ${AGENT_BIN_DIR}/claude "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/$CLAUDE_VERSION/linux-x64/claude" && ` + + `chmod +x ${AGENT_BIN_DIR}/claude`, + // Install Codex: download tarball, extract binary + `curl -fsSL -L https://github.com/openai/codex/releases/latest/download/codex-x86_64-unknown-linux-musl.tar.gz | tar -xzf - -C /tmp && ` + + `find /tmp -name 'codex-x86_64-unknown-linux-musl' -exec mv {} ${AGENT_BIN_DIR}/codex \\; && ` + + `chmod +x ${AGENT_BIN_DIR}/codex`, + ), + }, + { onLogs: (log) => console.log(` ${log}`) }, + ); + console.log("Snapshot created. Future runs will be instant."); +} + +console.log("Creating sandbox..."); +const envVars: Record = {}; +if (process.env.ANTHROPIC_API_KEY) envVars.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; +if (process.env.OPENAI_API_KEY) envVars.OPENAI_API_KEY = process.env.OPENAI_API_KEY; + +const sandbox = await daytona.create({ + snapshot: SNAPSHOT, + envVars, + autoStopInterval: 0, +}); + +console.log("Starting server..."); +await sandbox.process.executeCommand( + `nohup ${BINARY} server --no-token --host 0.0.0.0 --port 3000 >/tmp/sandbox-agent.log 2>&1 &`, +); + +const baseUrl = (await sandbox.getSignedPreviewUrl(3000, 4 * 60 * 60)).url; +logInspectorUrl({ baseUrl }); + +const cleanup = async () => { + console.log("Cleaning up..."); + await sandbox.delete(60); + process.exit(0); +}; +process.once("SIGINT", cleanup); +process.once("SIGTERM", cleanup); + +await runPrompt({ baseUrl }); +await cleanup(); diff --git a/examples/daytona/src/daytona.ts b/examples/daytona/src/daytona.ts index 9e5ed36..a894576 100644 --- a/examples/daytona/src/daytona.ts +++ b/examples/daytona/src/daytona.ts @@ -20,14 +20,18 @@ const hasSnapshot = await daytona.snapshot.get(SNAPSHOT).then( () => false, ); if (!hasSnapshot) { - console.log(`Creating snapshot '${SNAPSHOT}' (one-time setup, ~1-2min)...`); + console.log(`Creating snapshot '${SNAPSHOT}' (one-time setup, ~2-3min)...`); await daytona.snapshot.create( { name: SNAPSHOT, image: Image.base("ubuntu:22.04").runCommands( + // Install dependencies "apt-get update && apt-get install -y curl ca-certificates", - `curl -fsSL -o ${BINARY} https://releases.rivet.dev/sandbox-agent/latest/binaries/sandbox-agent-x86_64-unknown-linux-musl`, - `chmod +x ${BINARY}`, + // Download sandbox-agent + `curl -fsSL -o ${BINARY} https://releases.rivet.dev/sandbox-agent/latest/binaries/sandbox-agent-x86_64-unknown-linux-musl && chmod +x ${BINARY}`, + // Pre-install agents using sandbox-agent CLI + `${BINARY} install-agent claude`, + `${BINARY} install-agent codex`, ), }, { onLogs: (log) => console.log(` ${log}`) }, @@ -36,12 +40,13 @@ if (!hasSnapshot) { } console.log("Creating sandbox..."); +const envVars: Record = {}; +if (process.env.ANTHROPIC_API_KEY) envVars.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; +if (process.env.OPENAI_API_KEY) envVars.OPENAI_API_KEY = process.env.OPENAI_API_KEY; + const sandbox = await daytona.create({ snapshot: SNAPSHOT, - envVars: { - ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY, - OPENAI_API_KEY: process.env.OPENAI_API_KEY, - }, + envVars, autoStopInterval: 0, }); diff --git a/examples/daytona/tsconfig.json b/examples/daytona/tsconfig.json new file mode 100644 index 0000000..96ba2fd --- /dev/null +++ b/examples/daytona/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "module": "ESNext", + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "noEmit": true, + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "**/*.test.ts"] +} diff --git a/examples/shared/package.json b/examples/shared/package.json index 30a9ff5..c9870a6 100644 --- a/examples/shared/package.json +++ b/examples/shared/package.json @@ -4,5 +4,12 @@ "type": "module", "exports": { ".": "./src/sandbox-agent-client.ts" + }, + "scripts": { + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@types/node": "latest", + "typescript": "latest" } } diff --git a/examples/shared/src/sandbox-agent-client.ts b/examples/shared/src/sandbox-agent-client.ts index 2fad2a0..ddd3c40 100644 --- a/examples/shared/src/sandbox-agent-client.ts +++ b/examples/shared/src/sandbox-agent-client.ts @@ -21,20 +21,33 @@ const INSPECTOR_URL = "https://inspect.sandboxagent.dev"; export function buildInspectorUrl({ baseUrl, token, + headers, }: { baseUrl: string; token?: string; + headers?: Record; }): string { const normalized = normalizeBaseUrl(ensureUrl(baseUrl)); const params = new URLSearchParams({ url: normalized }); if (token) { params.set("token", token); } + if (headers && Object.keys(headers).length > 0) { + params.set("headers", JSON.stringify(headers)); + } return `${INSPECTOR_URL}?${params.toString()}`; } -export function logInspectorUrl({ baseUrl, token }: { baseUrl: string; token?: string }): void { - console.log(`Inspector: ${buildInspectorUrl({ baseUrl, token })}`); +export function logInspectorUrl({ + baseUrl, + token, + headers, +}: { + baseUrl: string; + token?: string; + headers?: Record; +}): void { + console.log(`Inspector: ${buildInspectorUrl({ baseUrl, token, headers })}`); } type HeaderOptions = { diff --git a/examples/shared/tsconfig.json b/examples/shared/tsconfig.json new file mode 100644 index 0000000..2be3197 --- /dev/null +++ b/examples/shared/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "module": "ESNext", + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "noEmit": true, + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} diff --git a/frontend/packages/inspector/src/App.tsx b/frontend/packages/inspector/src/App.tsx index 38d5d0a..52739bc 100644 --- a/frontend/packages/inspector/src/App.tsx +++ b/frontend/packages/inspector/src/App.tsx @@ -48,14 +48,24 @@ const getDefaultEndpoint = () => { const getInitialConnection = () => { if (typeof window === "undefined") { - return { endpoint: "http://127.0.0.1:2468", token: "" }; + return { endpoint: "http://127.0.0.1:2468", token: "", headers: {} as Record }; } const params = new URLSearchParams(window.location.search); const urlParam = params.get("url")?.trim(); const tokenParam = params.get("token") ?? ""; + const headersParam = params.get("headers"); + let headers: Record = {}; + if (headersParam) { + try { + headers = JSON.parse(headersParam); + } catch { + console.warn("Invalid headers query param, ignoring"); + } + } return { endpoint: urlParam && urlParam.length > 0 ? urlParam : getDefaultEndpoint(), - token: tokenParam + token: tokenParam, + headers }; }; @@ -64,6 +74,7 @@ export default function App() { const initialConnectionRef = useRef(getInitialConnection()); const [endpoint, setEndpoint] = useState(initialConnectionRef.current.endpoint); const [token, setToken] = useState(initialConnectionRef.current.token); + const [extraHeaders] = useState(initialConnectionRef.current.headers); const [connected, setConnected] = useState(false); const [connecting, setConnecting] = useState(false); const [connectError, setConnectError] = useState(null); @@ -160,7 +171,8 @@ export default function App() { const client = await SandboxAgent.connect({ baseUrl: endpoint, token: token || undefined, - fetch: fetchWithLog + fetch: fetchWithLog, + headers: Object.keys(extraHeaders).length > 0 ? extraHeaders : undefined }); clientRef.current = client; return client; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb8812d..6d81695 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,7 +72,14 @@ importers: specifier: latest version: 5.9.3 - examples/shared: {} + examples/shared: + devDependencies: + '@types/node': + specifier: latest + version: 25.0.10 + typescript: + specifier: latest + version: 5.9.3 examples/vercel: dependencies: diff --git a/sdks/typescript/src/index.ts b/sdks/typescript/src/index.ts index e734f42..db8b4eb 100644 --- a/sdks/typescript/src/index.ts +++ b/sdks/typescript/src/index.ts @@ -1,4 +1,6 @@ export { SandboxAgent, SandboxAgentError } from "./client.ts"; +export { buildInspectorUrl } from "./inspector.ts"; +export type { InspectorUrlOptions } from "./inspector.ts"; export type { SandboxAgentConnectOptions, SandboxAgentStartOptions, diff --git a/sdks/typescript/src/inspector.ts b/sdks/typescript/src/inspector.ts new file mode 100644 index 0000000..9c448e2 --- /dev/null +++ b/sdks/typescript/src/inspector.ts @@ -0,0 +1,32 @@ +const INSPECTOR_URL = "https://inspect.sandboxagent.dev"; + +export interface InspectorUrlOptions { + /** + * Base URL of the sandbox-agent server. + */ + baseUrl: string; + /** + * Optional bearer token for authentication. + */ + token?: string; + /** + * Optional extra headers to pass to the sandbox-agent server. + * Will be JSON-encoded in the URL. + */ + headers?: Record; +} + +/** + * Builds a URL to the sandbox-agent inspector UI with the given connection parameters. + */ +export function buildInspectorUrl(options: InspectorUrlOptions): string { + const normalized = options.baseUrl.replace(/\/+$/, ""); + const params = new URLSearchParams({ url: normalized }); + if (options.token) { + params.set("token", options.token); + } + if (options.headers && Object.keys(options.headers).length > 0) { + params.set("headers", JSON.stringify(options.headers)); + } + return `${INSPECTOR_URL}?${params.toString()}`; +}