Files
ai-agent/backend/schemas/misc.py
Gabriel Vidal 2119af66d5 refactor(schemas): split backend/schemas.py into a schemas/ package
One module per API area (base, files, activity, skills, conversations,
notifications, forms, search, plans, projects, services, misc, diff, cron,
agents) on a shared base.Schema; __init__.py re-exports every model so
handlers keep writing schemas.<Name>. OpenAPI output is byte-identical.
PlanSummary lives in conversations.py to keep the module graph a DAG.

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

264 lines
9.1 KiB
Python

"""Small mutation / misc envelopes (ui state, models, accounts, workers,
spawn/upload results, meta) and the deploy status banner."""
from __future__ import annotations
from typing import Literal, Optional
from .base import Schema
from .conversations import ConvMeta
__all__ = [
"UiStateValue",
"OkResponse",
"ModelInfo",
"ModelsResponse",
"PendingLogin",
"AccountStatus",
"LoginCodeResult",
"Account",
"AccountsResponse",
"Worker",
"WorkersResponse",
"OpenRouterModel",
"OpenRouterModelsResponse",
"SpawnResult",
"InterruptResult",
"UploadedFile",
"UploadResult",
"ConversationMetaResponse",
"MetaUpdateResult",
"ArchiveOldResult",
"DeployStep",
"DeployStatus",
]
# ── small mutation / misc envelopes ───────────────────────────────────────────
class UiStateValue(Schema):
value: Optional[str] = None
class OkResponse(Schema):
ok: bool
class ModelInfo(Schema):
"""One pickable model (a model tag in the composer)."""
id: str # the exact `--model` argument
displayName: str
family: str # opus | sonnet | haiku | fable | qwen
version: str # "4.8", "5", … (within the family)
alias: Optional[str] = None # CLI shorthand, only on a family's newest
latest: bool = False
# Which agent harness runs sessions on this model: "claude" (the claude
# CLI) or "pi" (the pi.dev runner). Picking the chip picks the harness.
harness: Optional[str] = None
# pi models only: "openrouter" (hosted) or "evox2" (local LM Studio).
provider: Optional[str] = None
# Optional short chip label ("qwen 3.6 local"); the frontend derives one
# from the id when absent.
label: Optional[str] = None
# Context-window size in tokens (`max_input_tokens` from the Anthropic
# Models API, with a static per-family fallback). Drives the conversation
# page's context-fill gizmo.
contextWindow: Optional[int] = None
class ModelsResponse(Schema):
models: list[ModelInfo]
default: str # model a run uses when nothing is picked
class PendingLogin(Schema):
"""A headless ``claude auth login`` waiting for the code its sign-in page
shows (see sidecar/claude_cli.py)."""
loginId: str
account: str
url: str # the OAuth sign-in URL to open
startedAt: int # epoch seconds
expiresAt: int # the CLI is killed after this
class AccountStatus(Schema):
"""An account's live login, from ``claude auth status`` under its config
dir (via the sidecar). ``configured`` False ⇒ the sidecar doesn't declare
the account (or couldn't be reached — then ``error`` says so)."""
configured: bool
loggedIn: bool
email: Optional[str] = None
orgName: Optional[str] = None
subscriptionType: Optional[str] = None
authMethod: Optional[str] = None
error: Optional[str] = None
# A `claude auth login` started from Settings and still waiting for its
# code (None when none is pending).
pendingLogin: Optional[PendingLogin] = None
class LoginCodeResult(Schema):
"""The account's fresh login after a code/logout (``claude auth status``)."""
id: str
loggedIn: bool
email: Optional[str] = None
orgName: Optional[str] = None
subscriptionType: Optional[str] = None
authMethod: Optional[str] = None
message: Optional[str] = None
class Account(Schema):
"""A Claude account profile a session can run on (backend/accounts.py —
hard-coded; Settings only owns its plan/price presentation)."""
id: str # "personal" | "work"
label: str
description: str
color: str # a TAG_COLORS key
projects: list[str] # ~/projects slugs routed to this account
excludeFromTotals: bool # left out of aggregated totals by default
default: bool
status: Optional[AccountStatus] = None # only with ?health=1
class AccountsResponse(Schema):
accounts: list[Account]
default: str
class Worker(Schema):
"""A paired remote runner (backend/workers.py): the same sidecar on
another machine, driven over the tailnet. No token here — it never leaves
the backend."""
id: str
name: str
host: str
port: int
url: str
account: Optional[str] = None # the hub account its login is
email: Optional[str] = None # its `claude` login
orgName: Optional[str] = None
hostname: Optional[str] = None
platform: Optional[str] = None # "darwin" | "linux"
machine: Optional[str] = None
version: Optional[str] = None
cwd: Optional[str] = None # where a new run starts there
permissionMode: Optional[str] = None
pairedAt: Optional[str] = None
# The account's new runs go here whenever it is online. Off: only runs the
# composer pins to this worker (the lab stays the default runner).
autoRoute: bool = False
status: str # online | offline | unauthorized | unknown
lastSeen: Optional[str] = None
lastError: Optional[str] = None
running: int = 0 # live runs it launched
mirrored: int = 0 # transcripts mirrored since boot
syncing: bool = False
class WorkersResponse(Schema):
workers: list[Worker]
class OpenRouterModel(Schema):
"""One OpenRouter model a pi.dev session can be spawned on.
The catalogue behind it is APPE's (models.dev, synced daily) — see
`backend/openrouter.py`. Prices are USD per **million** tokens.
"""
id: str # the `--model` argument, e.g. "openai/gpt-oss-20b"
name: str # display name ("GPT OSS 20B")
vendor: str # the id's first segment ("openai")
description: str = ""
inputCost: float
outputCost: float
# Cache-read price. None = the model has no prompt cache (a cache read is
# then billed as a fresh read), which is a different fact from a free cache.
cacheCost: Optional[float] = None
contextWindow: Optional[int] = None
# Parameters in billions, mined from the id/name by APPE. None for models
# that publish no size (most closed ones).
modelSize: Optional[float] = None
tags: list[str] = [] # vision | reasoning | tools | opensource | …
tier: str = "" # small | medium | big (blended-price heuristic)
license: str = ""
speedTps: Optional[float] = None # median output tokens/sec
class OpenRouterModelsResponse(Schema):
models: list[OpenRouterModel] # cheapest first (blended 3:1 in:out)
source: str # the APPE URL the catalogue came from
generatedAt: str = "" # when APPE generated it (ISO 8601)
class SpawnResult(Schema):
sessionId: str
pid: Optional[int] = None
# "inbox" when a follow-up was delivered into the still-running session
# through its inbox socket instead of restarting it on the new prompt.
delivered: Optional[str] = None
class InterruptResult(Schema):
sessionId: str
ok: bool
class UploadedFile(Schema):
name: str
size: int
contentType: str
# Repo-relative path the composer injects into the prompt (read by the session).
repoPath: str
# Backend serve route the thread viewer renders the file from.
url: str
class UploadResult(Schema):
files: list[UploadedFile]
class ConversationMetaResponse(Schema):
meta: dict[str, ConvMeta]
class MetaUpdateResult(Schema):
id: str
meta: ConvMeta
class ArchiveOldResult(Schema):
archived: int # how many conversations were newly archived
cutoff: str # UTC start-of-yesterday used as the age threshold
# ── deploy status ────────────────────────────────────────────────────────────
class DeployStep(Schema):
"""One phase of the blue-green deploy, with its rendered dot state."""
n: int
label: str
# pending (grey) · active (loading) · done (green) · failed (red)
state: Literal["pending", "active", "done", "failed"]
class DeployStatus(Schema):
"""Snapshot of the in-flight ai-agent deploy (``deploy.sh``), or ``null``.
Written by the host-side deploy script into the data dir and polled by the
backend, which pings a ``deploy`` SSE event on change. Drives the sticky
in-app deploy banner (worktree label + a dot-per-phase timeline)."""
# Worktree / branch label the deploy was kicked off from.
label: Optional[str] = None
status: Literal["running", "done", "failed"]
phaseNum: int
totalPhases: int
# Human description of the current phase.
phase: Optional[str] = None
steps: list[DeployStep]
startedAt: Optional[int] = None
updatedAt: Optional[int] = None
# The conversation that triggered the deploy, for the expanded panel's link.
sessionId: Optional[str] = None
convUrl: Optional[str] = None
convTitle: Optional[str] = None