Agent Sandbox Registry

awesome-templates

agents-cli/e2e_test.py

无中文版

agents-cli/e2e_test.py

#!/usr/bin/env python3
"""Local check of the Agents API template.

The server must already be running with ``E2E_RUN_LLM=0`` so ``e2e-bin`` is
on ``PATH``. No model API key is read or sent.
"""

from __future__ import annotations

import json
import os
import sys
import time
import urllib.error
import urllib.request

BASE = os.environ.get("E2E_BASE_URL", "http://127.0.0.1:9000").rstrip("/")


def call(method: str, path: str, body: dict | None = None) -> tuple[int, object, dict[str, str]]:
    data = None if body is None else json.dumps(body).encode("utf-8")
    request = urllib.request.Request(BASE + path, data=data, method=method)
    request.add_header("Accept", "application/json")
    if data is not None:
        request.add_header("Content-Type", "application/json")
    try:
        with urllib.request.urlopen(request, timeout=30) as response:
            raw = response.read().decode("utf-8")
            headers = {k.lower(): v for k, v in response.headers.items()}
            return response.status, json.loads(raw) if raw else {}, headers
    except urllib.error.HTTPError as exc:
        raw = exc.read().decode("utf-8", errors="replace")
        headers = {k.lower(): v for k, v in exc.headers.items()}
        try:
            parsed: object = json.loads(raw) if raw else {}
        except json.JSONDecodeError:
            parsed = raw
        return exc.code, parsed, headers


def texts(payload: object) -> list[str]:
    if not isinstance(payload, dict):
        return []
    found: list[str] = []
    for item in payload.get("data") or []:
        if not isinstance(item, dict) or item.get("type") != "message":
            continue
        for part in item.get("content") or []:
            if isinstance(part, dict) and part.get("text"):
                found.append(str(part["text"]))
    return found


def expect(title: str, status: int, want: int, payload: object) -> None:
    print(f"[{'ok' if status == want else 'fail'}] {title} ({status})")
    if status != want:
        print(json.dumps(payload, ensure_ascii=False)[:500])
        raise SystemExit(1)


def turn(adapter: str, needle: str) -> None:
    workspace = os.environ.get("E2E_WORKSPACE", "/tmp")
    status, created, headers = call(
        "POST",
        "/v1/agents/sessions",
        {
            "agent": {"model": adapter},
            "environment": {"type": "self_hosted", "workspace_directory": workspace},
        },
    )
    expect(f"POST session {adapter}", status, 200, created)
    if headers.get("x-agent-sandbox-local") != "1":
        print("missing X-Agent-Sandbox-Local")
        raise SystemExit(1)
    if not isinstance(created, dict) or "id" not in created:
        print(created)
        raise SystemExit(1)
    session_id = str(created["id"])
    status, acked, _headers = call(
        "POST",
        f"/v1/agents/sessions/{session_id}/events",
        {
            "events": [
                {
                    "type": "agent.session.input.message",
                    "input": [{"role": "user", "content": [{"type": "input_text", "text": "hi"}]}],
                }
            ]
        },
    )
    expect(f"POST events {adapter}", status, 202, acked)
    deadline = time.time() + 20
    items: object = {}
    status = 0
    while time.time() < deadline:
        status, items, _headers = call(
            "GET", f"/v1/agents/sessions/{session_id}/items?limit=100&order=asc"
        )
        if status == 200 and needle in texts(items):
            print(f"[ok] items {adapter} contains {needle}")
            return
        time.sleep(0.2)
    print(f"[fail] items {adapter} missing {needle} ({status})")
    print(json.dumps(items, ensure_ascii=False)[:800])
    _status, session, _headers = call("GET", f"/v1/agents/sessions/{session_id}")
    print(json.dumps(session, ensure_ascii=False)[:800])
    raise SystemExit(1)


def main() -> int:
    status, health, _headers = call("GET", "/health")
    expect("GET /health", status, 200, health)

    status, missing, _headers = call("POST", "/v1/agents/sessions", {"agent": {"model": "gpt-5"}})
    expect("omitted adapter uses the first runtime", status, 200, missing)
    agent = missing.get("agent") if isinstance(missing, dict) else None
    if not isinstance(agent, dict) or agent.get("id") != "codex" or agent.get("model") != "gpt-5":
        print(missing)
        raise SystemExit(1)

    status, refused, _headers = call(
        "POST",
        "/v1/agents/sessions",
        {"adapter": "claude-code", "agent": {"model": "gpt-5"}},
    )
    expect("claude-code is not in this slice", status, 404, refused)

    turn("codex", "hello-from-fake-codex")
    turn("qwen-code", "hello-from-fake-qwen")
    return 0


if __name__ == "__main__":
    sys.exit(main())