"""Unified response envelope (架构文档 §9.2). {"code": 0, "message": "ok", "data": {...}, "request_id": "..."} {"code": 40301, "message": "今日额度已用完", "data": {...}, "request_id": "...", "hint": "明天恢复,或完成任务多换 3 次"} Every endpoint returns this shape. Handlers in :mod:`packages.shared.handlers` guarantee it even for unhandled exceptions. """ from __future__ import annotations from typing import Any from pydantic import BaseModel, Field from packages.shared.context import get_request_id from packages.shared.errors import ERROR_META, ErrorCode class Envelope(BaseModel): code: int = Field(default=int(ErrorCode.OK), description="0 表示成功;4/5/6 开头为错误段") message: str = Field(default="ok", description="用户可读文案") data: Any = Field(default=None) request_id: str | None = Field(default=None) hint: str | None = Field(default=None, description="告诉用户下一步怎么办") def ok(data: Any = None) -> dict[str, Any]: return Envelope( code=int(ErrorCode.OK), message="ok", data=data, request_id=get_request_id() ).model_dump(exclude_none=True) def fail( code: ErrorCode, *, message: str | None = None, hint: str | None = None, data: Any = None, ) -> dict[str, Any]: meta = ERROR_META.get(code, ERROR_META[ErrorCode.INTERNAL_ERROR]) return Envelope( code=int(code), message=message or meta.message, data=data, request_id=get_request_id(), hint=hint if hint is not None else meta.hint, ).model_dump(exclude_none=True)