Metadata-Version: 2.4
Name: pouchy-companion
Version: 0.1.1
Summary: Pouchy companion SDK for Python — thin httpx/SSE client + pydantic models for server-side integrators (game backends, bots, services).
Project-URL: Homepage, https://pouchy.ai/sdk
Project-URL: Documentation, https://pouchy.ai/sdk
Project-URL: Repository, https://github.com/oviswang/pouchy
Author: Pouchy
License-Expression: MIT
Keywords: agent,ai-companion,pouchy,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.5
Description-Content-Type: text/markdown

# pouchy-companion — Pouchy Companion SDK for Python

A thin, typed client for the [Pouchy](https://pouchy.ai) companion REST/SSE
plane — the same wire contract the JS SDK (`@pouchy_ai/companion-sdk`) speaks,
aimed at **server-side integrators**: game backends, bots, services.

- **httpx** (sync) + minimal SSE — no framework assumptions
- **pydantic v2** models for every envelope/payload
- `protocol.py` is **generated from the TypeScript source of truth** and
  drift-tested against it in CI — the two SDKs cannot silently diverge
- Voice/WebRTC and browser-only surfaces are deliberately out of scope
  (use the JS SDK for embeds)

## Install

```bash
pip install pouchy-companion
```

## Quickstart

Mint a session token from your backend (one per end user):

```bash
curl -X POST https://pouchy.ai/v1/sessions \
  -H "Authorization: Bearer pchy_sk_…" \
  -H "Content-Type: application/json" \
  -d '{ "agent": "<agentId>", "external_user_id": "player_42" }'
```

Then talk:

```python
from pouchy_companion import CompanionClient

with CompanionClient(base_url="https://pouchy.ai", token=session_token) as client:
    ack = client.connect()
    print("scopes:", ack.grantedScopes)

    # Request/response in one call — streams server-side, returns the reply.
    out = client.send_text("what should I do about the boss on floor 3?", await_reply=True)
    print(out["text"])
```

### Streaming deltas

```python
client.send_text(
    "tell me a story",
    await_reply=True,
    on_delta=lambda chunk, reset: print(chunk, end="", flush=True),
)
```

### The event stream (proactive messages, confirms, tool calls)

```python
from pouchy_companion import ConfirmRequestPayload, MessagePayload, ToolCallPayload

for env in client.events():           # SSE; resumes from the session cursor
    if isinstance(env.payload, MessagePayload):
        print("companion:", env.payload.text)
    elif isinstance(env.payload, ConfirmRequestPayload):
        # Platform session tokens may resolve confirms directly — your end
        # user IS this instance's human. Show your own confirm UI, then:
        res = client.confirm_action(env.payload.confirmId, approve=True)
        print("outcome:", res.outcome)
    elif isinstance(env.payload, ToolCallPayload):
        # An app-declared tool (declared via CompanionClient(tools=[...])).
        client.send_tool_result(env.payload.id, ok=True, result={"answer": 42})
```

### World state (play-along context)

```python
client.send_world_state({
    "type": "game.event.boss_spawned",
    "data": {"name": "Ashen Knight", "floor": 3},
    "salience": 0.9,
})
```

### Memory / history / wallet

```python
client.recall(query="player preferences", limit=10)
client.history(limit=20)
client.get_wallet()          # read-only (wallet.read)
client.end_session()
```

## Errors

Every failed call raises `CompanionError` with `.status`, `.code`
(machine-switchable — `rate_limited`, `turn_pending`, `confirm_resolved`, …
see `COMPANION_ERROR_CODES`) and `.retry_after` seconds when the server said
when to come back.

```python
from pouchy_companion import CompanionError

try:
    client.send_text("hi")
except CompanionError as e:
    if e.code == "rate_limited":
        time.sleep(e.retry_after or 1)
```

## Reference

The full REST semantics (scopes, confirm flow, world-state salience, limits)
are documented at [pouchy.ai/sdk](https://pouchy.ai/sdk) and in
`docs/companion-api-reference.md` — the wire contract is identical across
SDKs. Protocol vocabulary lives in `pouchy_companion.protocol`
(`PROTOCOL_VERSION`, `OUTBOUND_TYPES`, error code lists), generated from the
TypeScript source of truth.

## Development

```bash
# Regenerate protocol.py after a protocol.ts change (CI enforces parity):
node scripts/generate-protocol.mjs

# Tests (offline, MockTransport):
pip install -e . pytest && pytest
```
