Files
ai-agent/sidecar/validate.py
Gabriel Vidal cf01bc00f4 refactor(sidecar): split sidecar.py into focused modules
config / pids / validate / claude_accounts / launch / fork / routes_runs /
routes_accounts, mounted by a thin sidecar.py (same `sidecar:app`, same 24
routes, same startup hook). Mutable state keeps one owner module
(pids._procs, claude_accounts._account_status_cache). Includes the
_claude_args builder from 1de9426 and its test; the Dockerfile's explicit
COPY list gains the new modules.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 23:55:47 +02:00

75 lines
2.7 KiB
Python

"""
Request validation shared by the routes: the bearer check, and the per-field
checks that turn a body value into a command-line argument (session id, model,
harness, thinking / effort flags).
"""
import hmac
import uuid as uuidlib
from fastapi import HTTPException
import pairing
from config import EFFORT_LEVELS, MODEL_RE, PI_MODEL_RE, THINKING_OFF, TOKEN
def _auth(authorization: str | None) -> None:
"""Bearer check. The token is SIDECAR_TOKEN (the host sidecar, the
in-container runner) or, on a paired worker, the one minted at pairing."""
token = TOKEN or pairing.token()
if not token:
raise HTTPException(500, "sidecar not configured (no SIDECAR_TOKEN, "
"not paired)")
if not hmac.compare_digest(authorization or "", f"Bearer {token}"):
raise HTTPException(401, "unauthorized")
def _valid_uuid(sid: str) -> str:
sid = (sid or "").strip()
try:
uuidlib.UUID(sid) # claude requires a valid UUID for --session-id/--resume
except ValueError:
raise HTTPException(400, "sessionId must be a valid UUID")
return sid
def _valid_model(model: str | None, harness: str = "claude") -> str | None:
"""Sanity-check a `--model` value (an alias or a model id). None = unset."""
model = (model or "").strip()
if not model:
return None
rx = PI_MODEL_RE if harness == "pi" else MODEL_RE
if not rx.match(model):
raise HTTPException(400, f"invalid model: {model!r}")
return model
def _valid_harness(harness: str | None) -> str:
h = (harness or "claude").strip().lower()
if h not in ("claude", "pi"):
raise HTTPException(400, f"invalid harness: {harness!r}")
return h
def _thinking_args(thinking: bool | None, harness: str) -> list[str]:
"""The `--thinking` flag for a run, in the harness's own vocabulary.
Only *off* is expressible: thinking on (or unset) emits nothing, leaving each
CLI on its own default level. Both harnesses take the same flag name, so this
is a straight append onto either argv."""
if thinking is False:
return ["--thinking", THINKING_OFF[harness]]
return []
def _effort_args(effort: str | None, harness: str) -> list[str]:
"""The `--effort` flag for a run. Claude-only, and validated as an
allow-list (unlike `--model`, the CLI's scale is a closed set), so a bogus
value is a 400 here rather than a run that dies on an unknown flag value.
Unset — or any pi run — emits nothing and the CLI keeps its default."""
effort = (effort or "").strip().lower()
if not effort or harness != "claude":
return []
if effort not in EFFORT_LEVELS:
raise HTTPException(400, f"invalid effort: {effort!r}")
return ["--effort", effort]