SPB Git forge

spb/doc-api

Public
2commits 1branches 0releases
15.7 MBsize
maindefault branch
14 days agolast push
Python 88.3% TypeScript 7.6% Shell 4.1%
7.3 KB · 150 lines python
Raw Blame History
1#!/usr/bin/env python32"""Anthropic Managed Agents (beta): create agent -> environment -> session -> user.message -> stream SSE -> cleanup.34STATUS: 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.5Model: claude-haiku-4-5-202510016Beta header: anthropic-beta: managed-agents-2026-04-017Budget: max budget minimal -- session budget capped at 5 US cents ({"amount": "5", "currency": "USD"}),8        prompt "Reply with OK.", no tools (nothing executes in the sandbox). Names prefixed "atlas-platform-agent".910Uses scripts.live.anthropic_request (auth injected from .env, every call logged to reports/live-requests.jsonl,11keys masked). SSE is read with stream=True, which returns a line iterator over the HTTP response.12Every resource created here is removed in `finally`: DELETE session (after interrupt if still running),13POST /v1/agents/{id}/archive (agents have no delete endpoint), DELETE /v1/environments/{id} (falls back to archive).1415Run:  .venv/bin/python examples/anthropic/agents/create_agent_session_stream.py16Docs: https://platform.claude.com/docs/en/managed-agents/quickstart17      https://platform.claude.com/docs/en/managed-agents/events-and-streaming18"""19from __future__ import annotations2021import json22import sys23import time24from pathlib import Path2526ROOT = Path(__file__).resolve().parents[3]27sys.path.insert(0, str(ROOT))28from scripts.live import anthropic_request  # noqa: E4022930BETA = "managed-agents-2026-04-01"31MODEL = "claude-haiku-4-5-20251001"32TS = int(time.time())33# Haiku 4.5 list price (USD per 1M tokens) used only for the est_cost_usd log field.34IN_PRICE, OUT_PRICE = 1.0, 5.0353637def ant(method: str, path: str, body=None, note: str = "", **kw):38    st, resp, _ = anthropic_request(method, path, body, beta=BETA, note=note, **kw)39    if st >= 300:40        raise RuntimeError(f"{method} {path} -> {st}: {json.dumps(resp)[:500] if not isinstance(resp, bytes) else resp[:500]!r}")41    return resp424344def main() -> int:45    agent_id = env_id = session_id = None46    try:47        # 1) Agent: versioned resource; no tools so nothing runs in the sandbox.48        agent = ant("POST", "/v1/agents", {49            "name": f"atlas-platform-agent-{TS}",50            "model": MODEL,51            "system": "You are a terse test agent. Reply with OK.",52            "metadata": {"origin": "api-atlas"},53        }, note="managed-agents create agent")54        agent_id = agent["id"]55        print("agent:", agent_id, "version", agent["version"])5657        # 2) Environment: cloud sandbox with limited egress (least privilege).58        env = ant("POST", "/v1/environments", {59            "name": f"atlas-platform-agent-env-{TS}",60            "config": {"type": "cloud", "networking": {"type": "limited", "allowed_hosts": ["example.com"]}},61        }, note="managed-agents create environment")62        env_id = env["id"]63        print("environment:", env_id)6465        # 3) Session with a hard 5-cent list-cost budget (only attachable at creation).66        session = ant("POST", "/v1/sessions", {67            "agent": agent_id,68            "environment_id": env_id,69            "title": f"atlas-platform-agent-session-{TS}",70            "budget": {"type": "limit", "max_list_cost": {"amount": "5", "currency": "USD"}},71        }, note="managed-agents create session")72        session_id = session["id"]73        print("session:", session_id, "status", session["status"])7475        # 4) Open the SSE stream BEFORE sending the message (only later events are delivered).76        st, lines, _hdrs = anthropic_request(77            "GET", f"/v1/sessions/{session_id}/events/stream", beta=BETA,78            extra_headers={"accept": "text/event-stream"}, content_type=None, stream=True, timeout=200,79            note="managed-agents stream events")80        if st >= 300:81            raise RuntimeError(f"stream -> {st}: {lines}")8283        # 5) Send the user message (returns as soon as the event is queued).84        ant("POST", f"/v1/sessions/{session_id}/events", {85            "events": [{"type": "user.message", "content": [{"type": "text", "text": "Reply with OK."}]}],86        }, note="managed-agents send user.message")8788        # 6) Consume the stream until the session goes idle (or errors / 180 s deadline).89        deadline = time.time() + 18090        seen: list[str] = []91        usage = None92        for line in lines:93            if time.time() > deadline:94                print("timeout waiting for session.status_idle")95                break96            if not line.startswith("data:"):97                continue98            ev = json.loads(line[5:].strip())99            seen.append(ev["type"])100            if ev["type"] == "agent.message":101                print("[agent.message]", "".join(c.get("text", "") for c in ev["content"] if c.get("type") == "text"))102            elif ev["type"] == "session.usage":103                usage = ev.get("usage")104                print("[session.usage] list_cost", (usage or {}).get("list_cost"), "active_seconds", (usage or {}).get("active_seconds"))105            elif ev["type"] == "session.status_idle":106                print("[session.status_idle] stop_reason", ev["stop_reason"]["type"])107                break108            elif ev["type"] == "session.error":109                print("[session.error]", ev["error"].get("type"), ev["error"].get("retry_status", {}).get("type"))110                break111            else:112                print(f"[{ev['type']}]")113        print("event sequence:", " -> ".join(seen))114115        # 7) Final snapshot; log an estimated cost from token usage.116        fin = ant("GET", f"/v1/sessions/{session_id}", note="managed-agents get session")117        u = fin.get("usage") or {}118        est = (u.get("input_tokens", 0) * IN_PRICE + u.get("output_tokens", 0) * OUT_PRICE) / 1e6119        print(json.dumps({"status": fin["status"], "usage": u, "est_cost_usd_tokens_only": round(est, 6)}, indent=2))120        return 0121    finally:122        # Cleanup: every resource this script created.123        try:124            if session_id:125                s = ant("GET", f"/v1/sessions/{session_id}")126                if s["status"] == "running":127                    ant("POST", f"/v1/sessions/{session_id}/events", {"events": [{"type": "user.interrupt"}]})128                    for _ in range(30):129                        time.sleep(2)130                        if ant("GET", f"/v1/sessions/{session_id}")["status"] != "running":131                            break132                ant("DELETE", f"/v1/sessions/{session_id}", note="cleanup delete session")133                print("deleted session", session_id)134            if agent_id:135                ant("POST", f"/v1/agents/{agent_id}/archive", note="cleanup archive agent")136                print("archived agent", agent_id)137            if env_id:138                try:139                    ant("DELETE", f"/v1/environments/{env_id}", note="cleanup delete environment")140                    print("deleted environment", env_id)141                except RuntimeError:142                    ant("POST", f"/v1/environments/{env_id}/archive", note="cleanup archive environment")143                    print("archived environment", env_id)144        except Exception as exc:  # noqa: BLE001145            print("cleanup error:", exc)146147148if __name__ == "__main__":149    raise SystemExit(main())150