287 lines
7.7 KiB
Python
287 lines
7.7 KiB
Python
"""Pydantic schemas (request/response) for the API layer.
|
|
|
|
These are NOT the same as the world's JSON-schema — see `app/core/state_validator`
|
|
for world-schema validation. Pydantic here only handles HTTP boundary validation.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
from datetime import datetime
|
|
from typing import Any, Literal
|
|
|
|
from pydantic import BaseModel, EmailStr, Field, field_validator
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Auth
|
|
# --------------------------------------------------------------------------- #
|
|
class RegisterRequest(BaseModel):
|
|
email: EmailStr
|
|
username: str = Field(min_length=3, max_length=64, pattern=r"^[a-zA-Z0-9_]+$")
|
|
password: str = Field(min_length=8, max_length=128)
|
|
password_confirm: str = Field(min_length=8, max_length=128)
|
|
|
|
@field_validator("password_confirm")
|
|
@classmethod
|
|
def _match(cls, v, info):
|
|
if "password" in info.data and v != info.data["password"]:
|
|
raise ValueError("password and password_confirm do not match")
|
|
return v
|
|
|
|
|
|
class AdminRegisterRequest(BaseModel):
|
|
token: str
|
|
email: EmailStr
|
|
username: str = Field(min_length=3, max_length=64, pattern=r"^[a-zA-Z0-9_]+$")
|
|
password: str = Field(min_length=8, max_length=128)
|
|
password_confirm: str = Field(min_length=8, max_length=128)
|
|
|
|
@field_validator("password_confirm")
|
|
@classmethod
|
|
def _match(cls, v, info):
|
|
if "password" in info.data and v != info.data["password"]:
|
|
raise ValueError("password and password_confirm do not match")
|
|
return v
|
|
|
|
|
|
class LoginRequest(BaseModel):
|
|
login: str = Field(min_length=1, max_length=255) # email OR username
|
|
password: str = Field(min_length=1, max_length=128)
|
|
|
|
|
|
class TokenResponse(BaseModel):
|
|
access_token: str
|
|
refresh_token: str
|
|
token_type: str = "bearer"
|
|
expires_in: int = 60 * 24
|
|
user: "UserPublic"
|
|
|
|
|
|
class UserPublic(BaseModel):
|
|
id: uuid.UUID
|
|
email: EmailStr
|
|
username: str
|
|
is_admin: bool
|
|
is_active: bool
|
|
created_at: datetime
|
|
last_login_at: datetime | None = None
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Worlds
|
|
# --------------------------------------------------------------------------- #
|
|
class WorldSummary(BaseModel):
|
|
id: uuid.UUID
|
|
name: str
|
|
description: str | None
|
|
language: str
|
|
status: str
|
|
last_played_at: datetime | None
|
|
current_time: str
|
|
created_at: datetime
|
|
preview_player_name: str | None = None
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class WorldFull(BaseModel):
|
|
id: uuid.UUID
|
|
owner_id: uuid.UUID
|
|
preset_id: uuid.UUID | None
|
|
name: str
|
|
description: str | None
|
|
language: str
|
|
rules: list
|
|
time_schema: dict
|
|
schemas: list
|
|
environment_schema: list
|
|
environment: dict
|
|
plot_rails: dict
|
|
current_time: str
|
|
status: str
|
|
intro_scene: str | None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
last_played_at: datetime | None
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class WorldCreateRequest(BaseModel):
|
|
mode: Literal["preset", "form"]
|
|
preset_id: uuid.UUID | None = None
|
|
form_data: dict | None = None
|
|
name: str = Field(min_length=1, max_length=255)
|
|
language: str = Field(min_length=2, max_length=8, default="en")
|
|
player_name: str = Field(min_length=1, max_length=128)
|
|
notes: str | None = None
|
|
|
|
|
|
class WorldPatchRequest(BaseModel):
|
|
name: str | None = None
|
|
description: str | None = None
|
|
rules: list | None = None
|
|
schemas: list | None = None
|
|
environment_schema: list | None = None
|
|
environment: dict | None = None
|
|
plot_rails: dict | None = None
|
|
time_schema: dict | None = None
|
|
current_time: str | None = None
|
|
intro_scene: str | None = None
|
|
status: str | None = None
|
|
updated_at: datetime | None = None # for optimistic locking
|
|
|
|
|
|
class WorldEditRequest(BaseModel):
|
|
instruction: str = Field(min_length=1, max_length=4000)
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Sessions
|
|
# --------------------------------------------------------------------------- #
|
|
class IterateRequest(BaseModel):
|
|
action: str = Field(min_length=1, max_length=4000)
|
|
action_source: Literal["custom", "suggested"] = "custom"
|
|
|
|
|
|
class AnswerRequest(BaseModel):
|
|
text: str = Field(min_length=1, max_length=4000)
|
|
|
|
|
|
class SessionState(BaseModel):
|
|
world: dict
|
|
environment: dict
|
|
recent_steps: list[dict]
|
|
next_actions: list[str]
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Presets
|
|
# --------------------------------------------------------------------------- #
|
|
class PresetSummary(BaseModel):
|
|
id: uuid.UUID
|
|
name: str
|
|
description: str | None
|
|
language: str
|
|
is_public: bool
|
|
status: str
|
|
version: int
|
|
created_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class PresetFull(BaseModel):
|
|
id: uuid.UUID
|
|
owner_id: uuid.UUID
|
|
name: str
|
|
description: str | None
|
|
language: str
|
|
rules: list
|
|
time_schema: dict
|
|
schemas: list
|
|
environment_schema: list
|
|
environment_initial: dict
|
|
status: str
|
|
is_public: bool
|
|
version: int
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class PresetCreateRequest(BaseModel):
|
|
name: str = Field(min_length=1, max_length=255)
|
|
description: str | None = None
|
|
language: str = "en"
|
|
rules: list = Field(default_factory=list)
|
|
time_schema: dict = Field(default_factory=lambda: {"hours_in_day": 24, "initial_date": "day_1_hour_8"})
|
|
schemas: list = Field(default_factory=list)
|
|
environment_schema: list = Field(default_factory=list)
|
|
environment_initial: dict = Field(default_factory=dict)
|
|
is_public: bool = False
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Admin
|
|
# --------------------------------------------------------------------------- #
|
|
class SettingsPatchRequest(BaseModel):
|
|
"""A flat dict of {setting_key: value} to upsert."""
|
|
|
|
settings: dict[str, Any]
|
|
|
|
|
|
class LlmLogOut(BaseModel):
|
|
id: uuid.UUID
|
|
stage: str
|
|
model: str
|
|
status: str
|
|
latency_ms: int | None
|
|
prompt_tokens: int | None
|
|
completion_tokens: int | None
|
|
error_message: str | None
|
|
created_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class LlmLogDetail(BaseModel):
|
|
id: uuid.UUID
|
|
user_id: uuid.UUID | None
|
|
world_id: uuid.UUID | None
|
|
step_id: uuid.UUID | None
|
|
stage: str
|
|
model: str
|
|
request_messages: list
|
|
request_tools: list | None
|
|
response_message: dict
|
|
tool_calls: list | None
|
|
prompt_tokens: int | None
|
|
completion_tokens: int | None
|
|
latency_ms: int | None
|
|
temperature: float | None
|
|
status: str
|
|
error_message: str | None
|
|
created_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class TestLlmRequest(BaseModel):
|
|
api_url: str | None = None
|
|
api_key: str | None = None
|
|
model: str | None = None
|
|
|
|
|
|
class TestEmbeddingsRequest(BaseModel):
|
|
api_url: str | None = None
|
|
api_key: str | None = None
|
|
model: str | None = None
|
|
provider: str | None = None
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Misc
|
|
# --------------------------------------------------------------------------- #
|
|
class HealthResponse(BaseModel):
|
|
status: str
|
|
db: bool
|
|
qdrant: bool
|
|
llm: bool
|
|
embeddings: bool
|
|
version: str
|
|
|
|
|
|
class ErrorOut(BaseModel):
|
|
error: dict[str, Any]
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Forward refs
|
|
# --------------------------------------------------------------------------- #
|
|
TokenResponse.model_rebuild()
|