pi-py-sdk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pi_py_sdk-0.1.0/.github/workflows/ci.yml +58 -0
- pi_py_sdk-0.1.0/.github/workflows/publish.yml +26 -0
- pi_py_sdk-0.1.0/.gitignore +17 -0
- pi_py_sdk-0.1.0/CLAUDE.md +89 -0
- pi_py_sdk-0.1.0/LICENSE +21 -0
- pi_py_sdk-0.1.0/PKG-INFO +155 -0
- pi_py_sdk-0.1.0/README.md +129 -0
- pi_py_sdk-0.1.0/docs/python-sdk-plan.md +236 -0
- pi_py_sdk-0.1.0/examples/one_shot.py +32 -0
- pi_py_sdk-0.1.0/examples/sync_usage.py +31 -0
- pi_py_sdk-0.1.0/examples/with_approvals.py +47 -0
- pi_py_sdk-0.1.0/pyproject.toml +52 -0
- pi_py_sdk-0.1.0/src/pi_py_agent/__init__.py +8 -0
- pi_py_sdk-0.1.0/src/pi_py_agent/app.py +337 -0
- pi_py_sdk-0.1.0/src/pi_py_agent/cli.py +53 -0
- pi_py_sdk-0.1.0/src/pi_py_agent/py.typed +0 -0
- pi_py_sdk-0.1.0/src/pi_py_agent/render.py +137 -0
- pi_py_sdk-0.1.0/src/pi_py_sdk/__init__.py +104 -0
- pi_py_sdk-0.1.0/src/pi_py_sdk/_discovery.py +41 -0
- pi_py_sdk-0.1.0/src/pi_py_sdk/client.py +436 -0
- pi_py_sdk-0.1.0/src/pi_py_sdk/config.py +51 -0
- pi_py_sdk-0.1.0/src/pi_py_sdk/errors.py +37 -0
- pi_py_sdk-0.1.0/src/pi_py_sdk/jsonl.py +66 -0
- pi_py_sdk-0.1.0/src/pi_py_sdk/protocol.py +310 -0
- pi_py_sdk-0.1.0/src/pi_py_sdk/py.typed +0 -0
- pi_py_sdk-0.1.0/src/pi_py_sdk/sync.py +123 -0
- pi_py_sdk-0.1.0/src/pi_py_sdk/transport.py +132 -0
- pi_py_sdk-0.1.0/tests/test_client_fake.py +87 -0
- pi_py_sdk-0.1.0/tests/test_commands.py +99 -0
- pi_py_sdk-0.1.0/tests/test_integration.py +94 -0
- pi_py_sdk-0.1.0/tests/test_jsonl.py +81 -0
- pi_py_sdk-0.1.0/tests/test_messages.py +88 -0
- pi_py_sdk-0.1.0/tests/test_protocol.py +71 -0
- pi_py_sdk-0.1.0/tests/test_render.py +105 -0
- pi_py_sdk-0.1.0/tests/test_repl_helpers.py +49 -0
- pi_py_sdk-0.1.0/tests/test_sync.py +77 -0
- pi_py_sdk-0.1.0/tests/test_ui_and_events.py +150 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
name: unit (py${{ matrix.python-version }})
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
- run: python -m pip install --upgrade pip
|
|
22
|
+
- run: pip install -e ".[dev]"
|
|
23
|
+
- name: Run unit tests
|
|
24
|
+
run: pytest -q
|
|
25
|
+
|
|
26
|
+
build:
|
|
27
|
+
name: build wheel
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
- uses: actions/setup-python@v5
|
|
32
|
+
with:
|
|
33
|
+
python-version: "3.12"
|
|
34
|
+
- run: pip install build
|
|
35
|
+
- run: python -m build
|
|
36
|
+
- uses: actions/upload-artifact@v4
|
|
37
|
+
with:
|
|
38
|
+
name: dist
|
|
39
|
+
path: dist/*
|
|
40
|
+
|
|
41
|
+
integration:
|
|
42
|
+
name: integration (live pi, best-effort)
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
# Best-effort: spins up a real `pi` to smoke the bridge. Never blocks merges,
|
|
45
|
+
# since it depends on the upstream npm package and default-model availability.
|
|
46
|
+
continue-on-error: true
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v4
|
|
49
|
+
- uses: actions/setup-python@v5
|
|
50
|
+
with:
|
|
51
|
+
python-version: "3.12"
|
|
52
|
+
- uses: actions/setup-node@v4
|
|
53
|
+
with:
|
|
54
|
+
node-version: "20"
|
|
55
|
+
- run: npm install -g @earendil-works/pi-coding-agent
|
|
56
|
+
- run: pip install -e ".[dev]"
|
|
57
|
+
- name: Run integration tests (non-LLM)
|
|
58
|
+
run: pytest -m integration -q
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
# Builds the distribution on every release and publishes it to PyPI.
|
|
4
|
+
#
|
|
5
|
+
# Setup required once: configure a PyPI Trusted Publisher for this repo/workflow
|
|
6
|
+
# (https://docs.pypi.org/trusted-publishers/) so no API token secret is needed.
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
release:
|
|
10
|
+
types: [published]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
environment: pypi
|
|
16
|
+
permissions:
|
|
17
|
+
id-token: write # OIDC token for PyPI trusted publishing
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
- run: pip install build
|
|
24
|
+
- run: python -m build
|
|
25
|
+
- name: Publish to PyPI
|
|
26
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Guidance for working in this repository.
|
|
4
|
+
|
|
5
|
+
## What this is
|
|
6
|
+
|
|
7
|
+
`pi-py-sdk` is a Python SDK that drives **Pi**'s agent runtime (`pi-agent-core`, written
|
|
8
|
+
in TypeScript) over Pi's **RPC mode** (`pi --mode rpc`, strict JSONL over stdin/stdout),
|
|
9
|
+
plus a terminal coding agent (`pi-py`) built on that SDK.
|
|
10
|
+
|
|
11
|
+
**Core principle: no agent logic is reimplemented in Python.** The agent loop, tool
|
|
12
|
+
calling, sessions, compaction, retries, and provider auth all run inside the `pi`
|
|
13
|
+
subprocess. This package is a transport + protocol + ergonomics layer over that
|
|
14
|
+
subprocess. When extending it, prefer exposing an existing Pi RPC command/event over
|
|
15
|
+
adding behavior here.
|
|
16
|
+
|
|
17
|
+
## Layout
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
src/pi_py_sdk/ # the SDK (the bridge)
|
|
21
|
+
jsonl.py # strict LF-only JSONL framing (mirrors Pi's jsonl.ts)
|
|
22
|
+
transport.py # async subprocess lifecycle, stdout framing, stderr ring buffer
|
|
23
|
+
protocol.py # Pydantic models: commands, responses, events, messages
|
|
24
|
+
client.py # PiAgent — async API, id-correlated requests, prompt streaming
|
|
25
|
+
sync.py # PiAgentSync — blocking facade over a background-thread loop
|
|
26
|
+
config.py # PiConfig (CLI args + env)
|
|
27
|
+
_discovery.py # resolve `pi`: PATH -> npx fallback (pinned version)
|
|
28
|
+
errors.py # PiError hierarchy
|
|
29
|
+
src/pi_py_agent/ # the terminal coding agent (consumes the SDK only)
|
|
30
|
+
render.py # stream events -> terminal (text/thinking/tool/queue)
|
|
31
|
+
app.py # async REPL + one-shot runner, mid-turn steering, approvals
|
|
32
|
+
cli.py # `pi-py` console entry point
|
|
33
|
+
tests/ # pytest; unit tests need no Node (fake transports)
|
|
34
|
+
docs/python-sdk-plan.md # full design + roadmap
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Non-obvious invariants — read before editing the protocol
|
|
38
|
+
|
|
39
|
+
- **JSONL framing is LF-only.** Split stdout on `\n` only; never on U+2028/U+2029 (valid
|
|
40
|
+
inside JSON strings). Strip a single trailing `\r`. See `jsonl.py`; this must stay
|
|
41
|
+
byte-compatible with Pi's `packages/coding-agent/src/modes/rpc/jsonl.ts`.
|
|
42
|
+
- **A `prompt` response means preflight succeeded, not that the turn finished.**
|
|
43
|
+
Completion is the `agent_end` event — and only when `agent_end.willRetry == False`.
|
|
44
|
+
`willRetry == True` is followed by an `auto_retry_*` cycle and another `agent_end`.
|
|
45
|
+
`prompt_stream`/`wait_for_idle` depend on this.
|
|
46
|
+
- **Wire field names are camelCase** (`assistantMessageEvent`, `toolCallId`, `willRetry`,
|
|
47
|
+
`modelId`). Models keep those names verbatim. All models use `extra="allow"` so new Pi
|
|
48
|
+
fields/events degrade gracefully — don't tighten this.
|
|
49
|
+
- **Auth lives in Pi, not here.** The transport forwards `os.environ`; Pi resolves all
|
|
50
|
+
provider keys/OAuth. Never add key handling in Python.
|
|
51
|
+
- **`bash` command vs the bash tool:** the `bash` RPC command stores a
|
|
52
|
+
BashExecutionMessage that is only sent to the LLM on the *next* prompt.
|
|
53
|
+
- **`extension_ui_response` is not id-correlated** — it's written directly to stdin, not
|
|
54
|
+
via the request/response (`_send`) path.
|
|
55
|
+
|
|
56
|
+
## Dev commands
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install -e ".[dev]"
|
|
60
|
+
pytest # unit tests (no Node; integration deselected via addopts)
|
|
61
|
+
pytest -m integration # live tests; needs `pi` on PATH (no key needed)
|
|
62
|
+
PI_LIVE_LLM=1 pytest -m integration # also runs the prompt-completion test (needs a model)
|
|
63
|
+
python -m build # build wheel + sdist
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
There is no linter/formatter configured. Match the surrounding style: 4-space indent,
|
|
67
|
+
type hints, `from __future__ import annotations`, Google-ish docstrings.
|
|
68
|
+
|
|
69
|
+
## Testing approach
|
|
70
|
+
|
|
71
|
+
Unit tests inject a **fake transport** (see `tests/test_client_fake.py`,
|
|
72
|
+
`test_ui_and_events.py`, `test_commands.py`) or a fake agent (`test_sync.py`) so nothing
|
|
73
|
+
spawns Node. Pure helpers (framing, event/message parsing, REPL routing, rendering) are
|
|
74
|
+
tested directly. Live behavior goes in `tests/test_integration.py` behind the
|
|
75
|
+
`integration` marker (skipped automatically when `pi` is absent).
|
|
76
|
+
|
|
77
|
+
## Release
|
|
78
|
+
|
|
79
|
+
Version lives in `pyproject.toml` and `src/pi_py_sdk/__init__.py` (keep them in sync).
|
|
80
|
+
CI (`.github/workflows/ci.yml`) runs the unit matrix (3.10–3.13), builds the wheel, and
|
|
81
|
+
best-effort-smokes a real `pi`. Publishing to PyPI happens on a GitHub Release via
|
|
82
|
+
`.github/workflows/publish.yml` using Trusted Publishing (OIDC, no token). To cut a
|
|
83
|
+
release: bump the version, then `gh release create vX.Y.Z --generate-notes`.
|
|
84
|
+
|
|
85
|
+
## Git
|
|
86
|
+
|
|
87
|
+
Work on a feature branch and fast-forward into `main`; `main` tracks
|
|
88
|
+
`origin` (github.com/noclaw/pi-py). End commit messages with the
|
|
89
|
+
`Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>` trailer.
|
pi_py_sdk-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NoClaw.ai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pi_py_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pi-py-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the Pi coding agent (pi-agent-core) over its RPC bridge
|
|
5
|
+
Project-URL: Homepage, https://github.com/earendil-works/pi
|
|
6
|
+
Author: Jeff Jacobsen
|
|
7
|
+
License: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: agent,coding-agent,llm,pi,rpc
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: pydantic<3,>=2.6
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# pi-py-sdk
|
|
28
|
+
|
|
29
|
+
[](https://github.com/noclaw/pi-py/actions/workflows/ci.yml)
|
|
30
|
+
|
|
31
|
+
Python SDK for the [Pi](https://pi.dev) coding agent. It drives `pi-agent-core` — the
|
|
32
|
+
well-tested TypeScript agent runtime — over Pi's **RPC mode** (`pi --mode rpc`, strict
|
|
33
|
+
JSONL over stdin/stdout), so the agent loop, tool calling, sessions, compaction,
|
|
34
|
+
retries, and provider auth all run inside Pi. No agent logic is reimplemented in Python.
|
|
35
|
+
|
|
36
|
+
It includes the bridge core (transport, strict JSONL framing, id-correlated commands,
|
|
37
|
+
streaming), the full RPC command surface, typed events and message models, the
|
|
38
|
+
interactive **extension-UI sub-protocol** (tool approvals/dialogs), and a synchronous
|
|
39
|
+
facade (`PiAgentSync`). A terminal coding agent (`pi-py`) ships on top. See
|
|
40
|
+
[`docs/python-sdk-plan.md`](docs/python-sdk-plan.md) for the design.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install pi-py-sdk
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This installs the `pi_py_sdk` library and the `pi-py` agent CLI. You also need the Pi
|
|
49
|
+
runtime for live use:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm i -g @earendil-works/pi-coding-agent # provides the `pi` binary
|
|
53
|
+
export ANTHROPIC_API_KEY=... # or another supported provider key
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If `pi` isn't on `PATH`, the SDK falls back to `npx --yes @earendil-works/pi-coding-agent@<pinned>`.
|
|
57
|
+
|
|
58
|
+
### Development
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
python -m venv .venv && source .venv/bin/activate
|
|
62
|
+
pip install -e ".[dev]"
|
|
63
|
+
pytest
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import asyncio
|
|
70
|
+
from pi_py_sdk import PiAgent, MessageUpdateEvent
|
|
71
|
+
|
|
72
|
+
async def main():
|
|
73
|
+
async with PiAgent(model="anthropic/claude-sonnet-4-20250514", cwd=".") as agent:
|
|
74
|
+
async for ev in agent.prompt_stream("List the Python files here"):
|
|
75
|
+
if isinstance(ev, MessageUpdateEvent) and ev.assistantMessageEvent:
|
|
76
|
+
ame = ev.assistantMessageEvent
|
|
77
|
+
if ame.type == "text_delta" and ame.delta:
|
|
78
|
+
print(ame.delta, end="", flush=True)
|
|
79
|
+
|
|
80
|
+
asyncio.run(main())
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A prompt completes on an `agent_end` event with `willRetry == False` (an `agent_end`
|
|
84
|
+
with `willRetry == True` is followed by an automatic retry).
|
|
85
|
+
|
|
86
|
+
### Synchronous use
|
|
87
|
+
|
|
88
|
+
For non-async code, `PiAgentSync` runs the agent on a background loop and blocks:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from pi_py_sdk import PiAgentSync, message_text
|
|
92
|
+
|
|
93
|
+
with PiAgentSync(model="anthropic/claude-sonnet-4-20250514") as agent:
|
|
94
|
+
for event in agent.prompt_stream("hello"):
|
|
95
|
+
...
|
|
96
|
+
for msg in agent.get_messages(): # typed messages
|
|
97
|
+
print(msg.role, message_text(msg))
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Tool approvals
|
|
101
|
+
|
|
102
|
+
Extensions request decisions (allow this tool? pick an option? enter a value?) via the
|
|
103
|
+
extension-UI sub-protocol. Install a handler with `on_ui_request`; without one, the SDK
|
|
104
|
+
safely denies confirmations and cancels other dialogs so the agent never hangs.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
def approve(req):
|
|
108
|
+
if req.method == "confirm":
|
|
109
|
+
return True # allow
|
|
110
|
+
if req.method == "select":
|
|
111
|
+
return (req.options or [None])[0]
|
|
112
|
+
return None # cancel input/editor
|
|
113
|
+
|
|
114
|
+
agent.on_ui_request(approve) # see examples/with_approvals.py
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The full command surface (`set_model`, `bash`, `compact`, `fork`, `get_session_stats`,
|
|
118
|
+
steering/follow-up modes, …) is available as async methods on `PiAgent`.
|
|
119
|
+
|
|
120
|
+
## The `pi-py` coding agent
|
|
121
|
+
|
|
122
|
+
The repo also ships `pi_py_agent`, a small terminal coding agent built entirely on the
|
|
123
|
+
SDK (the agent loop, tools, and model calls all run inside Pi). Installing the package
|
|
124
|
+
provides a `pi-py` command:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pi-py # interactive REPL
|
|
128
|
+
pi-py --print "Run the tests and summarize failures" # one-shot
|
|
129
|
+
pi-py --model anthropic/claude-sonnet-4-20250514 --no-session
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
It streams assistant text, thinking, and tool activity (with result previews) to the
|
|
133
|
+
terminal, answers approval dialogs interactively, and supports slash commands (`/help`,
|
|
134
|
+
`/model`, `/models`, `/new`, `/state`, `/compact`, `/clone`, `/fork`, `/exit`). While
|
|
135
|
+
the agent is responding you can **steer** it by typing (or `+text` to queue a
|
|
136
|
+
follow-up). Ctrl-C aborts the current turn; Ctrl-D exits.
|
|
137
|
+
|
|
138
|
+
## Tests
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
pytest # unit tests (no Node required); integration is deselected by default
|
|
142
|
+
pytest -m integration # live tests against a real `pi` (needs the binary on PATH)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The integration tests avoid LLM calls (state, models, bash), so they don't need a
|
|
146
|
+
provider key. The one prompt-completion test additionally needs a working model and is
|
|
147
|
+
skipped unless `PI_LIVE_LLM=1` is set.
|
|
148
|
+
|
|
149
|
+
## Releasing
|
|
150
|
+
|
|
151
|
+
CI (`.github/workflows/ci.yml`) runs the unit suite across Python 3.10–3.13, builds the
|
|
152
|
+
wheel, and best-effort-smokes a real `pi` on every push/PR. Publishing
|
|
153
|
+
(`.github/workflows/publish.yml`) builds and uploads to PyPI when a GitHub Release is
|
|
154
|
+
published — it uses [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/)
|
|
155
|
+
(OIDC, no token secret), which must be configured once for the repo.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# pi-py-sdk
|
|
2
|
+
|
|
3
|
+
[](https://github.com/noclaw/pi-py/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
Python SDK for the [Pi](https://pi.dev) coding agent. It drives `pi-agent-core` — the
|
|
6
|
+
well-tested TypeScript agent runtime — over Pi's **RPC mode** (`pi --mode rpc`, strict
|
|
7
|
+
JSONL over stdin/stdout), so the agent loop, tool calling, sessions, compaction,
|
|
8
|
+
retries, and provider auth all run inside Pi. No agent logic is reimplemented in Python.
|
|
9
|
+
|
|
10
|
+
It includes the bridge core (transport, strict JSONL framing, id-correlated commands,
|
|
11
|
+
streaming), the full RPC command surface, typed events and message models, the
|
|
12
|
+
interactive **extension-UI sub-protocol** (tool approvals/dialogs), and a synchronous
|
|
13
|
+
facade (`PiAgentSync`). A terminal coding agent (`pi-py`) ships on top. See
|
|
14
|
+
[`docs/python-sdk-plan.md`](docs/python-sdk-plan.md) for the design.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install pi-py-sdk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This installs the `pi_py_sdk` library and the `pi-py` agent CLI. You also need the Pi
|
|
23
|
+
runtime for live use:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm i -g @earendil-works/pi-coding-agent # provides the `pi` binary
|
|
27
|
+
export ANTHROPIC_API_KEY=... # or another supported provider key
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If `pi` isn't on `PATH`, the SDK falls back to `npx --yes @earendil-works/pi-coding-agent@<pinned>`.
|
|
31
|
+
|
|
32
|
+
### Development
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python -m venv .venv && source .venv/bin/activate
|
|
36
|
+
pip install -e ".[dev]"
|
|
37
|
+
pytest
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import asyncio
|
|
44
|
+
from pi_py_sdk import PiAgent, MessageUpdateEvent
|
|
45
|
+
|
|
46
|
+
async def main():
|
|
47
|
+
async with PiAgent(model="anthropic/claude-sonnet-4-20250514", cwd=".") as agent:
|
|
48
|
+
async for ev in agent.prompt_stream("List the Python files here"):
|
|
49
|
+
if isinstance(ev, MessageUpdateEvent) and ev.assistantMessageEvent:
|
|
50
|
+
ame = ev.assistantMessageEvent
|
|
51
|
+
if ame.type == "text_delta" and ame.delta:
|
|
52
|
+
print(ame.delta, end="", flush=True)
|
|
53
|
+
|
|
54
|
+
asyncio.run(main())
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
A prompt completes on an `agent_end` event with `willRetry == False` (an `agent_end`
|
|
58
|
+
with `willRetry == True` is followed by an automatic retry).
|
|
59
|
+
|
|
60
|
+
### Synchronous use
|
|
61
|
+
|
|
62
|
+
For non-async code, `PiAgentSync` runs the agent on a background loop and blocks:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from pi_py_sdk import PiAgentSync, message_text
|
|
66
|
+
|
|
67
|
+
with PiAgentSync(model="anthropic/claude-sonnet-4-20250514") as agent:
|
|
68
|
+
for event in agent.prompt_stream("hello"):
|
|
69
|
+
...
|
|
70
|
+
for msg in agent.get_messages(): # typed messages
|
|
71
|
+
print(msg.role, message_text(msg))
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Tool approvals
|
|
75
|
+
|
|
76
|
+
Extensions request decisions (allow this tool? pick an option? enter a value?) via the
|
|
77
|
+
extension-UI sub-protocol. Install a handler with `on_ui_request`; without one, the SDK
|
|
78
|
+
safely denies confirmations and cancels other dialogs so the agent never hangs.
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
def approve(req):
|
|
82
|
+
if req.method == "confirm":
|
|
83
|
+
return True # allow
|
|
84
|
+
if req.method == "select":
|
|
85
|
+
return (req.options or [None])[0]
|
|
86
|
+
return None # cancel input/editor
|
|
87
|
+
|
|
88
|
+
agent.on_ui_request(approve) # see examples/with_approvals.py
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The full command surface (`set_model`, `bash`, `compact`, `fork`, `get_session_stats`,
|
|
92
|
+
steering/follow-up modes, …) is available as async methods on `PiAgent`.
|
|
93
|
+
|
|
94
|
+
## The `pi-py` coding agent
|
|
95
|
+
|
|
96
|
+
The repo also ships `pi_py_agent`, a small terminal coding agent built entirely on the
|
|
97
|
+
SDK (the agent loop, tools, and model calls all run inside Pi). Installing the package
|
|
98
|
+
provides a `pi-py` command:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pi-py # interactive REPL
|
|
102
|
+
pi-py --print "Run the tests and summarize failures" # one-shot
|
|
103
|
+
pi-py --model anthropic/claude-sonnet-4-20250514 --no-session
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
It streams assistant text, thinking, and tool activity (with result previews) to the
|
|
107
|
+
terminal, answers approval dialogs interactively, and supports slash commands (`/help`,
|
|
108
|
+
`/model`, `/models`, `/new`, `/state`, `/compact`, `/clone`, `/fork`, `/exit`). While
|
|
109
|
+
the agent is responding you can **steer** it by typing (or `+text` to queue a
|
|
110
|
+
follow-up). Ctrl-C aborts the current turn; Ctrl-D exits.
|
|
111
|
+
|
|
112
|
+
## Tests
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pytest # unit tests (no Node required); integration is deselected by default
|
|
116
|
+
pytest -m integration # live tests against a real `pi` (needs the binary on PATH)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The integration tests avoid LLM calls (state, models, bash), so they don't need a
|
|
120
|
+
provider key. The one prompt-completion test additionally needs a working model and is
|
|
121
|
+
skipped unless `PI_LIVE_LLM=1` is set.
|
|
122
|
+
|
|
123
|
+
## Releasing
|
|
124
|
+
|
|
125
|
+
CI (`.github/workflows/ci.yml`) runs the unit suite across Python 3.10–3.13, builds the
|
|
126
|
+
wheel, and best-effort-smokes a real `pi` on every push/PR. Publishing
|
|
127
|
+
(`.github/workflows/publish.yml`) builds and uploads to PyPI when a GitHub Release is
|
|
128
|
+
published — it uses [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/)
|
|
129
|
+
(OIDC, no token secret), which must be configured once for the repo.
|