Files
aicc-py/aicc/protocol.py
T
Emil Shanaty 0cfb4277b5 aicc-py 0.1.0: initial SDK
- protocol.py: pydantic models for all AICC message types (envelope,
  session_init, tool_call, tool_result, event, error, heartbeat)
- bridge.py: Bridge with @bridge.tool registration, capability checks,
  session management, serve_forever
- client.py: AICCClient with single background reader (safe on
  concurrent transports like WebSocket), call_tool, events, manifest
- tool.py: @tool decorator with schema generation from type hints
- schema.py: JSON Schema generation (str/int/float/bool, list, dict,
  Optional, pydantic models)
- transport: Transport protocol, InProcessTransport, WebSocket client+server
- tests: 12 passing (integration, schema, websocket roundtrip)
- examples: bridge_minimal.py + agent_minimal.py (verified end-to-end)
2026-08-08 03:10:35 +03:00

194 lines
4.6 KiB
Python

"""AICC protocol message types and enums."""
from __future__ import annotations
import uuid
from enum import Enum
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field
PROTOCOL_VERSION = "aicc/0.1"
class MessageType(str, Enum):
SESSION_INIT = "session_init"
SESSION_RESUME = "session_resume"
SESSION_CLOSE = "session_close"
MANIFEST_REQUEST = "manifest_request"
TOOL_CALL = "tool_call"
TOOL_RESULT = "tool_result"
EVENT = "event"
ERROR = "error"
HEARTBEAT = "heartbeat"
class TickMode(str, Enum):
FIXED = "fixed"
EVENT = "event"
HYBRID = "hybrid"
class WorldKind(str, Enum):
TWO_D = "2d"
THREE_D = "3d"
TEXT = "text"
ABSTRACT = "abstract"
class ModelClass(str, Enum):
EDGE_SMALL = "edge_small"
EDGE_MEDIUM = "edge_medium"
CLOUD_MEDIUM = "cloud_medium"
CLOUD_LARGE = "cloud_large"
class ToolClass(str, Enum):
SENSOR = "sensor"
ACTUATOR = "actuator"
GENERATOR = "generator"
class ErrorCode(str, Enum):
PROTOCOL_MISMATCH = "protocol_mismatch"
SESSION_EXPIRED = "session_expired"
TOOL_UNKNOWN = "tool_unknown"
TOOL_UNAVAILABLE = "tool_unavailable"
INVALID_INPUT = "invalid_input"
EXECUTION_FAILED = "execution_failed"
TIMEOUT = "timeout"
INTERNAL_ERROR = "internal_error"
class EventTopic(str, Enum):
TICK = "tick"
COLLISION = "collision"
AUDIO = "audio"
STATE_CHANGE = "state_change"
AGENT_MESSAGE = "agent_message"
def _uuid() -> str:
return str(uuid.uuid4())
class Envelope(BaseModel):
"""Common envelope for all AICC messages."""
model_config = ConfigDict(extra="forbid")
protocol: Literal["aicc/0.1"] = PROTOCOL_VERSION
session_id: str = Field(default_factory=_uuid)
message_id: str = Field(default_factory=_uuid)
class WorldSpec(BaseModel):
model_config = ConfigDict(extra="forbid")
name: str
kind: WorldKind
class ModelDecl(BaseModel):
model_config = ConfigDict(extra="forbid", populate_by_name=True)
class_: ModelClass = Field(alias="class")
expected_first_token_ms: int | None = None
expected_full_response_ms: int | None = None
class ToolLimits(BaseModel):
model_config = ConfigDict(extra="forbid")
calls_per_minute: int | None = None
calls_per_session: int | None = None
class ToolSpec(BaseModel):
"""Tool definition advertised in the session manifest."""
model_config = ConfigDict(extra="forbid", populate_by_name=True)
id: str = Field(pattern=r"^[a-z][a-z0-9_]*$")
class_: ToolClass = Field(alias="class")
description: str
input_schema: dict[str, Any]
output_schema: dict[str, Any]
requires_capability: str | None = None
limits: ToolLimits | None = None
strict_input: bool = False
class Capabilities(BaseModel):
model_config = ConfigDict(extra="forbid")
sensors: list[str] = Field(default_factory=list)
actuators: list[str] = Field(default_factory=list)
generators: list[str] = Field(default_factory=list)
class SessionInit(Envelope):
type: Literal[MessageType.SESSION_INIT] = MessageType.SESSION_INIT
tick_rate_hz: float = Field(gt=0)
tick_mode: TickMode
world: WorldSpec
agent_model: ModelDecl
capabilities: Capabilities
tools: list[ToolSpec]
event_subscriptions: list[str] = Field(default_factory=list)
class SessionClose(Envelope):
type: Literal[MessageType.SESSION_CLOSE] = MessageType.SESSION_CLOSE
reason: str | None = None
class ToolCall(Envelope):
type: Literal[MessageType.TOOL_CALL] = MessageType.TOOL_CALL
call_id: str
tool: str = Field(pattern=r"^[a-z][a-z0-9_]*$")
input: dict[str, Any] = Field(default_factory=dict)
class Meta(BaseModel):
model_config = ConfigDict(extra="allow")
tick: int | None = None
latency_ms: int | None = None
source: str | None = None
class ErrorPayload(BaseModel):
model_config = ConfigDict(extra="forbid")
code: ErrorCode
message: str
retryable: bool
class ToolResult(Envelope):
type: Literal[MessageType.TOOL_RESULT] = MessageType.TOOL_RESULT
call_id: str
ok: bool
output: dict[str, Any] | None = None
error: ErrorPayload | None = None
meta: Meta | None = None
class EventMessage(Envelope):
type: Literal[MessageType.EVENT] = MessageType.EVENT
topic: EventTopic | str
payload: dict[str, Any]
meta: Meta | None = None
class ErrorMessage(Envelope):
type: Literal[MessageType.ERROR] = MessageType.ERROR
error: ErrorPayload
class Heartbeat(Envelope):
type: Literal[MessageType.HEARTBEAT] = MessageType.HEARTBEAT
manifest_update: dict[str, Any] | None = None