feat(01-foundation-01): monorepo scaffolding, Docker Compose, and shared data models
- pyproject.toml: uv workspace with 5 member packages (shared, gateway, router, orchestrator, llm-pool) - docker-compose.yml: PostgreSQL 16 + Redis 7 + Ollama services on konstruct-net - .env.example: all required env vars documented, konstruct_app role (not superuser) - scripts/init-db.sh: creates konstruct_app role at DB init time - packages/shared/shared/config.py: Pydantic Settings loading all env vars - packages/shared/shared/models/message.py: KonstructMessage, ChannelType, SenderInfo, MessageContent - packages/shared/shared/models/tenant.py: Tenant, Agent, ChannelConnection SQLAlchemy 2.0 models - packages/shared/shared/models/auth.py: PortalUser model for admin portal auth - packages/shared/shared/db.py: async SQLAlchemy engine, session factory, get_session dependency - packages/shared/shared/rls.py: current_tenant_id ContextVar and configure_rls_hook with parameterized SET LOCAL - packages/shared/shared/redis_keys.py: tenant-namespaced key constructors (rate_limit, idempotency, session, engaged_thread)
This commit is contained in:
93
packages/shared/shared/models/message.py
Normal file
93
packages/shared/shared/models/message.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
KonstructMessage — the unified internal message format.
|
||||
|
||||
All channel adapters (Slack, WhatsApp, Mattermost, etc.) normalize inbound
|
||||
events into this format before passing them to the Message Router. The core
|
||||
business logic never depends on which messaging platform a message came from.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from enum import StrEnum
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ChannelType(StrEnum):
|
||||
"""Supported messaging channels."""
|
||||
|
||||
SLACK = "slack"
|
||||
WHATSAPP = "whatsapp"
|
||||
MATTERMOST = "mattermost"
|
||||
ROCKETCHAT = "rocketchat"
|
||||
TEAMS = "teams"
|
||||
TELEGRAM = "telegram"
|
||||
SIGNAL = "signal"
|
||||
|
||||
|
||||
class SenderInfo(BaseModel):
|
||||
"""Information about the message sender."""
|
||||
|
||||
user_id: str = Field(description="Channel-native user ID (e.g. Slack user ID U12345)")
|
||||
display_name: str = Field(description="Human-readable display name")
|
||||
email: str | None = Field(default=None, description="Sender email if available")
|
||||
is_bot: bool = Field(default=False, description="True if sender is a bot/automation")
|
||||
|
||||
|
||||
class MessageContent(BaseModel):
|
||||
"""The content of a message — text and optional attachments."""
|
||||
|
||||
text: str = Field(description="Plain text content of the message")
|
||||
html: str | None = Field(default=None, description="HTML-formatted content if available")
|
||||
attachments: list[dict[str, Any]] = Field(
|
||||
default_factory=list,
|
||||
description="File attachments, images, or structured payloads",
|
||||
)
|
||||
mentions: list[str] = Field(
|
||||
default_factory=list,
|
||||
description="List of user/bot IDs mentioned in the message",
|
||||
)
|
||||
|
||||
|
||||
class KonstructMessage(BaseModel):
|
||||
"""
|
||||
Unified internal message format for Konstruct.
|
||||
|
||||
All channel adapters normalize events into this format. Downstream services
|
||||
(Router, Orchestrator) operate exclusively on KonstructMessage — they never
|
||||
inspect channel-specific fields directly.
|
||||
|
||||
`tenant_id` is None immediately after normalization. The Message Router
|
||||
populates it via channel_connections lookup before forwarding.
|
||||
"""
|
||||
|
||||
id: str = Field(
|
||||
default_factory=lambda: str(uuid.uuid4()),
|
||||
description="Unique message ID (UUID)",
|
||||
)
|
||||
tenant_id: str | None = Field(
|
||||
default=None,
|
||||
description="Konstruct tenant ID — populated by Message Router after resolution",
|
||||
)
|
||||
channel: ChannelType = Field(description="Source messaging channel")
|
||||
channel_metadata: dict[str, Any] = Field(
|
||||
description="Channel-specific identifiers: workspace_id, channel_id, bot_user_id, etc."
|
||||
)
|
||||
sender: SenderInfo = Field(description="Message sender information")
|
||||
content: MessageContent = Field(description="Message content")
|
||||
timestamp: datetime = Field(description="Message timestamp (UTC)")
|
||||
thread_id: str | None = Field(
|
||||
default=None,
|
||||
description="Thread identifier for threaded conversations (e.g. Slack thread_ts)",
|
||||
)
|
||||
reply_to: str | None = Field(
|
||||
default=None,
|
||||
description="Parent message ID if this is a reply",
|
||||
)
|
||||
context: dict[str, Any] = Field(
|
||||
default_factory=dict,
|
||||
description="Extracted intent, entities, sentiment — populated by downstream processors",
|
||||
)
|
||||
Reference in New Issue
Block a user