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)
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user