@deus-hq/sdk
v0.9.1
Published
Deus SDK — run AI coding agents in isolated sandboxes
Readme
@deus-hq/sdk
TypeScript SDK for running AI coding agents (Claude) inside isolated cloud sandboxes.
Install
npm install @deus-hq/sdkEnvironment variables
| Variable | Required | Description |
|----------|----------|-------------|
| DEUS_API_KEY | Yes | Your Deus API key (starts with deus_sk_) |
| ANTHROPIC_API_KEY | Yes* | Anthropic API key for the agent (*unless org-level key configured) |
| DEUS_BASE_URL | No | API base URL (defaults to https://api.hivenet.app) |
All can also be passed directly to configure() or as options on individual function calls.
Quick start
Level 1: run() — fire-and-forget
One async call. Provisions a sandbox, clones a repo, runs the agent, returns the result.
import { configure, Image, run } from "@deus-hq/sdk";
configure({
apiKey: process.env.DEUS_API_KEY,
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
});
const img = Image.from("node-20")
.repo("acme/backend"); // Also accepts github.com/acme/backend, full URLs, or SSH
const result = await run({
task: "Add a health-check endpoint at GET /healthz",
image: img,
});
console.log(result.output); // Agent's final response
console.log(`$${result.cost.usd}`); // Cost in USD
console.log(result.filesChanged); // Files modifiedRunResult
interface RunResult {
id: string; // Session ID
workspaceId: string; // Workspace ID
output: string; // Agent's final text output
cost: Cost; // { inputTokens, outputTokens, usd }
turns: number; // Number of agent turns
duration: number; // Wall-clock time in milliseconds
filesChanged: string[]; // Files the agent modified
}Level 2: stream() — real-time events
Pull-based async iteration over agent events. Supports multi-turn conversations.
import { configure, Image, createWorkspace, createSession, stream } from "@deus-hq/sdk";
configure({
apiKey: process.env.DEUS_API_KEY,
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
});
const img = Image.from("node-20").repo("acme/backend");
const handle = await createWorkspace({ image: img });
const session = await createSession({ workspaceId: handle.id });
for await (const event of stream(session, "Fix the failing tests")) {
switch (event.type) {
case "message.delta":
process.stdout.write(event.delta);
break;
case "message.complete":
console.log();
break;
case "tool.start":
console.log(`-> ${event.toolName}(...)`);
break;
case "tool.complete":
console.log(`<- ${event.toolName}: ${event.isError ? "error" : "ok"}`);
break;
case "turn.started":
console.log(`\n--- turn ${event.turnIndex} ---`);
break;
case "turn.ended":
console.log(`Turn ${event.turnIndex} — $${event.cost.usd}`);
break;
case "session.complete":
console.log(`Done — ${event.turns} turns, $${event.totalCost.usd}`);
break;
case "session.error":
console.error(event.error.message);
break;
}
}
// Send a follow-up message on the same session
for await (const event of stream(session, "Now add integration tests")) {
// ...
}EventStream helpers
stream() returns an EventStream with additional methods:
const events = stream(session, "Add tests");
// Collapse to RunResult (consumes the stream)
const result = await events.result();
// Convert to Web ReadableStream (for SSE endpoints)
const readable = events.toReadableStream();
// Cancel the agent
events.abort();Level 3: createSessionClient() — reactive UI
Push-based WebSocket client for browser frontends. Maintains full session state with subscribe/notify.
import { createSessionClient, createSessionToken } from "@deus-hq/sdk";
// Server-side: create a short-lived token (don't expose your API key to browsers)
const { token } = await createSessionToken(sessionId);
// Client-side: connect via WebSocket
const client = createSessionClient({
sessionId,
token,
url: "https://api.deus.co",
});
client.subscribe((state) => {
console.log(`Status: ${state.status}`);
console.log(`Text: ${state.turn?.text}`);
if (state.pendingQuestion) {
client.answerQuestion(
state.pendingQuestion.questionId,
state.pendingQuestion.sessionId,
["yes"],
);
}
if (state.pendingPermission) {
client.respondToPermission(
state.pendingPermission.requestId,
state.pendingPermission.sessionId,
{ behavior: "allow" },
);
}
});
client.connect();
client.send("Fix the bug");SessionState
interface SessionState {
connected: boolean;
status: "connecting" | "provisioning" | "ready" | "running" | "error";
history: Message[];
completedTurns: TurnState[];
turn: TurnState | null;
pendingQuestion: PendingQuestion | null;
pendingPermission: PendingPermission | null;
pendingHook: PendingHook | null;
error: string | null;
}Image configuration
Image.from() defines the sandbox VM: what template to use, which repo to clone, packages to install, and setup commands to run during provisioning.
const img = Image.from("node-20")
.repo("github.com/acme/backend", "main") // repo + optional branch
.packages(["postgresql-client"])
.setup(["npm install", "npm run db:migrate"])
.env({ DATABASE_URL: "postgres://localhost:5432/test" })
.timeout(600_000);
const result = await run({ task: "Run the test suite and fix failures", image: img });Setup phases
Setup commands run in two phases: pre-clone (before the repo is cloned) and post-clone (after — the default). Multiple .setup() calls accumulate in order.
const img = Image.from("node-20")
.repo("acme/backend")
.setup("pre-clone", ["setup-credentials.sh"]) // runs before git clone
.setup(["npm install"]) // post-clone (default)
.setup(["npm run db:migrate"]); // runs after npm installParallel setup
Use .setupParallel() to run independent branches concurrently. Each branch runs its commands sequentially; branches execute in parallel. The next call waits for all branches to finish.
const img = Image.from("node-20")
.repo("acme/backend")
.setupParallel(
["npm install"], // branch 1
["pip install -r requirements.txt"], // branch 2 (runs concurrently)
)
.setup(["npm run db:migrate"]); // waits for both branches
// With a phase
Image.from("node-20")
.setupParallel("pre-clone",
["setup-aws.sh"],
["setup-gcp.sh"],
);Image builder reference
| Method | Description |
|--------|-------------|
| Image.from(template) | Create from a template (e.g. "node-20") |
| .repo(url, branch?) | Repository to clone, with optional branch |
| .packages(list) | System packages to install via apt-get |
| .setup(commands) | Sequential setup commands (post-clone) |
| .setup(phase, commands) | Sequential setup commands in a specific phase |
| .setupParallel(...branches) | Parallel branches (post-clone) |
| .setupParallel(phase, ...branches) | Parallel branches in a specific phase |
| .env(vars) | Environment variables for setup + agent |
| .secrets(secrets) | Third-party API secrets (e.g. github_token) |
| .timeout(ms) | Sandbox timeout (30s–24h, default 1h) |
| .metadata(data) | Arbitrary key-value metadata |
MCP servers
Attach MCP tool servers to the agent:
const img = Image.from("node-20").repo("github.com/acme/backend");
const result = await run({
task: "Look up the latest issues and fix the top one",
image: img,
mcpServers: {
github: {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-github"],
env: { GITHUB_TOKEN: "ghp_..." },
},
},
});Hooks
Intercept agent lifecycle events. Decision hooks (PreToolUse, Stop) block the agent until you respond:
const img = Image.from("node-20").repo("github.com/acme/backend");
const result = await run({
task: "Refactor the auth module",
image: img,
hooks: {
PreToolUse: (input) => {
if (input.tool_name === "Bash" && input.tool_input?.command?.includes("rm")) {
return { allow: false, reason: "Destructive commands are not allowed" };
}
return { allow: true };
},
Stop: (input) => {
return { allow: true }; // Let the agent stop
},
},
});Error handling
import { run, isDeusError } from "@deus-hq/sdk";
try {
const img = Image.from("node-20").repo("github.com/acme/backend");
await run({ task: "Fix tests", image: img });
} catch (err) {
if (isDeusError(err)) {
console.error(`[${err.code}] ${err.message}`);
// err.code: "MISSING_API_KEY" | "API_ERROR" | "TIMEOUT" | "ABORTED"
// err.statusCode: HTTP status (if from API)
}
}During streaming, errors arrive as events:
for await (const event of stream(session, "Fix tests")) {
if (event.type === "session.error") {
console.error(event.error.message);
if (!event.recoverable) break;
}
}Workspace management
import {
Image,
createWorkspace,
listWorkspaces,
stopWorkspace,
deleteWorkspace,
} from "@deus-hq/sdk";
const img = Image.from("node-20").repo("github.com/acme/backend");
const handle = await createWorkspace({ image: img });
const { items } = await listWorkspaces({ status: "active", limit: 10 });
await stopWorkspace(handle.id); // Pause the sandbox
await deleteWorkspace(handle.id); // Permanently removeLicense
MIT
