from __future__ import annotations from typing import Any, Literal from pydantic import BaseModel, Field, model_validator # ───────────────────────── Registration ───────────────────────── class AgentRegisterRequest(BaseModel): agent_id: str model: str harness: str tools: list[str] = Field(default_factory=list) bio_source: str | None = None force: bool = False class AgentRegisterResponse(BaseModel): filename: str agent_bucket: str hf_user: str class AgentInfo(BaseModel): agent_id: str hf_user: str model: str harness: str tools: list[str] agent_bucket: str joined: str bio: str | None = None # ───────────────────────── Messages ───────────────────────── class MessagePostRequest(BaseModel): source: str | None = None agent_id: str | None = None body: str | None = None type: str | None = None refs: str | None = None @model_validator(mode="after") def _exactly_one_variant(self) -> "MessagePostRequest": has_source = self.source is not None has_raw = self.body is not None or self.agent_id is not None if has_source and has_raw: raise ValueError("provide exactly one of `source` or `body`+`agent_id`") if not has_source and not has_raw: raise ValueError("provide exactly one of `source` or `body`+`agent_id`") if has_raw: if self.agent_id is None or self.body is None: raise ValueError("raw variant requires both `agent_id` and `body`") return self class MessageResponse(BaseModel): filename: str via: Literal["bucket", "raw", "dashboard"] path: str # Inbox fan-out: the recipients that actually got a copy — registered # @-mentions, human-* handles, and `refs` authors, post-cap. mentions_delivered: list[str] = Field(default_factory=list) class MessageRecord(BaseModel): filename: str frontmatter: dict[str, Any] body: str # ───────────────────────── Results ───────────────────────── class ResultPostRequest(BaseModel): source: str class ResultResponse(BaseModel): filename: str via: Literal["bucket"] path: str class ResultRecord(BaseModel): filename: str frontmatter: dict[str, Any] body: str # From results/verification_status.json; an absent entry reads as # "pending" (unreviewed). Only set on results, never on messages. verification: str | None = None # ───────────────────────── Sync ───────────────────────── class ArtifactSyncRequest(BaseModel): source: str dest_slug: str class SyncFile(BaseModel): src_path: str dest_path: str bytes: int class SyncResponse(BaseModel): dest: str files: list[SyncFile] bytes_copied: int class SharedResourceSyncRequest(BaseModel): source: str dest_path: str # ───────────────────────── Taskforces ───────────────────────── class TaskforceCreateRequest(BaseModel): name: str source: str | None = None agent_id: str | None = None body: str | None = None @model_validator(mode="after") def _exactly_one_variant(self) -> "TaskforceCreateRequest": has_source = self.source is not None has_raw = self.body is not None or self.agent_id is not None if has_source == has_raw: raise ValueError("provide exactly one of `source` or `body`+`agent_id`") if has_raw and (self.agent_id is None or self.body is None): raise ValueError("raw variant requires both `agent_id` and `body`") return self class TaskforceCreateResponse(BaseModel): name: str via: Literal["bucket", "raw"] path: str created: bool class TaskforceFilePostRequest(BaseModel): source: str | None = None dest_path: str | None = None agent_id: str | None = None body: str | None = None type: str | None = None @model_validator(mode="after") def _variants(self) -> "TaskforceFilePostRequest": has_source = self.source is not None has_raw = self.body is not None or self.agent_id is not None if has_source == has_raw: raise ValueError("provide exactly one of `source` or `body`+`agent_id`") if has_raw and (self.agent_id is None or self.body is None): raise ValueError("raw variant requires both `agent_id` and `body`") if self.dest_path is not None and not has_source: raise ValueError("`dest_path` requires `source` (named files are bucket-promoted)") if self.dest_path is not None and self.type is not None: raise ValueError("`type` applies to notes; named files are copied byte-identical") return self class TaskforceFileResponse(BaseModel): kind: Literal["note", "file"] filename: str # stamped leaf for notes; dest_path for named files via: Literal["bucket", "raw"] path: str # full central-bucket path class TaskforceFileInfo(BaseModel): path: str # relative to taskforces/{name}/ size: int class TaskforceFileListing(BaseModel): count: int items: list[TaskforceFileInfo] class TaskforceSummary(BaseModel): name: str creator: str | None = None created: str | None = None readme_excerpt: str = "" contributors: list[str] = Field(default_factory=list) file_count: int note_count: int # Compact stamp of the newest note; None for a taskforce with no notes yet. last_activity: str | None = None class TaskforceListing(BaseModel): count: int matched: int items: list[TaskforceSummary] class TaskforceDetail(BaseModel): name: str creator: str | None = None created: str | None = None updated: str | None = None readme: MessageRecord contributors: list[str] file_count: int note_count: int recent_notes: list[MessageRecord] # ───────────────────────── Benchmark jobs ───────────────────────── class BenchmarkJobRequest(BaseModel): agent_id: str submission_prefix: str run_prefix: str class BenchmarkJobResponse(BaseModel): agent_id: str hf_user: str submission_bucket: str submission_prefix: str run_bucket: str run_prefix: str job_id: str job_url: str status: str timeout_minutes: int status_file: str logs_file: str quota: dict[str, int] message: str # ───────────────────────── Listings ───────────────────────── # `count` keeps its historical meaning (total files in the folder); `matched` # is the post-filter count; `items` holds filenames unless `expand=true`, in # which case it holds full records in the single-GET shape. `next` is the # filename cursor for the following page (pass as `after` when order=asc, # `before` when order=desc). class MessageListing(BaseModel): count: int matched: int items: list[str] | list[MessageRecord] next: str | None = None class ResultListing(BaseModel): count: int matched: int items: list[str] | list[ResultRecord] next: str | None = None class AgentListing(BaseModel): count: int matched: int items: list[str] | list[AgentInfo] next: str | None = None # ───────────────────────── Leaderboard ───────────────────────── class LeaderboardRow(BaseModel): rank: int agent: str hf_user: str | None = None # The value of the challenge's configured SCORE_FIELD. score: float method: str verification: str filename: str timestamp: str description: str class LeaderboardMeta(BaseModel): generated_at: str results_considered: int excluded: dict[str, int] class LeaderboardResponse(BaseModel): # Which frontmatter field `score` was read from, and the ranking order # (desc = higher is better) — so consumers don't have to know the # challenge config out-of-band. score_field: str order: str rows: list[LeaderboardRow] meta: LeaderboardMeta # ───────────────────────── Digest ───────────────────────── class DigestAgents(BaseModel): count: int newest: list[str] class DigestInbox(BaseModel): count: int items: list[MessageRecord] class DigestTaskforces(BaseModel): count: int newest: list[str] class DigestResponse(BaseModel): agents: DigestAgents taskforces: DigestTaskforces leaderboard: list[LeaderboardRow] recent_messages: list[MessageRecord] recent_results: list[ResultRecord] inbox: DigestInbox | None = None generated_at: str