8414c59557
Five rounds of sweep across the tree. Net ~544 lines removed. Removed: - Section-divider banners and one-line section labels (# Display utilities, # ----- list_requests -----, # CVSS breakdown, etc.). - Module-level prose docstrings on internal modules. Kept one-line summaries; trimmed multi-paragraph narration about SDK/Strix responsibility splits, cache strategies, three-source precedence. - Internal-helper docstrings that just restate the function name — caido_api helpers (caido_url, get_client, view_request, etc.), settings-class one-liners (LLMSettings, RuntimeSettings, ...), UI helper docstrings. - Args/Returns blocks on non-LLM-facing internal helpers (build_strix_agent, render_system_prompt, create_or_reuse, bootstrap_caido) — kept only the genuinely non-obvious params. - Internal-history phrasing — "Mirrors main-branch shape", "pre-SDK harness", "previous lookup matched no attribute". - Narrative comments inside function bodies that explained what the next line does, design rationale obvious from the surrounding code, or "we used to..." asides. - Trailing periods on every error-string literal across the tool tree. - Duplicated roundtripTime quirk comment (kept the LLM-facing copy in tools/proxy/tools.py). Kept (every one names an upstream bug, vendored-code provenance, or non-obvious data quirk): - core/runner.py: SDK replay-with-empty-initial-input + on_agent_end lifecycle gap. - runtime/docker_client.py: VERBATIM COPY block of the upstream _create_container body, pinned to SDK v0.14.6. - runtime/session_manager.py: NO_PROXY for agent-browser CDP loopback. - tools/proxy/caido_api.py: generated-pydantic Request.raw quirk, replay double-history pitfall. - tools/proxy/tools.py: Caido roundtripTime=0 quirk for proxy captures. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
102 lines
3.1 KiB
Python
102 lines
3.1 KiB
Python
"""Caido client bootstrap.
|
|
|
|
The Caido CLI runs as an in-container sidecar listening on
|
|
``127.0.0.1:48080`` *inside* the sandbox. We grab a guest token by
|
|
``session.exec()``-ing curl from inside the container, then construct
|
|
a host-side :class:`caido_sdk_client.Client` against the runtime's
|
|
exposed-port URL for all subsequent SDK calls.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import json
|
|
import logging
|
|
from typing import TYPE_CHECKING
|
|
|
|
from caido_sdk_client import Client, TokenAuthOptions
|
|
from caido_sdk_client.types import CreateProjectOptions
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
from agents.sandbox.session import BaseSandboxSession
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
_LOGIN_AS_GUEST_BODY = (
|
|
'{"query":"mutation LoginAsGuest { loginAsGuest { token { accessToken } } }"}'
|
|
)
|
|
|
|
|
|
async def _login_as_guest(
|
|
session: BaseSandboxSession,
|
|
*,
|
|
container_url: str,
|
|
attempts: int = 10,
|
|
) -> str:
|
|
"""``session.exec`` curl to fetch a guest token; retry until ready.
|
|
|
|
Caido's GraphQL listener may not be up the instant the container
|
|
starts. The retry loop also doubles as the Caido readiness probe —
|
|
no separate TCP healthcheck needed.
|
|
"""
|
|
last_err: str | None = None
|
|
for i in range(1, attempts + 1):
|
|
result = await session.exec(
|
|
"curl",
|
|
"-fsS",
|
|
"-X",
|
|
"POST",
|
|
"-H",
|
|
"Content-Type: application/json",
|
|
"-d",
|
|
_LOGIN_AS_GUEST_BODY,
|
|
f"{container_url}/graphql",
|
|
timeout=15,
|
|
)
|
|
if result.ok():
|
|
try:
|
|
payload = json.loads(result.stdout)
|
|
token = (
|
|
payload.get("data", {})
|
|
.get("loginAsGuest", {})
|
|
.get("token", {})
|
|
.get("accessToken")
|
|
)
|
|
if token:
|
|
return str(token)
|
|
last_err = f"loginAsGuest returned no token: {payload}"
|
|
except json.JSONDecodeError as exc:
|
|
last_err = f"unparseable response: {exc}: {result.stdout!r}"
|
|
else:
|
|
stderr = result.stderr.decode("utf-8", errors="replace")[:200]
|
|
last_err = f"curl exit {result.exit_code}: {stderr}"
|
|
logger.debug("loginAsGuest attempt %d/%d failed: %s", i, attempts, last_err)
|
|
await asyncio.sleep(min(2.0 * i, 8.0))
|
|
|
|
raise RuntimeError(f"loginAsGuest failed after {attempts} attempts: {last_err}")
|
|
|
|
|
|
async def bootstrap_caido(
|
|
session: BaseSandboxSession,
|
|
*,
|
|
host_url: str,
|
|
container_url: str,
|
|
) -> Client:
|
|
"""Connect to the in-container Caido sidecar and select a fresh project."""
|
|
logger.info("Bootstrapping Caido client (host=%s, container=%s)", host_url, container_url)
|
|
|
|
access_token = await _login_as_guest(session, container_url=container_url)
|
|
|
|
client = Client(host_url, auth=TokenAuthOptions(token=access_token))
|
|
await client.connect()
|
|
|
|
project = await client.project.create(
|
|
CreateProjectOptions(name="sandbox", temporary=True),
|
|
)
|
|
await client.project.select(project.id)
|
|
logger.info("Caido project selected: %s", project.id)
|
|
return client
|