# platform/interface.py from __future__ import annotations from collections.abc import AsyncIterator from datetime import datetime from typing import Any, Literal, Protocol from pydantic import BaseModel, Field class User(BaseModel): user_id: str external_id: str platform: str display_name: str | None = None created_at: datetime is_new: bool = False class Attachment(BaseModel): url: str | None = None mime_type: str | None = None size: int | None = None filename: str | None = None workspace_path: str | None = None class MessageResponse(BaseModel): message_id: str response: str tokens_used: int finished: bool attachments: list[Attachment] = Field(default_factory=list) class MessageChunk(BaseModel): """Один кусок стримингового ответа. При sync-режиме — единственный чанк с finished=True.""" message_id: str delta: str finished: bool tokens_used: int = 0 class UserSettings(BaseModel): skills: dict[str, bool] = {} connectors: dict[str, dict] = {} soul: dict[str, str] = {} # свободные поля: name, instructions и т.п. — без пресетов стилей safety: dict[str, bool] = {} plan: dict[str, Any] = {} class AgentEvent(BaseModel): """Webhook-уведомление от платформы — агент закончил долгую задачу.""" event_id: str user_id: str chat_id: str event_type: Literal["task_done", "task_error", "task_progress"] payload: dict[str, Any] = {} class PlatformError(Exception): def __init__(self, message: str, code: str = "PLATFORM_ERROR"): super().__init__(message) self.code = code class PlatformClient(Protocol): async def get_or_create_user( self, external_id: str, platform: str, display_name: str | None = None, ) -> User: ... # Sync — используем сейчас async def send_message( self, user_id: str, chat_id: str, text: str, attachments: list[Attachment] | None = None, ) -> MessageResponse: ... # Streaming — дверь открыта, мок отдаёт один чанк async def stream_message( self, user_id: str, chat_id: str, text: str, attachments: list[Attachment] | None = None, ) -> AsyncIterator[MessageChunk]: ... async def get_settings(self, user_id: str) -> UserSettings: ... async def update_settings(self, user_id: str, action: Any) -> None: ... class WebhookReceiver(Protocol): """Регистрируется в боте. Платформа зовёт нас когда агент закончил долгую задачу.""" async def on_agent_event(self, event: AgentEvent) -> None: ...