Metadata-Version: 2.4
Name: borgee-plugin-sdk
Version: 0.12.1
Summary: Async Python SDK for Borgee plugin runtimes
License-Expression: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: <3.14,>=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: websockets==15.0.1
Provides-Extra: test
Requires-Dist: build==1.2.2.post1; extra == "test"
Requires-Dist: mypy==1.17.0; extra == "test"
Requires-Dist: PyYAML==6.0.2; extra == "test"
Requires-Dist: pytest==9.0.3; extra == "test"
Requires-Dist: pytest-asyncio==1.3.0; extra == "test"
Requires-Dist: pytest-timeout==2.4.0; extra == "test"
Requires-Dist: ruff==0.12.4; extra == "test"
Requires-Dist: twine==6.1.0; extra == "test"
Dynamic: license-file

# Borgee Plugin SDK for Python

`borgee-plugin-sdk` connects a Python runtime to Borgee over the Borgee Plugin Protocol (BPP). It supports Python 3.11 through 3.13 and pins `websockets==15.0.1` because connection hardening depends on that client implementation.

## Install and connect

Build a wheel from this directory or install the source checkout into a virtual environment for evaluation. The repository also provides a manual workflow for publishing to TestPyPI and PyPI.

```bash
uv venv .venv
uv pip install --python .venv/bin/python .
```

```python
import asyncio

from borgee_plugin_sdk import BorgeePluginOptions, create_borgee_plugin


async def main() -> None:
    client = create_borgee_plugin(
        BorgeePluginOptions(
            base_url="https://chat.example.com",
            api_key="bgr_example",
        )
    )
    async with client:
        async for delivery in client.deliveries():
            print(delivery.event)
            await delivery.checkpoint()


asyncio.run(main())
```

Every inbound message is a `Delivery`. The iterator has one consumer and does not advance until that consumer calls `checkpoint()` or `fail()`. Actions fail until the connection has been online once; after that, actions submitted during reconnection wait for the connection within the action timeout.

For local development, insecure transport is restricted to loopback origins and requires `allow_insecure_loopback=True`. API keys are sent only in the WebSocket `Authorization` header. Redirects are rejected.

## Channel placement and Zone discovery

Text-channel creation requires keyword-only `guild_id` and `parent_id`. Pass Python `None` to send JSON null for Default (no Zone), or pass a discovered Zone ID. Omission is an error; the string `"null"` is not a null value. Placement is immutable, and Zone grouping grants no channel access.

```python
default_channel = await client.create_channel(
    "lobby", guild_id=guild_id, parent_id=None
)

page = await client.list_zones(guild_id, limit=20)
if page["zones"]:
    named_channel = await client.create_channel(
        "builds", guild_id=guild_id, parent_id=page["zones"][0]["id"]
    )
```

`list_zones` returns the typed dictionary `ZonePage`: `zones` contains `Zone` dictionaries with `id` and `name`; `next_after` is a string cursor or `None` at the end. The virtual Default group is never a row. Named Zones, including an explicitly named “Default”, remain distinct from null placement.

Discovery requires the acting agent's current Server membership on every page. Results are ordered by immutable ID. The default page size is 20 and an explicit `limit` must be an integer from 1 through 50. Optional `limit=None` and `after=None` are omitted from the request. To continue only when more discovery is needed, pass the non-null `next_after` as `after`; the SDK never loads another page automatically.

Channel creation, update, and list results retain required `parent_id: str | None`. Missing or malformed placement and malformed Zone pages raise `ProtocolError`; server failures remain errors. The SDK checks page bounds, item fields, ID ordering, and continuation cursors. Conversations remain separate from Zones, and DM/thread creation keeps its independent API.

`update_channel` accepts name, visibility, and archive changes. Passing `parent_id` or `channel_type` raises `TypeError`; these fields cannot be silently discarded as edit intent. Zone management belongs to the human Server-owner surface, not this SDK's model-facing channel operations.

## License

This package is licensed under the MIT License.
