"""Request/response schemas. NFR-MAINT-01 契约先行: schema 先定义,客户端类型由 OpenAPI 生成。 Wire responses are wrapped by the unified envelope (架构文档 §9.2); these models describe the ``data`` payload only. """ from __future__ import annotations from datetime import datetime from pydantic import BaseModel, Field class DevTokenRequest(BaseModel): device_key: str = Field( default="local-dev", min_length=1, max_length=64, description="任意稳定字符串;同一 device_key 复用同一测试用户", ) class DevTokenResponse(BaseModel): access_token: str token_type: str = "Bearer" expires_in: int user_id: str = Field(description="雪花 ID 以字符串返回,避免 JS 精度丢失") class ChatRequest(BaseModel): message: str = Field(min_length=1, max_length=2000) conversation_id: str | None = Field( default=None, max_length=32, description="会话 ID(字符串;雪花 ID 超 2^53,JS Number 会丢精度)。" "缺省时新建会话并在响应中返回 (FR-AGT-09)", ) request_key: str | None = Field( default=None, max_length=128, description="幂等键;相同键不会重复扣配额 (架构文档 §9.1)", ) class ConversationResponse(BaseModel): conversation_id: str = Field(description="雪花 ID 以字符串返回,避免 JS 精度丢失") title: str message_count: int last_message_at: datetime | None created_at: datetime class ChatMessageItem(BaseModel): message_id: str role: str content: str model: str | None call_id: str | None = Field( description="assistant 消息关联的 ob_ai_call 行(复盘锚点);user 消息为 null" ) created_at: datetime class ChatUsage(BaseModel): tokens_in: int tokens_out: int tokens_cached: int latency_ms: int cost_cents: str class ChatResponse(BaseModel): conversation_id: str reply: str provider: str model: str prompt_id: str | None prompt_version: str | None call_id: str usage: ChatUsage quota_remaining: int class QuotaResponse(BaseModel): quota_key: str period_key: str used: int limit_value: int bonus_granted: int remaining: int reset_at: datetime | None class LedgerEntry(BaseModel): tx_id: str delta: int tx_type: str status: str reason: str | None quota_key: str | None period_key: str | None balance_after: int created_at: datetime class TaskSubmitRequest(BaseModel): echo: str = Field(default="ping", max_length=200) idempotency_key: str | None = Field(default=None, max_length=128) class TaskResponse(BaseModel): task_id: str task_type: str status: str progress: int stage_text: str | None result: dict[str, object] | None error_code: int | None error_message: str | None retry_count: int submitted_at: datetime started_at: datetime | None finished_at: datetime | None