# anycast-agent

Python SDK for [Anycast Agents](https://agents.anycast.com) P2P connectivity.

Connect agents to the Anycast network for peer-to-peer communication with relay fallback, powered by the global Anycast edge infrastructure.

## Installation

```bash
pip install anycast-agent
```

## Quick Start

```python
import asyncio
from anycast_agent import AnycastAgent

async def main():
    agent = AnycastAgent(
        rendezvous_url="wss://agents.anycast.com/rendezvous",
        token="agt_your_token_here",
    )

    @agent.on_connect
    async def on_connect(info):
        print(f"Connected as {info.agent_id}")

    @agent.on_peer
    async def on_peer(agent_id, status):
        print(f"Peer {agent_id} is {status}")

    await agent.connect()
    await agent.run_forever()

asyncio.run(main())
```

## API Reference

### `AnycastAgent(rendezvous_url, token, **kwargs)`

Create a new agent instance.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `rendezvous_url` | `str` | required | WebSocket URL of the rendezvous server |
| `token` | `str` | required | Agent auth token from the Anycast portal |
| `machine_id` | `str` | auto-generated | Unique machine identifier |
| `public_key` | `str` | auto-generated | Public key for E2E encryption |
| `version` | `str` | `None` | Agent version string |
| `reconnect` | `bool` | `True` | Auto-reconnect on disconnect |
| `reconnect_interval` | `float` | `3.0` | Base reconnect interval (seconds) |
| `timeout` | `float` | `30.0` | Connection timeout (seconds) |
| `heartbeat_interval` | `float` | `25.0` | Heartbeat interval (seconds) |

### Properties

| Property | Type | Description |
|----------|------|-------------|
| `agent_id` | `str \| None` | Assigned agent ID (set after connect) |
| `tenant_id` | `str \| None` | Tenant ID (set after connect) |
| `connected` | `bool` | Whether the agent is connected |
| `connection_state` | `ConnectionState` | Current state |

### Methods

#### `await agent.connect() -> AgentInfo`

Connect to the rendezvous server. Returns `AgentInfo` with `agent_id`, `tenant_id`, `name`.

#### `await agent.disconnect()`

Disconnect from the server.

#### `await agent.run_forever()`

Block until disconnected. Handles reconnection automatically.

#### `agent.request_connection(target_agent_id) -> str`

Request a P2P connection to another agent. Returns a session ID.

#### `await agent.send_signal(session_id, target_agent_id, signal)`

Send a WebRTC signal (offer/answer/ICE candidate).

#### `await agent.disconnect_session(session_id)`

Close a specific session.

### Events

Register handlers with decorators or `.on()`:

```python
# Decorator style
@agent.on_connect
async def handler(info): ...

# Method style
agent.on("connect", handler)
```

| Event | Handler Signature | Description |
|-------|-------------------|-------------|
| `connect` | `(info: AgentInfo)` | Connected and registered |
| `disconnect` | `(reason: str)` | Disconnected |
| `reconnecting` | `(attempt: int)` | Attempting reconnection |
| `peer` | `(agent_id: str, status: str)` | Peer online/offline |
| `connect_incoming` | `(session_id, from_id, from_name)` | Incoming connection |
| `connect_accepted` | `(session_id, agent_id, public_key)` | Connection accepted |
| `connect_rejected` | `(session_id, reason)` | Connection rejected |
| `signal` | `(session_id, from_id, signal)` | WebRTC signal |
| `relay_assigned` | `(session_id, host, port, token)` | Relay assigned |
| `error` | `(error: AnycastError)` | Error occurred |

Handlers can be sync or async — async handlers are awaited automatically.

### Exceptions

```python
from anycast_agent import (
    AnycastError,          # Base error (has .code, .fatal)
    AnycastConnectionError, # WebSocket connection failed
    AnycastAuthError,       # Token invalid (fatal)
    AnycastTimeoutError,    # Connection/registration timeout
)
```

### ConnectionState

```python
from anycast_agent import ConnectionState

ConnectionState.DISCONNECTED
ConnectionState.CONNECTING
ConnectionState.CONNECTED
ConnectionState.RECONNECTING
```

## Auto-Reconnection

By default, the agent reconnects with exponential backoff (3s, 6s, 12s, ..., max 30s). Disable with `reconnect=False`.

## License

MIT
