"""本章注入透明(B0)的请求/响应 schema(C 扩 / ARCH §3.4、§5.3)。 snake_case;前端经 OpenAPI 生成 TS 类型消费。改字段 → 前端必须 `pnpm gen:api`。 读取无 LLM、无 commit——回放 assemble() 的确定性 SelectionTrace(不变量 #6)。 可控版(PUT)存作者覆盖:pin 强制纳入 / excluded 强制剔除 / recent_n 覆盖回看章数, 本身仍是确定性输入,故「看到的=写章用的」成立。 """ from __future__ import annotations import uuid from pydantic import BaseModel, Field # recent_n 合法区间(回看近况摘要章数)——下限 1,上限防作者误填爆 token。 RECENT_N_MIN = 1 RECENT_N_MAX = 20 class InjectionEntity(BaseModel): """一个被注入本章上下文的实体 + 入选理由(喂给透明面板的徽标)。""" kind: str = Field(description="character / world_entity") name: str reasons: list[str] = Field( default_factory=list, description="explicit_beat/main_character/recent_digest/foreshadow_window/author_pin", ) class InjectionEntityRef(BaseModel): """作者 pin/排除点名的实体引用(kind + name)。""" kind: str = Field(description="character / world_entity") name: str class InjectionOverrideRequest(BaseModel): """PUT /projects/:id/chapters/:no/injection:作者对本章注入的覆盖。 pinned 强制纳入(加 author_pin 理由);excluded 强制剔除;recent_n 覆盖近况回看章数 (None=用默认)。空覆盖 = 纯自动选择。 """ pinned: list[InjectionEntityRef] = Field(default_factory=list) excluded: list[InjectionEntityRef] = Field(default_factory=list) recent_n: int | None = Field(default=None, ge=RECENT_N_MIN, le=RECENT_N_MAX) class InjectionResponse(BaseModel): """GET/PUT /projects/:id/chapters/:no/injection:本章注入留痕 + 作者覆盖回显。""" project_id: uuid.UUID chapter_no: int selected: list[InjectionEntity] = Field(default_factory=list) recent_n: int = Field(description="生效的近况回看章数(默认或被 override 覆盖后的值)") pinned: list[InjectionEntityRef] = Field( default_factory=list, description="作者强制纳入的实体(回显)" ) excluded: list[InjectionEntityRef] = Field( default_factory=list, description="作者强制剔除的实体(回显,供面板恢复)" )