chore: sync workspace changes

This commit is contained in:
Nathan Flurry 2026-01-28 01:13:50 -08:00
parent 8a91b8e9aa
commit 6aa591bd91
13 changed files with 237 additions and 26 deletions

View file

@ -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

View file

@ -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:

View file

@ -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",

View file

@ -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<string, string> = {};
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();

View file

@ -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<string, string> = {};
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,
});

View file

@ -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"]
}

View file

@ -4,5 +4,12 @@
"type": "module",
"exports": {
".": "./src/sandbox-agent-client.ts"
},
"scripts": {
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@types/node": "latest",
"typescript": "latest"
}
}

View file

@ -21,20 +21,33 @@ const INSPECTOR_URL = "https://inspect.sandboxagent.dev";
export function buildInspectorUrl({
baseUrl,
token,
headers,
}: {
baseUrl: string;
token?: string;
headers?: Record<string, string>;
}): 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<string, string>;
}): void {
console.log(`Inspector: ${buildInspectorUrl({ baseUrl, token, headers })}`);
}
type HeaderOptions = {

View file

@ -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"]
}

View file

@ -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<string, string> };
}
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<string, string> = {};
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<string | null>(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;

9
pnpm-lock.yaml generated
View file

@ -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:

View file

@ -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,

View file

@ -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<string, string>;
}
/**
* 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()}`;
}