from __future__ import annotations from enum import StrEnum from pydantic import BaseModel, Field, field_validator class DmPolicyEnum(StrEnum): PAIRING = "pairing" ALLOWLIST = "allowlist" OPEN = "open" DISABLED = "disabled" class GroupPolicyEnum(StrEnum): OPEN = "open" ALLOWLIST = "allowlist" DISABLED = "disabled" class IMessageConnectionConfig(BaseModel): server_url: str = "http://localhost:1234" password: str = "" probe_timeout_ms: int = Field(default=10000, ge=1000, le=60000) max_retries: int = Field(default=3, ge=1, le=10) http_timeout_s: float = Field(default=30.0, ge=1.0, le=300.0) class IMessageSecurityConfigSchema(BaseModel): dm_policy: DmPolicyEnum = DmPolicyEnum.PAIRING group_policy: GroupPolicyEnum = GroupPolicyEnum.ALLOWLIST allow_from: list[str] = Field(default_factory=list) group_allow_from: list[str] = Field(default_factory=list) require_mention: bool = False @field_validator("dm_policy", mode="before") @classmethod def coerce_dm_policy(cls, v): if isinstance(v, str): try: return DmPolicyEnum(v) except ValueError: return DmPolicyEnum.PAIRING return v @field_validator("group_policy", mode="before") @classmethod def coerce_group_policy(cls, v): if isinstance(v, str): try: return GroupPolicyEnum(v) except ValueError: return GroupPolicyEnum.ALLOWLIST return v class GroupOverrideConfig(BaseModel): enabled: bool | None = None require_mention: bool | None = None tools: list[str] | None = None class IMessageAccountConfig(BaseModel): account_id: str = "default" name: str = "" connection: IMessageConnectionConfig = Field(default_factory=IMessageConnectionConfig) security: IMessageSecurityConfigSchema = Field(default_factory=IMessageSecurityConfigSchema) enabled: bool = True default_to: str = "" class IMessageFullConfig(BaseModel): name: str = "iMessage" connection: IMessageConnectionConfig = Field(default_factory=IMessageConnectionConfig) security: IMessageSecurityConfigSchema = Field(default_factory=IMessageSecurityConfigSchema) config_writes: bool = True block_streaming: bool = False text_chunk_limit: int = Field(default=4096, ge=100, le=16000) media_max_mb: int = Field(default=100, ge=1, le=1000) history_limit: int = Field(default=20, ge=1, le=200) default_account: str = "" default_to: str = "" accounts: dict[str, IMessageAccountConfig] = Field(default_factory=dict) include_attachments: bool = False attachment_roots: list[str] = Field(default_factory=list) remote_attachment_roots: list[str] = Field(default_factory=list) groups: dict[str, GroupOverrideConfig] = Field(default_factory=dict) ws_reconnect_initial_delay: float = Field(default=5.0, ge=0.5, le=120.0) ws_reconnect_max_delay: float = Field(default=60.0, ge=1.0, le=600.0) loop_rate_limit: int = Field(default=5, ge=0, le=50) loop_rate_window_s: float = Field(default=60.0, ge=1.0, le=600.0) loop_cooldown_s: float = Field(default=120.0, ge=1.0, le=3600.0) @field_validator("accounts", mode="before") @classmethod def coerce_accounts(cls, v): if v is None: return {} if isinstance(v, list): return {a.account_id: a for a in v} return v def validate_config(config: dict) -> IMessageFullConfig: """校验并标准化 iMessage 配置。 Raises: ValidationError: 配置不符合 Schema 时抛出。 """ return IMessageFullConfig(**config)