// Anthropic Managed Agents (beta) — create agent -> create environment -> create session -> // send user.message -> stream SSE events (raw fetch) -> clean up everything created. // // STATUS: LIVE_VERIFIED 2026-09-18 — the identical HTTP sequence (env → agent → session with string-cents budget → user.message → SSE stream → cleanup) ran via scripts/live.py (tmp-live/platform-anthropic/agents-probes-2.json); this file itself was not executed. // Model: claude-haiku-4-5-20251001 // Beta header: anthropic-beta: managed-agents-2026-04-01 // Budget: max budget minimal — session budget capped at 5 US cents, prompt "Reply with OK.", no tools. // Names are prefixed "atlas-platform-agent". // // Run: node --env-file=.env --experimental-strip-types examples/anthropic/agents/create_agent_session_stream.ts // Uses process.env.ANTHROPIC_API_KEY only; never prints it. // // Docs: https://platform.claude.com/docs/en/managed-agents/quickstart // https://platform.claude.com/docs/en/managed-agents/events-and-streaming const API_KEY = process.env.ANTHROPIC_API_KEY; if (!API_KEY) throw new Error("ANTHROPIC_API_KEY missing (node --env-file=.env ...)"); const BASE = process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com"; const BETA = "managed-agents-2026-04-01"; const MODEL = "claude-haiku-4-5-20251001"; const TS = Date.now(); const headers: Record = { "x-api-key": API_KEY, "anthropic-version": "2023-06-01", "anthropic-beta": BETA, "content-type": "application/json", }; async function ant(method: string, path: string, body?: unknown): Promise { const res = await fetch(BASE + path, { method, headers, body: body === undefined ? undefined : JSON.stringify(body) }); const text = await res.text(); if (!res.ok) throw new Error(`${method} ${path} -> ${res.status}: ${text.slice(0, 500)}`); return text ? (JSON.parse(text) as T) : (undefined as T); } let agentId = "", envId = "", sessionId = ""; const abort = new AbortController(); async function cleanup() { abort.abort(); try { if (sessionId) { let s = await ant("GET", `/v1/sessions/${sessionId}`); if (s.status === "running") { await ant("POST", `/v1/sessions/${sessionId}/events`, { events: [{ type: "user.interrupt" }] }); for (let i = 0; i < 30 && s.status === "running"; i++) { await new Promise((r) => setTimeout(r, 2000)); s = await ant("GET", `/v1/sessions/${sessionId}`); } } await ant("DELETE", `/v1/sessions/${sessionId}`); console.log("deleted session", sessionId); } if (agentId) { await ant("POST", `/v1/agents/${agentId}/archive`); console.log("archived agent", agentId); } if (envId) { try { await ant("DELETE", `/v1/environments/${envId}`); console.log("deleted environment", envId); } catch { await ant("POST", `/v1/environments/${envId}/archive`); console.log("archived environment", envId); } } } catch (e) { console.error("cleanup error:", (e as Error).message); } } async function main() { // 1) Agent (no tools => nothing executes in the sandbox) const agent = await ant("POST", "/v1/agents", { name: `atlas-platform-agent-${TS}`, model: MODEL, system: "You are a terse test agent. Reply with OK.", metadata: { origin: "api-atlas" }, }); agentId = agent.id; console.log("agent:", agentId, "v" + agent.version); // 2) Environment (cloud sandbox, limited egress) const env = await ant("POST", "/v1/environments", { name: `atlas-platform-agent-env-${TS}`, config: { type: "cloud", networking: { type: "limited", allowed_hosts: ["example.com"] } }, }); envId = env.id; console.log("environment:", envId); // 3) Session with a 5-cent hard budget const session = await ant("POST", "/v1/sessions", { agent: agentId, environment_id: envId, title: `atlas-platform-agent-session-${TS}`, budget: { type: "limit", max_list_cost: { amount: "5", currency: "USD" } }, }); sessionId = session.id; console.log("session:", sessionId, "status=" + session.status); // 4) Open the stream before sending the message const streamRes = await fetch(`${BASE}/v1/sessions/${sessionId}/events/stream`, { headers: { ...headers, accept: "text/event-stream" }, signal: abort.signal, }); if (!streamRes.ok || !streamRes.body) throw new Error(`stream -> ${streamRes.status}`); // 5) Send user.message await ant("POST", `/v1/sessions/${sessionId}/events`, { events: [{ type: "user.message", content: [{ type: "text", text: "Reply with OK." }] }], }); // 6) Parse SSE ("data: {...}" lines) until session.status_idle / session.error / timeout const reader = streamRes.body.getReader(); const dec = new TextDecoder(); let buf = ""; const timer = setTimeout(() => abort.abort(), 180_000); const seen: string[] = []; outer: while (true) { const { value, done } = await reader.read().catch(() => ({ value: undefined, done: true })); if (done) break; buf += dec.decode(value, { stream: true }); let idx: number; while ((idx = buf.indexOf("\n")) >= 0) { const line = buf.slice(0, idx).trimEnd(); buf = buf.slice(idx + 1); if (!line.startsWith("data:")) continue; const ev = JSON.parse(line.slice(5).trim()); seen.push(ev.type); if (ev.type === "agent.message") { console.log("[agent.message]", ev.content.filter((c: any) => c.type === "text").map((c: any) => c.text).join("")); } else if (ev.type === "session.usage") { console.log("[session.usage] list_cost=", ev.usage?.list_cost?.amount, "active_seconds=", ev.usage?.active_seconds); } else if (ev.type === "session.status_idle") { console.log("[session.status_idle] stop_reason=", ev.stop_reason?.type); break outer; } else if (ev.type === "session.error") { console.log("[session.error]", ev.error?.type, ev.error?.retry_status?.type); break outer; } else { console.log(`[${ev.type}]`); } } } clearTimeout(timer); console.log("event sequence:", seen.join(" -> ")); // 7) Final snapshot const fin = await ant("GET", `/v1/sessions/${sessionId}`); console.log(JSON.stringify({ status: fin.status, usage: fin.usage }, null, 2)); } main().catch((e) => { console.error("error:", (e as Error).message); process.exitCode = 1; }).finally(cleanup);