feat: 实现起卦、设置与积分系统
This commit is contained in:
@@ -7,16 +7,7 @@ from schemas.agent.forwarded_props import (
|
||||
from schemas.agent.forwarded_props import RuntimeMode
|
||||
from schemas.agent.runtime_models import (
|
||||
AgentOutput,
|
||||
ConstraintItem,
|
||||
ExecutionMode,
|
||||
KeyEntity,
|
||||
NormalizedTaskInput,
|
||||
ResultTyping,
|
||||
ResultType,
|
||||
RouterAgentOutput,
|
||||
RunStatus,
|
||||
TaskType,
|
||||
TaskTyping,
|
||||
ToolAgentOutput,
|
||||
ToolStatus,
|
||||
WorkerAgentOutputLite,
|
||||
@@ -36,19 +27,10 @@ from schemas.agent.ui_hints import (
|
||||
__all__ = [
|
||||
"AgentType",
|
||||
"AgentOutput",
|
||||
"ConstraintItem",
|
||||
"ExecutionMode",
|
||||
"ForwardedPropsPayload",
|
||||
"KeyEntity",
|
||||
"NormalizedTaskInput",
|
||||
"ResultTyping",
|
||||
"ClientTimeContext",
|
||||
"ResultType",
|
||||
"RouterAgentOutput",
|
||||
"RunStatus",
|
||||
"RuntimeMode",
|
||||
"TaskType",
|
||||
"TaskTyping",
|
||||
"SystemAgentLLMConfig",
|
||||
"SystemVisibilityBit",
|
||||
"ToolAgentOutput",
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
class ContextSource(str, Enum):
|
||||
LATEST_CHAT = "latest_chat"
|
||||
|
||||
|
||||
class ContextWindowMode(str, Enum):
|
||||
DAY = "day"
|
||||
NUMBER = "number"
|
||||
|
||||
|
||||
class MessageContextConfig(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
source: ContextSource = ContextSource.LATEST_CHAT
|
||||
window_mode: ContextWindowMode = ContextWindowMode.DAY
|
||||
window_count: int = Field(default=2, ge=1, le=200)
|
||||
|
||||
|
||||
class RuntimeConfig(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
enabled_tools: list[str] = Field(default_factory=list, max_length=0)
|
||||
context: MessageContextConfig = Field(default_factory=MessageContextConfig)
|
||||
@@ -1,70 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
from schemas.agent.ui_hints import UiHintsPayload
|
||||
|
||||
|
||||
class TaskType(str, Enum):
|
||||
KNOWLEDGE = "knowledge"
|
||||
RECOMMENDATION = "recommendation"
|
||||
PLANNING = "planning"
|
||||
SCHEDULING = "scheduling"
|
||||
REMINDER_MANAGEMENT = "reminder_management"
|
||||
TODO_MANAGEMENT = "todo_management"
|
||||
COMMUNICATION_DRAFTING = "communication_drafting"
|
||||
INFORMATION_ORGANIZATION = "information_organization"
|
||||
STATUS_TRACKING = "status_tracking"
|
||||
TRANSACTION_ASSIST = "transaction_assist"
|
||||
ACTION_EXECUTION = "action_execution"
|
||||
TROUBLESHOOTING = "troubleshooting"
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
|
||||
class ResultType(str, Enum):
|
||||
DIRECT_ANSWER = "direct_answer"
|
||||
OPTIONS_WITH_RECOMMENDATION = "options_with_recommendation"
|
||||
ACTION_PLAN = "action_plan"
|
||||
SCHEDULE_PROPOSAL = "schedule_proposal"
|
||||
TODO_LIST = "todo_list"
|
||||
DRAFT_MESSAGE = "draft_message"
|
||||
SUMMARY = "summary"
|
||||
PROGRESS_SUMMARY = "progress_summary"
|
||||
DIAGNOSIS_REPORT = "diagnosis_report"
|
||||
STRUCTURED_PAYLOAD = "structured_payload"
|
||||
EXECUTION_REPORT = "execution_report"
|
||||
CLARIFICATION_REQUEST = "clarification_request"
|
||||
SAFETY_BLOCK = "safety_block"
|
||||
ERROR_REPORT = "error_report"
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
|
||||
class TaskTyping(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
primary: TaskType
|
||||
secondary: list[TaskType] = Field(default_factory=list, max_length=3)
|
||||
|
||||
|
||||
class ResultTyping(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
primary: ResultType
|
||||
secondary: list[ResultType] = Field(default_factory=list, max_length=3)
|
||||
|
||||
|
||||
class ExecutionMode(str, Enum):
|
||||
ONESTEP = "onestep"
|
||||
TOOL_ASSISTED = "tool_assisted"
|
||||
MULTISTEP = "multistep"
|
||||
|
||||
|
||||
class RunStatus(str, Enum):
|
||||
SUCCESS = "success"
|
||||
PARTIAL_SUCCESS = "partial_success"
|
||||
FAILED = "failed"
|
||||
|
||||
|
||||
@@ -74,59 +19,6 @@ class ToolStatus(str, Enum):
|
||||
PARTIAL = "partial"
|
||||
|
||||
|
||||
class KeyEntity(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
name: str
|
||||
type: str
|
||||
value: str | None = None
|
||||
|
||||
@field_validator("value", mode="before")
|
||||
@classmethod
|
||||
def normalize_value(cls, value: object) -> object:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, str):
|
||||
return value
|
||||
if isinstance(value, bool | int | float):
|
||||
return str(value)
|
||||
return value
|
||||
|
||||
|
||||
class ConstraintItem(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
key: str
|
||||
value: str
|
||||
required: bool = True
|
||||
|
||||
@field_validator("value", mode="before")
|
||||
@classmethod
|
||||
def normalize_value(cls, value: object) -> object:
|
||||
if isinstance(value, bool | int | float):
|
||||
return str(value)
|
||||
return value
|
||||
|
||||
|
||||
class NormalizedTaskInput(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
user_text: str
|
||||
multimodal_summary: list[str] = Field(default_factory=list)
|
||||
context_summary: str = Field(default="", max_length=2000)
|
||||
|
||||
|
||||
class RouterAgentOutput(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
normalized_task_input: NormalizedTaskInput
|
||||
key_entities: list[KeyEntity] = Field(default_factory=list)
|
||||
constraints: list[ConstraintItem] = Field(default_factory=list)
|
||||
task_typing: TaskTyping
|
||||
execution_mode: ExecutionMode
|
||||
result_typing: ResultTyping
|
||||
|
||||
|
||||
class ErrorInfo(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
@@ -151,12 +43,28 @@ class WorkerAgentOutputLite(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
status: RunStatus = RunStatus.SUCCESS
|
||||
answer: str
|
||||
key_points: list[str] = Field(default_factory=list)
|
||||
result_type: ResultType = ResultType.UNKNOWN
|
||||
suggested_actions: list[str] = Field(default_factory=list)
|
||||
sign_level: Literal["上上签", "中上签", "中下签"]
|
||||
summary: str = Field(min_length=1, max_length=300)
|
||||
conclusion: list[str] = Field(min_length=1, max_length=6)
|
||||
focus_points: list[str] = Field(default_factory=list, max_length=6)
|
||||
advice: list[str] = Field(min_length=1, max_length=6)
|
||||
keywords: list[str] = Field(min_length=3, max_length=8)
|
||||
answer: str = Field(min_length=1, max_length=4000)
|
||||
error: ErrorInfo | None = None
|
||||
|
||||
# Backward-compatible shadow fields for legacy consumers.
|
||||
key_points: list[str] = Field(default_factory=list, max_length=6)
|
||||
result_type: str = Field(default="structured_payload")
|
||||
suggested_actions: list[str] = Field(default_factory=list, max_length=6)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def sync_compatibility_fields(self) -> WorkerAgentOutputLite:
|
||||
if not self.key_points and self.focus_points:
|
||||
self.key_points = list(self.focus_points)
|
||||
if not self.suggested_actions and self.advice:
|
||||
self.suggested_actions = list(self.advice)
|
||||
return self
|
||||
|
||||
|
||||
class WorkerAgentOutputRich(WorkerAgentOutputLite):
|
||||
ui_hints: UiHintsPayload | None = None
|
||||
@@ -169,9 +77,5 @@ class AgentOutput(WorkerAgentOutputRich):
|
||||
WorkerAgentOutput = WorkerAgentOutputLite | WorkerAgentOutputRich
|
||||
|
||||
|
||||
def resolve_worker_output_model(
|
||||
execution_mode: ExecutionMode,
|
||||
) -> type[WorkerAgentOutputLite]:
|
||||
if execution_mode == ExecutionMode.ONESTEP:
|
||||
return WorkerAgentOutputLite
|
||||
return WorkerAgentOutputRich
|
||||
def resolve_worker_output_model() -> type[WorkerAgentOutputLite]:
|
||||
return WorkerAgentOutputLite
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""Reusable domain schemas shared across backend modules."""
|
||||
@@ -0,0 +1,124 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Protocol
|
||||
from uuid import UUID
|
||||
|
||||
from core.agentscope.tools.tool_config import AgentTool
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
from schemas.enums import AutomationJobStatus, ScheduleType
|
||||
|
||||
|
||||
class AutomationJobLike(Protocol):
|
||||
id: UUID
|
||||
owner_id: UUID
|
||||
bootstrap_key: str | None
|
||||
title: str
|
||||
config: dict[str, object]
|
||||
next_run_at: datetime
|
||||
timezone: str
|
||||
last_run_at: datetime | None
|
||||
status: AutomationJobStatus
|
||||
created_by: UUID | None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class ContextSource(str, Enum):
|
||||
LATEST_CHAT = "latest_chat"
|
||||
|
||||
|
||||
class ContextWindowMode(str, Enum):
|
||||
DAY = "day"
|
||||
NUMBER = "number"
|
||||
|
||||
|
||||
class MessageContextConfig(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
source: ContextSource = ContextSource.LATEST_CHAT
|
||||
window_mode: ContextWindowMode = ContextWindowMode.DAY
|
||||
window_count: int = Field(default=2, ge=1, le=200)
|
||||
|
||||
|
||||
class ScheduleRunAt(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
hour: int = Field(default=8, ge=0, le=23)
|
||||
minute: int = Field(default=0, ge=0, le=59)
|
||||
|
||||
|
||||
class ScheduleConfig(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
type: ScheduleType
|
||||
run_at: ScheduleRunAt
|
||||
weekdays: list[int] | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_weekdays(self) -> "ScheduleConfig":
|
||||
if self.type == ScheduleType.WEEKLY:
|
||||
if not self.weekdays:
|
||||
raise ValueError("weekdays is required when schedule type is weekly")
|
||||
invalid = [day for day in self.weekdays if day < 1 or day > 7]
|
||||
if invalid:
|
||||
raise ValueError("weekdays must be within 1-7")
|
||||
self.weekdays = sorted(set(self.weekdays))
|
||||
else:
|
||||
self.weekdays = None
|
||||
return self
|
||||
|
||||
|
||||
class RuntimeConfig(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
enabled_tools: list[AgentTool] = Field(default_factory=list, max_length=32)
|
||||
context: MessageContextConfig = Field(default_factory=MessageContextConfig)
|
||||
|
||||
|
||||
class AutomationJobConfig(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
enabled_tools: list[AgentTool] | None = Field(default=None, max_length=32)
|
||||
context: MessageContextConfig | None = None
|
||||
input_template: str | None = Field(default=None, min_length=1, max_length=4000)
|
||||
schedule: ScheduleConfig | None = None
|
||||
|
||||
|
||||
class AutomationJob(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
id: UUID
|
||||
owner_id: UUID
|
||||
bootstrap_key: str | None = Field(default=None, min_length=1, max_length=64)
|
||||
title: str = Field(..., min_length=1, max_length=255)
|
||||
config: AutomationJobConfig
|
||||
next_run_at: datetime
|
||||
timezone: str = Field(default="UTC", min_length=1, max_length=50)
|
||||
last_run_at: datetime | None = None
|
||||
status: AutomationJobStatus
|
||||
created_by: UUID | None = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
@classmethod
|
||||
def from_orm(cls, obj: object) -> "AutomationJob":
|
||||
return cls(
|
||||
id=getattr(obj, "id"),
|
||||
owner_id=getattr(obj, "owner_id"),
|
||||
bootstrap_key=getattr(obj, "bootstrap_key"),
|
||||
title=getattr(obj, "title"),
|
||||
config=AutomationJobConfig.model_validate(getattr(obj, "config", {}) or {}),
|
||||
next_run_at=getattr(obj, "next_run_at"),
|
||||
timezone=getattr(obj, "timezone"),
|
||||
last_run_at=getattr(obj, "last_run_at"),
|
||||
status=getattr(obj, "status"),
|
||||
created_by=getattr(obj, "created_by"),
|
||||
created_at=getattr(obj, "created_at"),
|
||||
updated_at=getattr(obj, "updated_at"),
|
||||
)
|
||||
|
||||
@property
|
||||
def is_system(self) -> bool:
|
||||
return self.bootstrap_key is not None
|
||||
@@ -0,0 +1,76 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any, ClassVar
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from schemas.agent.runtime_models import AgentOutput
|
||||
|
||||
from ..agent import AgentType, ToolAgentOutput
|
||||
|
||||
|
||||
class UserMessageAttachment(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="allow")
|
||||
|
||||
bucket: str
|
||||
path: str
|
||||
mime_type: str
|
||||
|
||||
|
||||
class AgentChatMessageMetadata(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="allow")
|
||||
run_id: str
|
||||
agent_type: AgentType | None = None
|
||||
user_message_attachments: list[UserMessageAttachment] | None = None
|
||||
tool_agent_output: ToolAgentOutput | None = None
|
||||
agent_output: AgentOutput | None = None
|
||||
|
||||
|
||||
class AgentChatMessage(BaseModel):
|
||||
"""Canonical schema aligned with `messages` table columns."""
|
||||
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
||||
|
||||
id: UUID
|
||||
seq: int
|
||||
role: str
|
||||
content: str
|
||||
model_code: str | None = None
|
||||
tool_name: str | None = None
|
||||
input_tokens: int = Field(default=0, ge=0)
|
||||
output_tokens: int = Field(default=0, ge=0)
|
||||
cost: Decimal = Field(default=Decimal("0"))
|
||||
latency_ms: int | None = Field(default=None, ge=0)
|
||||
metadata: AgentChatMessageMetadata | dict[str, object] | None = None
|
||||
timestamp: datetime
|
||||
|
||||
|
||||
def extract_user_message_attachments(
|
||||
metadata: AgentChatMessageMetadata | dict[str, object] | None,
|
||||
) -> list[UserMessageAttachment]:
|
||||
if metadata is None:
|
||||
return []
|
||||
|
||||
if isinstance(metadata, AgentChatMessageMetadata):
|
||||
raw_value: Any = metadata.user_message_attachments
|
||||
else:
|
||||
raw_value = metadata.get("user_message_attachments")
|
||||
|
||||
if raw_value is None:
|
||||
return []
|
||||
|
||||
raw_items: list[Any]
|
||||
if isinstance(raw_value, list):
|
||||
raw_items = raw_value
|
||||
else:
|
||||
raw_items = [raw_value]
|
||||
|
||||
attachments: list[UserMessageAttachment] = []
|
||||
for item in raw_items:
|
||||
try:
|
||||
attachments.append(UserMessageAttachment.model_validate(item))
|
||||
except Exception:
|
||||
continue
|
||||
return attachments
|
||||
@@ -0,0 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import ClassVar
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class SessionStateSnapshot(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="allow")
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,81 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import ClassVar, Literal, Union
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from schemas.enums import InboxMessageStatus, InboxMessageType
|
||||
|
||||
__all__ = [
|
||||
"InboxMessageType",
|
||||
"InboxMessageStatus",
|
||||
"CalendarInviteContent",
|
||||
"CalendarUpdateContent",
|
||||
"CalendarDeleteContent",
|
||||
"FriendshipContent",
|
||||
"CalendarContent",
|
||||
"InboxMessageContent",
|
||||
"parse_calendar_content",
|
||||
]
|
||||
|
||||
|
||||
class CalendarInviteContent(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
||||
|
||||
type: Literal["invite"]
|
||||
permission: int = Field(..., description="权限: 1=view, 4=edit, 8=invite")
|
||||
action: Literal["pending"] = "pending"
|
||||
|
||||
|
||||
class CalendarUpdateContent(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
||||
|
||||
type: Literal["update"]
|
||||
title: str = Field(..., description="事件标题")
|
||||
action: Literal["updated"] = "updated"
|
||||
|
||||
|
||||
class CalendarDeleteContent(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
||||
|
||||
type: Literal["delete"]
|
||||
title: str = Field(..., description="事件标题")
|
||||
action: Literal["deleted"] = "deleted"
|
||||
|
||||
|
||||
class FriendshipContent(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
||||
|
||||
type: Literal["request"]
|
||||
message: str | None = Field(None, description="好友申请消息")
|
||||
|
||||
|
||||
CalendarContent = Union[
|
||||
CalendarInviteContent,
|
||||
CalendarUpdateContent,
|
||||
CalendarDeleteContent,
|
||||
]
|
||||
|
||||
InboxMessageContent = Union[
|
||||
CalendarInviteContent,
|
||||
CalendarUpdateContent,
|
||||
CalendarDeleteContent,
|
||||
FriendshipContent,
|
||||
]
|
||||
|
||||
|
||||
def parse_calendar_content(content: str | None) -> CalendarContent | None:
|
||||
if not content:
|
||||
return None
|
||||
try:
|
||||
data = json.loads(content)
|
||||
content_type = data.get("type")
|
||||
if content_type == "invite":
|
||||
return CalendarInviteContent(**data)
|
||||
if content_type == "update":
|
||||
return CalendarUpdateContent(**data)
|
||||
if content_type == "delete":
|
||||
return CalendarDeleteContent(**data)
|
||||
raise ValueError(f"Unknown calendar content type: {content_type}")
|
||||
except Exception:
|
||||
return None
|
||||
@@ -0,0 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import ClassVar
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class InviteCodeRewardConfig(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="allow")
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,43 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import ClassVar, Literal
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from schemas.domain.memory_content import (
|
||||
TeamMember,
|
||||
UserMemoryContent,
|
||||
UserPreferences,
|
||||
WorkHabit,
|
||||
WorkProfileContent,
|
||||
WorkProject,
|
||||
)
|
||||
from schemas.enums import MemoryStatus, MemoryType
|
||||
|
||||
|
||||
class MemoryModel(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(
|
||||
extra="forbid", from_attributes=True
|
||||
)
|
||||
|
||||
id: UUID
|
||||
owner_id: UUID
|
||||
memory_type: Literal["user", "work"]
|
||||
content: UserMemoryContent | WorkProfileContent
|
||||
status: MemoryStatus
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
__all__ = [
|
||||
"MemoryModel",
|
||||
"MemoryStatus",
|
||||
"MemoryType",
|
||||
"TeamMember",
|
||||
"UserMemoryContent",
|
||||
"UserPreferences",
|
||||
"WorkHabit",
|
||||
"WorkProfileContent",
|
||||
"WorkProject",
|
||||
]
|
||||
@@ -0,0 +1,192 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
Weekday = Literal["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
|
||||
ProjectStatus = Literal["planned", "active", "paused", "completed"]
|
||||
PreferenceLevel = Literal["like", "neutral", "avoid"]
|
||||
MemorySource = Literal["user", "inferred", "calendar", "email", "agent"]
|
||||
|
||||
|
||||
class MemoryMeta(BaseModel):
|
||||
source: MemorySource | None = Field(default=None, description="记忆来源")
|
||||
confidence: float = Field(default=1.0, ge=0.0, le=1.0, description="置信度")
|
||||
last_updated_at: datetime | None = Field(default=None, description="最后更新时间")
|
||||
|
||||
|
||||
class TimeWindow(BaseModel):
|
||||
weekdays: list[Weekday] = Field(default_factory=list, description="适用星期")
|
||||
start: str = Field(description="开始时间,HH:MM")
|
||||
end: str = Field(description="结束时间,HH:MM")
|
||||
|
||||
|
||||
class PersonMemory(BaseModel):
|
||||
name: str = Field(description="人物姓名")
|
||||
relationship: str | None = Field(
|
||||
default=None, description="与用户关系,如家人/同事/导师/朋友"
|
||||
)
|
||||
role: str | None = Field(default=None, description="角色,如老板/导师/合作方")
|
||||
preferred_contact_channel: str | None = Field(
|
||||
default=None, description="偏好联系方式"
|
||||
)
|
||||
notes: str | None = Field(default=None, description="补充说明")
|
||||
meta: MemoryMeta = Field(default_factory=MemoryMeta)
|
||||
|
||||
|
||||
class PlaceMemory(BaseModel):
|
||||
name: str = Field(description="地点名称")
|
||||
category: str | None = Field(
|
||||
default=None, description="地点类别,如home/office/gym/cafe"
|
||||
)
|
||||
address: str | None = Field(default=None, description="地址")
|
||||
timezone: str | None = Field(default=None, description="地点时区")
|
||||
commute_minutes: int | None = Field(default=None, ge=0, description="典型通勤时长")
|
||||
preference: PreferenceLevel | None = Field(default=None, description="地点偏好")
|
||||
notes: str | None = Field(default=None, description="补充说明")
|
||||
meta: MemoryMeta = Field(default_factory=MemoryMeta)
|
||||
|
||||
|
||||
class UserPreferences(BaseModel):
|
||||
communication_style: str | None = Field(
|
||||
default=None, description="沟通风格,如简洁直接"
|
||||
)
|
||||
language_preference: list[str] = Field(default_factory=list, description="语言偏好")
|
||||
location_preference: str | None = Field(
|
||||
default=None, description="地点偏好,如喜欢远程"
|
||||
)
|
||||
work_lifestyle: str | None = Field(default=None, description="作息方式,如早睡早起")
|
||||
notification_preference: list[str] = Field(
|
||||
default_factory=list, description="通知方式偏好"
|
||||
)
|
||||
|
||||
|
||||
class SchedulingPreferences(BaseModel):
|
||||
productive_windows: list[TimeWindow] = Field(
|
||||
default_factory=list, description="高效率时段"
|
||||
)
|
||||
preferred_meeting_windows: list[TimeWindow] = Field(
|
||||
default_factory=list, description="偏好的会议时段"
|
||||
)
|
||||
no_meeting_windows: list[TimeWindow] = Field(
|
||||
default_factory=list, description="尽量不安排会议的时段"
|
||||
)
|
||||
deep_work_windows: list[TimeWindow] = Field(
|
||||
default_factory=list, description="深度工作时段"
|
||||
)
|
||||
preferred_meeting_duration_minutes: list[int] = Field(
|
||||
default_factory=lambda: [30, 60], description="偏好的会议时长"
|
||||
)
|
||||
meeting_buffer_minutes: int | None = Field(
|
||||
default=None, ge=0, description="会议间缓冲时间"
|
||||
)
|
||||
max_meetings_per_day: int | None = Field(
|
||||
default=None, ge=0, description="单日会议上限"
|
||||
)
|
||||
notes: str | None = Field(default=None, description="其他排程说明")
|
||||
|
||||
|
||||
class RecurringRoutine(BaseModel):
|
||||
name: str = Field(description="周期性安排名称")
|
||||
description: str | None = Field(default=None, description="周期性安排描述")
|
||||
cadence: str | None = Field(
|
||||
default=None, description="频率,如daily/weekly/monthly"
|
||||
)
|
||||
time_windows: list[TimeWindow] = Field(
|
||||
default_factory=list, description="通常发生时段"
|
||||
)
|
||||
importance: str | None = Field(default=None, description="重要程度")
|
||||
meta: MemoryMeta = Field(default_factory=MemoryMeta)
|
||||
|
||||
|
||||
class UserMemoryContent(BaseModel):
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
occupation: str | None = Field(default=None, description="职业")
|
||||
timezone: str | None = Field(default=None, description="时区")
|
||||
primary_language: str | None = Field(default=None, description="主要语言")
|
||||
people: list[PersonMemory] = Field(default_factory=list, description="重要人物")
|
||||
places: list[PlaceMemory] = Field(default_factory=list, description="常去地点")
|
||||
preferences: UserPreferences = Field(default_factory=UserPreferences)
|
||||
scheduling_preferences: SchedulingPreferences = Field(
|
||||
default_factory=SchedulingPreferences
|
||||
)
|
||||
interests: list[str] = Field(default_factory=list, description="兴趣爱好")
|
||||
avoid_topics: list[str] = Field(default_factory=list, description="不想讨论的话题")
|
||||
custom_rules: list[str] = Field(default_factory=list, description="用户自定义规则")
|
||||
recurring_routines: list[RecurringRoutine] = Field(
|
||||
default_factory=list, description="周期性习惯/安排"
|
||||
)
|
||||
|
||||
|
||||
class Milestone(BaseModel):
|
||||
name: str = Field(description="里程碑名称")
|
||||
due_date: date | None = Field(default=None, description="截止日期")
|
||||
status: str | None = Field(default=None, description="状态")
|
||||
notes: str | None = Field(default=None, description="补充说明")
|
||||
|
||||
|
||||
class WorkProject(BaseModel):
|
||||
name: str = Field(description="项目名")
|
||||
description: str | None = Field(default=None, description="项目描述")
|
||||
status: ProjectStatus | None = Field(default=None, description="项目状态")
|
||||
priority: str | None = Field(default=None, description="项目优先级")
|
||||
deadline: date | None = Field(default=None, description="项目截止时间")
|
||||
collaborators: list[str] = Field(default_factory=list, description="协作人")
|
||||
key_milestones: list[Milestone] = Field(
|
||||
default_factory=list, description="关键里程碑"
|
||||
)
|
||||
notes: str | None = Field(default=None, description="补充说明")
|
||||
meta: MemoryMeta = Field(default_factory=MemoryMeta)
|
||||
|
||||
|
||||
class WorkHabit(BaseModel):
|
||||
available_hours: list[TimeWindow] = Field(
|
||||
default_factory=list, description="常规可工作时间"
|
||||
)
|
||||
deep_work_blocks: list[TimeWindow] = Field(
|
||||
default_factory=list, description="偏好的深度工作时间"
|
||||
)
|
||||
preferred_meeting_windows: list[TimeWindow] = Field(
|
||||
default_factory=list, description="偏好的会议时间"
|
||||
)
|
||||
no_meeting_windows: list[TimeWindow] = Field(
|
||||
default_factory=list, description="不希望开会的时间"
|
||||
)
|
||||
preferred_meeting_duration_minutes: list[int] = Field(
|
||||
default_factory=lambda: [30, 60], description="偏好的会议时长"
|
||||
)
|
||||
notification_channel: str | None = Field(default=None, description="首选沟通渠道")
|
||||
notes: str | None = Field(default=None, description="补充说明")
|
||||
|
||||
|
||||
class TeamMember(BaseModel):
|
||||
name: str = Field(description="成员姓名")
|
||||
role: str | None = Field(default=None, description="团队角色")
|
||||
relationship: str | None = Field(
|
||||
default=None, description="关系,如直属上级/同事/合作方"
|
||||
)
|
||||
preferred_contact_channel: str | None = Field(
|
||||
default=None, description="偏好沟通渠道"
|
||||
)
|
||||
notes: str | None = Field(default=None, description="补充说明")
|
||||
meta: MemoryMeta = Field(default_factory=MemoryMeta)
|
||||
|
||||
|
||||
class WorkProfileContent(BaseModel):
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
occupation: str | None = Field(default=None, description="职业身份")
|
||||
expertise: list[str] = Field(default_factory=list, description="专业领域")
|
||||
preferred_tools: list[str] = Field(default_factory=list, description="惯用工具")
|
||||
current_projects: list[WorkProject] = Field(
|
||||
default_factory=list, description="长期项目画像"
|
||||
)
|
||||
work_habits: WorkHabit = Field(default_factory=WorkHabit)
|
||||
team_members: list[TeamMember] = Field(default_factory=list, description="团队成员")
|
||||
team_context: str | None = Field(default=None, description="团队背景")
|
||||
work_rules: list[str] = Field(
|
||||
default_factory=list, description="工作规则或默认原则"
|
||||
)
|
||||
@@ -0,0 +1,78 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Literal
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
from ..enums import (
|
||||
PointsChangeType,
|
||||
PointsOperatorType,
|
||||
)
|
||||
|
||||
|
||||
class PointsChargeSnapshot(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
message_id: UUID
|
||||
message_seq: int = Field(ge=1)
|
||||
model_code: str = Field(min_length=1, max_length=50)
|
||||
input_tokens: int = Field(ge=0)
|
||||
output_tokens: int = Field(ge=0)
|
||||
cost: Decimal = Field(ge=Decimal("0"))
|
||||
|
||||
|
||||
class PointsLedgerMetadataBase(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
schema_version: Literal[1] = 1
|
||||
operator_type: PointsOperatorType
|
||||
run_id: str = Field(min_length=1, max_length=128)
|
||||
request_id: str | None = Field(default=None, max_length=128)
|
||||
ext: dict[str, object] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class RegisterLedgerMetadata(PointsLedgerMetadataBase):
|
||||
pass
|
||||
|
||||
|
||||
class ConsumeLedgerMetadata(PointsLedgerMetadataBase):
|
||||
charge: PointsChargeSnapshot
|
||||
|
||||
|
||||
class GrantLedgerMetadata(PointsLedgerMetadataBase):
|
||||
charge: PointsChargeSnapshot | None = None
|
||||
|
||||
|
||||
class AdjustLedgerMetadata(PointsLedgerMetadataBase):
|
||||
charge: PointsChargeSnapshot | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_ticket(self) -> "AdjustLedgerMetadata":
|
||||
ticket_id = self.ext.get("ticket_id")
|
||||
if not isinstance(ticket_id, str) or not ticket_id.strip():
|
||||
raise ValueError("ext.ticket_id is required for adjust")
|
||||
return self
|
||||
|
||||
|
||||
PointsLedgerMetadata = (
|
||||
RegisterLedgerMetadata
|
||||
| ConsumeLedgerMetadata
|
||||
| GrantLedgerMetadata
|
||||
| AdjustLedgerMetadata
|
||||
)
|
||||
|
||||
|
||||
def parse_points_ledger_metadata(
|
||||
*,
|
||||
change_type: PointsChangeType,
|
||||
payload: dict[str, object],
|
||||
) -> PointsLedgerMetadata:
|
||||
if change_type == PointsChangeType.REGISTER:
|
||||
return RegisterLedgerMetadata.model_validate(payload)
|
||||
if change_type == PointsChangeType.CONSUME:
|
||||
return ConsumeLedgerMetadata.model_validate(payload)
|
||||
if change_type == PointsChangeType.GRANT:
|
||||
return GrantLedgerMetadata.model_validate(payload)
|
||||
return AdjustLedgerMetadata.model_validate(payload)
|
||||
@@ -0,0 +1,43 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import ClassVar, Literal
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from schemas.enums import ScheduleItemSourceType, ScheduleItemStatus
|
||||
|
||||
__all__ = [
|
||||
"AttachmentType",
|
||||
"ScheduleItemMetadataAttachment",
|
||||
"ScheduleItemMetadata",
|
||||
"ScheduleItemSourceType",
|
||||
"ScheduleItemStatus",
|
||||
]
|
||||
|
||||
|
||||
class AttachmentType(str, Enum):
|
||||
DOCUMENT = "document"
|
||||
REMINDER = "reminder"
|
||||
|
||||
|
||||
class ScheduleItemMetadataAttachment(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
||||
|
||||
name: str
|
||||
type: AttachmentType
|
||||
visible_to: list[UUID] = Field(default_factory=list)
|
||||
url: str | None = None
|
||||
note: str | None = None
|
||||
content: str | None = None
|
||||
|
||||
|
||||
class ScheduleItemMetadata(BaseModel):
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
||||
|
||||
color: str | None = Field(default=None, pattern=r"^#[0-9A-Fa-f]{6}$")
|
||||
location: str | None = None
|
||||
notes: str | None = None
|
||||
attachments: list[ScheduleItemMetadataAttachment] = Field(default_factory=list)
|
||||
reminder_minutes: int | None = Field(default=None, ge=0, le=10080)
|
||||
version: Literal[1] = 1
|
||||
@@ -0,0 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
TodoOrder = Annotated[int, Field(ge=0)]
|
||||
@@ -0,0 +1,153 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ScheduleItemStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
ARCHIVED = "archived"
|
||||
|
||||
|
||||
class ScheduleItemSourceType(str, Enum):
|
||||
MANUAL = "manual"
|
||||
IMPORTED = "imported"
|
||||
AGENT_GENERATED = "agent_generated"
|
||||
|
||||
|
||||
class AutomationJobStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
DISABLED = "disabled"
|
||||
|
||||
|
||||
class ScheduleType(str, Enum):
|
||||
DAILY = "daily"
|
||||
WEEKLY = "weekly"
|
||||
|
||||
|
||||
class MemoryType(str, Enum):
|
||||
USER = "user"
|
||||
WORK = "work"
|
||||
|
||||
|
||||
class MemoryStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
DISABLED = "disabled"
|
||||
|
||||
|
||||
class TodoStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
DONE = "done"
|
||||
CANCELED = "canceled"
|
||||
|
||||
|
||||
class TodoPriority(int, Enum):
|
||||
IMPORTANT_URGENT = 1
|
||||
IMPORTANT_NOT_URGENT = 2
|
||||
NOT_IMPORTANT_URGENT = 3
|
||||
NOT_IMPORTANT_NOT_URGENT = 4
|
||||
|
||||
|
||||
class AgentChatMessageRole(str, Enum):
|
||||
USER = "user"
|
||||
ASSISTANT = "assistant"
|
||||
SYSTEM = "system"
|
||||
TOOL = "tool"
|
||||
|
||||
|
||||
class AgentChatSessionStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
RUNNING = "running"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
|
||||
|
||||
class SessionType(str, Enum):
|
||||
CHAT = "chat"
|
||||
AUTOMATION = "automation"
|
||||
|
||||
|
||||
class PointsChangeType(str, Enum):
|
||||
REGISTER = "register"
|
||||
CONSUME = "consume"
|
||||
GRANT = "grant"
|
||||
ADJUST = "adjust"
|
||||
|
||||
|
||||
class PointsBizType(str, Enum):
|
||||
CHAT = "chat"
|
||||
|
||||
|
||||
class PointsOperatorType(str, Enum):
|
||||
USER = "user"
|
||||
SYSTEM = "system"
|
||||
ADMIN = "admin"
|
||||
|
||||
|
||||
class InboxMessageType(str, Enum):
|
||||
FRIEND_REQUEST = "friend_request"
|
||||
CALENDAR = "calendar"
|
||||
SYSTEM = "system"
|
||||
GROUP = "group"
|
||||
|
||||
|
||||
class InboxMessageStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
ACCEPTED = "accepted"
|
||||
REJECTED = "rejected"
|
||||
DISMISSED = "dismissed"
|
||||
|
||||
|
||||
class SubscriptionStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
PENDING = "pending"
|
||||
UNSUBSCRIBED = "unsubscribed"
|
||||
|
||||
|
||||
class NotifyLevel(str, Enum):
|
||||
ALL = "all"
|
||||
MENTIONS = "mentions"
|
||||
NONE = "none"
|
||||
|
||||
|
||||
class SubscriptionPermission(int, Enum):
|
||||
VIEW = 1
|
||||
INVITE = 2
|
||||
EDIT = 4
|
||||
DELETE = 8
|
||||
OWNER = 15 # VIEW | INVITE | EDIT | DELETE
|
||||
|
||||
|
||||
class FriendshipStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
ACCEPTED = "accepted"
|
||||
BLOCKED = "blocked"
|
||||
DECLINED = "declined"
|
||||
CANCELED = "canceled"
|
||||
|
||||
|
||||
class InviteCodeStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
DISABLED = "disabled"
|
||||
EXPIRED = "expired"
|
||||
|
||||
|
||||
class GroupStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
ARCHIVED = "archived"
|
||||
|
||||
|
||||
class GroupMemberRole(str, Enum):
|
||||
OWNER = "owner"
|
||||
ADMIN = "admin"
|
||||
MEMBER = "member"
|
||||
|
||||
|
||||
class GroupMemberSource(str, Enum):
|
||||
INVITED = "invited"
|
||||
JOINED = "joined"
|
||||
|
||||
|
||||
class GroupMemberStatus(str, Enum):
|
||||
ACTIVE = "active"
|
||||
MUTED = "muted"
|
||||
REMOVED = "removed"
|
||||
@@ -0,0 +1 @@
|
||||
"""Shared schemas used across multiple domain modules."""
|
||||
@@ -0,0 +1,88 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Literal
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
from ..domain.points import PointsLedgerMetadata
|
||||
from ..enums import PointsBizType, PointsChangeType
|
||||
|
||||
|
||||
class UserPointsSnapshot(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
user_id: UUID
|
||||
balance: int = Field(ge=0)
|
||||
frozen_balance: int = Field(ge=0)
|
||||
lifetime_earned: int = Field(ge=0)
|
||||
lifetime_spent: int = Field(ge=0)
|
||||
version: int = Field(ge=0)
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class PointsLedgerEntry(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: UUID
|
||||
user_id: UUID
|
||||
direction: Literal[1, -1]
|
||||
amount: int = Field(gt=0)
|
||||
balance_after: int = Field(ge=0)
|
||||
change_type: PointsChangeType
|
||||
biz_type: PointsBizType | None = None
|
||||
biz_id: UUID | None = None
|
||||
event_id: str = Field(min_length=1, max_length=64)
|
||||
operator_id: UUID | None = None
|
||||
metadata: PointsLedgerMetadata = Field(validation_alias="metadata_json")
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class ApplyPointsChangeCommand(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
user_id: UUID
|
||||
change_type: PointsChangeType
|
||||
biz_type: PointsBizType | None = None
|
||||
biz_id: UUID | None = None
|
||||
event_id: str = Field(min_length=1, max_length=64)
|
||||
amount: int = Field(gt=0)
|
||||
direction: Literal[1, -1]
|
||||
operator_id: UUID | None = None
|
||||
metadata: PointsLedgerMetadata
|
||||
occurred_at: datetime | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_change_type_contract(self) -> "ApplyPointsChangeCommand":
|
||||
if self.change_type == PointsChangeType.REGISTER:
|
||||
if (
|
||||
self.direction != 1
|
||||
or self.biz_type is not None
|
||||
or self.biz_id is not None
|
||||
):
|
||||
raise ValueError("register must use direction=1 and no biz binding")
|
||||
return self
|
||||
|
||||
if self.change_type == PointsChangeType.CONSUME:
|
||||
if (
|
||||
self.direction != -1
|
||||
or self.biz_type != PointsBizType.CHAT
|
||||
or self.biz_id is None
|
||||
):
|
||||
raise ValueError("consume must use direction=-1 and chat binding")
|
||||
return self
|
||||
|
||||
if self.change_type == PointsChangeType.GRANT:
|
||||
if (
|
||||
self.direction != 1
|
||||
or self.biz_type != PointsBizType.CHAT
|
||||
or self.biz_id is None
|
||||
):
|
||||
raise ValueError("grant must use direction=1 and chat binding")
|
||||
return self
|
||||
|
||||
if self.biz_type != PointsBizType.CHAT or self.biz_id is None:
|
||||
raise ValueError("adjust must use chat binding")
|
||||
return self
|
||||
@@ -0,0 +1,72 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Literal
|
||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
_BCP47_PATTERN = re.compile(r"^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$")
|
||||
_COUNTRY_PATTERN = re.compile(r"^[A-Z]{2}$")
|
||||
|
||||
|
||||
class PreferenceSettings(BaseModel):
|
||||
interface_language: str = "zh-CN"
|
||||
ai_language: str = "zh-CN"
|
||||
timezone: str = "Asia/Shanghai"
|
||||
country: str = "CN"
|
||||
|
||||
@field_validator("interface_language", "ai_language")
|
||||
@classmethod
|
||||
def validate_language(cls, value: str) -> str:
|
||||
if not _BCP47_PATTERN.fullmatch(value):
|
||||
raise ValueError("language must be a valid BCP-47 tag")
|
||||
return value
|
||||
|
||||
@field_validator("timezone")
|
||||
@classmethod
|
||||
def validate_timezone(cls, value: str) -> str:
|
||||
try:
|
||||
ZoneInfo(value)
|
||||
except ZoneInfoNotFoundError as exc:
|
||||
raise ValueError("timezone must be a valid IANA timezone") from exc
|
||||
return value
|
||||
|
||||
@field_validator("country")
|
||||
@classmethod
|
||||
def validate_country(cls, value: str) -> str:
|
||||
normalized = value.upper()
|
||||
if not _COUNTRY_PATTERN.fullmatch(normalized):
|
||||
raise ValueError("country must be an ISO 3166-1 alpha-2 code")
|
||||
return normalized
|
||||
|
||||
|
||||
class ProfileSettingsV1(BaseModel):
|
||||
version: Literal[1] = 1
|
||||
preferences: PreferenceSettings = Field(default_factory=PreferenceSettings)
|
||||
privacy: dict = Field(default_factory=dict)
|
||||
notification: dict = Field(default_factory=dict)
|
||||
|
||||
|
||||
ProfileSettingsUnion = ProfileSettingsV1
|
||||
|
||||
|
||||
def parse_profile_settings(raw: dict | None) -> ProfileSettingsUnion:
|
||||
payload = dict(raw or {})
|
||||
payload.setdefault("version", 1)
|
||||
return ProfileSettingsV1.model_validate(payload)
|
||||
|
||||
|
||||
def upgrade_to_latest(settings: ProfileSettingsUnion) -> ProfileSettingsV1:
|
||||
return settings
|
||||
|
||||
|
||||
class UserContext(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: str
|
||||
username: str
|
||||
email: str | None = None
|
||||
avatar_url: str | None = None
|
||||
bio: str | None = None
|
||||
settings: ProfileSettingsUnion | None = None
|
||||
Reference in New Issue
Block a user