Metadata-Version: 2.4
Name: pantheon-chat
Version: 0.1.0
Summary: Async Python client and chat component for the Pantheon agent platform
Author: Alethi Consulting Inc.
License-Expression: MIT
Project-URL: Homepage, https://pantheon-todo.alethiconsulting.com/docs/
Project-URL: Documentation, https://pantheon-todo.alethiconsulting.com/docs/
Project-URL: Source, https://github.com/alethibusiness/pantheon
Keywords: pantheon,chat,sse,agents
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: aiohttp>=3.9; extra == "dev"
Provides-Extra: streamlit
Requires-Dist: streamlit>=1.30; extra == "streamlit"
Dynamic: license-file

# pantheon-chat

Python client and chat component for the Pantheon agent platform. Talks to `/v1` with bearer auth, streams session events over SSE, reconnects with `Last-Event-ID`, and turns those events into UI updates any front end can render.

Requires Python 3.11+. Runtime dependency: `httpx`.

## Install

```bash
pip install pantheon-chat
```

From this folder (editable, with tests):

```bash
pip install -e ".[dev]"
```

Optional Streamlit extra:

```bash
pip install -e ".[streamlit]"
```

## Client (about 10 lines)

```python
import asyncio
from pantheon_chat import PantheonClient

async def main():
    async with PantheonClient("http://127.0.0.1:8000", "pk_your_key") as client:
        session = await client.create_session(agent_slug="todo-assistant")
        run = await client.start_run(session.id, "What is 15 + 15?")
        async for event in client.stream_events(session.id, run_id=run.run_id, stop_on_terminal=True):
            print(event.type, event.content or event.data)

asyncio.run(main())
```

Scripts can use the sync wrapper instead:

```python
from pantheon_chat import SyncPantheonClient

with SyncPantheonClient("http://127.0.0.1:8000", "pk_your_key") as client:
    session = client.create_session(agent_slug="todo-assistant")
    run = client.start_run(session.id, "Hello")
    for event in client.stream_events(session.id, run_id=run.run_id):
        print(event.type)
```

Application-tool pause: when you see `agent.custom_tool_use` then `session.status_idle` with `stop_reason.type == requires_action`, execute the tool locally and resume:

```python
await client.resume_with_tool_result(
    session.id,
    run.run_id,
    in_reply_to_event_id=tool_event.sse_id,
    content={"echoed": "ok"},
)
```

Busy session: `start_run` raises `SessionBusyError` with `active_run_id`. Recover with `cancel_run` or `resume_with_tool_result`. Archive with `archive_session`.

## Terminal chat

```bash
export PANTHEON_BASE_URL=http://127.0.0.1:8000
export PANTHEON_API_KEY=pk_your_key
export PANTHEON_AGENT_SLUG=todo-assistant
python -m pantheon_chat
```

Live tokens print as they stream. Application tools prompt `Approve? [y/N]` unless you pass `--auto-approve`. `/quit` exits.

Pass local tools from your own script:

```python
from pantheon_chat.cli import main

def echo_tool(payload):
    return {"echoed": payload["message"]}

raise SystemExit(main(tools={"echo_tool": echo_tool}))
```

## Streamlit

```bash
pip install "pantheon-chat[streamlit]"
pantheon-chat-streamlit
```

Enter base URL, API key, and agent slug in the sidebar. The same `PantheonChat` updates drive typing text and tool pills. The console script resolves the installed package location, so it works outside a source checkout.

Register application tools in an importable module:

```python
# my_tools.py
def echo_tool(payload):
    return {"echoed": payload["message"]}

TOOLS = {"echo_tool": echo_tool}
```

Then launch with the registry module. With auto-approve off, each application tool stays pending across reruns until Approve or Decline is clicked.

```bash
export PANTHEON_TOOLS_MODULE=my_tools
pantheon-chat-streamlit
```

An embedded Streamlit page can instead call `render_app(tools={...})` directly.

## Chat component

```python
from pantheon_chat import PantheonChat

chat = PantheonChat(
    "http://127.0.0.1:8000",
    "pk_your_key",
    "todo-assistant",
    tools={"echo_tool": lambda payload: {"echoed": payload["message"]}},
    require_approval=False,
)
async for update in chat.run("Call echo_tool with hi"):
    print(update.kind, update)
```

Update kinds: `typing`, `reasoning`, `tool` (one pill per `tool_call_id`, status `running` / `done` / `failed`), `approval`, `final`, `error`. When `require_approval` is set, call `chat.resolve_approval(tool_call_id, True)` after an `approval` update.

## Tests

```bash
pip install -e ".[dev]"
pytest
```

## Troubleshooting

**SESSION_BUSY (409).** Another run is queued, running, or waiting for a tool result. The error includes `active_run_id` and `recovery` (`resume` or `cancel`). Cancel that run, or resume it with a tool result, then send the new message.

**Stream stalls, then jumps.** The session stream sends a keep-alive comment every 15 seconds. If live deltas cannot be delivered, Pantheon emits `stream.resync` (no SSE id) and closes. This client reconnects with `Last-Event-ID` and re-reads persisted events. Ephemeral deltas are not replayed.

**Reconnect duplicates.** Last-Event-ID is the SSE `id:` line of a durable event, never the JSON `id` on a delta. This client records that cursor and drops already-seen durable ids.

**Tool call never finishes.** Resume must POST `user.custom_tool_result` to `/v1/sessions/{id}/runs/{run_id}/resume` with `Idempotency-Key` (8 to 255 characters) and `in_reply_to_event_id` set to the `agent.custom_tool_use` event id. Content must be a JSON object, not a string.

**4xx from create_session.** Provide `agent_slug` or `deployment_id`. The agent must already exist and be deployed.

**5xx / network loss.** JSON calls raise `PantheonHTTPError`. The event stream retries with backoff until `max_reconnects`. Raise `StreamError` if that budget is spent.

**Streamlit ImportError.** Install the extra: `pip install pantheon-chat[streamlit]`.
