pi-agent-python-sdk 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl
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_agent/_events.py +4 -2
- pi_agent/_launch.py +1 -1
- pi_agent/_observation.py +28 -6
- pi_agent/_runs.py +4 -4
- pi_agent/_transport.py +11 -2
- pi_agent/client.py +27 -8
- pi_agent/sync.py +10 -0
- pi_agent/types.py +41 -3
- {pi_agent_python_sdk-0.2.0.dist-info → pi_agent_python_sdk-0.2.2.dist-info}/METADATA +177 -77
- pi_agent_python_sdk-0.2.2.dist-info/RECORD +17 -0
- {pi_agent_python_sdk-0.2.0.dist-info → pi_agent_python_sdk-0.2.2.dist-info}/WHEEL +1 -1
- pi_agent_python_sdk-0.2.0.dist-info/RECORD +0 -17
- {pi_agent_python_sdk-0.2.0.dist-info → pi_agent_python_sdk-0.2.2.dist-info}/licenses/LICENSE +0 -0
pi_agent/_events.py
CHANGED
|
@@ -117,8 +117,10 @@ class _Subscription(Generic[T]):
|
|
|
117
117
|
async def _next_batch(self) -> list[T]:
|
|
118
118
|
records = [await self.__anext__()]
|
|
119
119
|
# Transfer only records already buffered; never wait to fill a batch.
|
|
120
|
-
#
|
|
121
|
-
|
|
120
|
+
# Drain the ready queue in one crossing so bridge latency does not leave
|
|
121
|
+
# a growing backlog. No await below: count and bytes stay within the
|
|
122
|
+
# existing queue budgets, including the first record just removed.
|
|
123
|
+
while self._records:
|
|
122
124
|
record = self._next_nowait()
|
|
123
125
|
assert record is not None
|
|
124
126
|
records.append(record)
|
pi_agent/_launch.py
CHANGED
|
@@ -13,7 +13,7 @@ from ._transport import _close_process_pipes, _wait_for_exit
|
|
|
13
13
|
from .errors import PiProcessError, PiVersionError
|
|
14
14
|
|
|
15
15
|
MINIMUM_PI_VERSION = "0.85.1"
|
|
16
|
-
TESTED_PI_VERSION = "0.
|
|
16
|
+
TESTED_PI_VERSION = "0.86.1"
|
|
17
17
|
|
|
18
18
|
# The client owns these options. In particular --print consumes an initial prompt.
|
|
19
19
|
_RESERVED = {
|
pi_agent/_observation.py
CHANGED
|
@@ -36,6 +36,7 @@ class ObservationStatus:
|
|
|
36
36
|
stdout_eof: bool = False
|
|
37
37
|
rpc_complete: bool = True
|
|
38
38
|
error: Exception | None = field(default=None, repr=False)
|
|
39
|
+
end_reason: Literal["stopped", "closed", "overflow", "process_end"] | None = None
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
class ProcessObservation(_Subscription[ProcessOutput]):
|
|
@@ -56,18 +57,39 @@ class ProcessObservation(_Subscription[ProcessOutput]):
|
|
|
56
57
|
def status(self) -> ObservationStatus:
|
|
57
58
|
return self._status
|
|
58
59
|
|
|
60
|
+
async def stop(self) -> None:
|
|
61
|
+
"""Unsubscribe without discarding queued output or closing the client.
|
|
62
|
+
|
|
63
|
+
Drain the iterator afterwards, then inspect status. Repeated stops retain
|
|
64
|
+
the first termination status, including overflow or process failure.
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
RuntimeError: If the observation context has not been entered.
|
|
68
|
+
"""
|
|
69
|
+
if not self._entered:
|
|
70
|
+
raise RuntimeError("Enter the observation context before stopping")
|
|
71
|
+
if not self._closed:
|
|
72
|
+
self._status = replace(self._status, end_reason="stopped")
|
|
73
|
+
self._finish()
|
|
74
|
+
|
|
59
75
|
def _finish(self, error: Exception | None = None) -> None:
|
|
60
76
|
if not self._closed and self._status.ended_at_ns is None:
|
|
61
|
-
|
|
62
|
-
|
|
77
|
+
overflow = isinstance(error, PiSubscriptionOverflow)
|
|
78
|
+
self._status = replace(
|
|
79
|
+
self._status,
|
|
63
80
|
ended_at_ns=time.time_ns(),
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
lost=self._status.lost or overflow,
|
|
82
|
+
error=error or self._status.error,
|
|
83
|
+
end_reason="overflow" if overflow else self._status.end_reason or "closed",
|
|
67
84
|
)
|
|
68
85
|
super()._finish(error)
|
|
69
86
|
|
|
70
87
|
def _discard(self, *, buffered: bool = False) -> None:
|
|
71
88
|
if self._records or buffered:
|
|
72
|
-
self._status = replace(
|
|
89
|
+
self._status = replace(
|
|
90
|
+
self._status,
|
|
91
|
+
complete=False,
|
|
92
|
+
lost=True,
|
|
93
|
+
end_reason="overflow" if self._status.end_reason == "overflow" else "closed",
|
|
94
|
+
)
|
|
73
95
|
super()._discard()
|
pi_agent/_runs.py
CHANGED
|
@@ -172,6 +172,10 @@ class RunStream:
|
|
|
172
172
|
if error is not None:
|
|
173
173
|
self._fail(error)
|
|
174
174
|
|
|
175
|
+
def _mark_submitted(self) -> None:
|
|
176
|
+
self._submitted = True
|
|
177
|
+
self._begin = time.monotonic()
|
|
178
|
+
|
|
175
179
|
async def _drive(self) -> RunResult:
|
|
176
180
|
assert self._ready is not None
|
|
177
181
|
failure: BaseException | None = None
|
|
@@ -183,8 +187,6 @@ class RunStream:
|
|
|
183
187
|
fields: dict[str, Any] = {"message": self._message}
|
|
184
188
|
if self._images is not None:
|
|
185
189
|
fields["images"] = self._images
|
|
186
|
-
self._submitted = True
|
|
187
|
-
self._begin = time.monotonic()
|
|
188
190
|
await self._client._request(
|
|
189
191
|
"prompt", fields, timeout=self._command_timeout, owner=self
|
|
190
192
|
)
|
|
@@ -300,8 +302,6 @@ class RunStream:
|
|
|
300
302
|
def __aiter__(self) -> Self:
|
|
301
303
|
if self._draining:
|
|
302
304
|
raise PiBusyError("Cannot iterate while result() drains the stream")
|
|
303
|
-
if self._iterating:
|
|
304
|
-
raise PiBusyError("A stream permits one event iterator")
|
|
305
305
|
self._iterating = True
|
|
306
306
|
return self
|
|
307
307
|
|
pi_agent/_transport.py
CHANGED
|
@@ -124,6 +124,7 @@ class Transport:
|
|
|
124
124
|
fields: dict[str, Any] | None = None,
|
|
125
125
|
*,
|
|
126
126
|
timeout: float | None = 30, # noqa: ASYNC109 - per-response API, distinct from write bound
|
|
127
|
+
before_write: Callable[[], None] | None = None,
|
|
127
128
|
) -> dict[str, Any]:
|
|
128
129
|
if not isinstance(command, str) or not command:
|
|
129
130
|
raise ValueError("command must be a nonempty string")
|
|
@@ -136,7 +137,9 @@ class Transport:
|
|
|
136
137
|
# A fast child can respond while drain() is still suspended.
|
|
137
138
|
self._pending[request_id] = _Pending(command, future)
|
|
138
139
|
try:
|
|
139
|
-
await self._write(
|
|
140
|
+
await self._write(
|
|
141
|
+
{"type": command, "id": request_id, **(fields or {})}, before_write=before_write
|
|
142
|
+
)
|
|
140
143
|
try:
|
|
141
144
|
# Keep cancellation on this task: wait_for can lose cancellation
|
|
142
145
|
# when its separate waiter completes at the same time on Python 3.11.
|
|
@@ -169,7 +172,9 @@ class Transport:
|
|
|
169
172
|
if not self.running:
|
|
170
173
|
raise PiProcessError("Pi subprocess is not running")
|
|
171
174
|
|
|
172
|
-
async def _write(
|
|
175
|
+
async def _write(
|
|
176
|
+
self, record: dict[str, Any], *, before_write: Callable[[], None] | None = None
|
|
177
|
+
) -> None:
|
|
173
178
|
# Escape lone UTF-16 surrogates, as JSON.stringify does, while keeping
|
|
174
179
|
# ordinary Unicode as UTF-8. Never emit invalid UTF-8 or replace values.
|
|
175
180
|
data = json.dumps(
|
|
@@ -177,6 +182,10 @@ class Transport:
|
|
|
177
182
|
).encode("utf-8", errors="backslashreplace")
|
|
178
183
|
if len(data) > self._limits.max_record_bytes:
|
|
179
184
|
raise ValueError("Outbound JSON record exceeds max_record_bytes")
|
|
185
|
+
# Local validation cannot submit work. Notify ownership before the first
|
|
186
|
+
# suspension so another run cannot race a validated, queued write.
|
|
187
|
+
if before_write is not None:
|
|
188
|
+
before_write()
|
|
180
189
|
wrote = False
|
|
181
190
|
try:
|
|
182
191
|
# Bound lock acquisition too: a wedged pipe must not queue writers forever.
|
pi_agent/client.py
CHANGED
|
@@ -424,9 +424,9 @@ class AsyncPiClient:
|
|
|
424
424
|
"""Observe selected process output; enter before start for lifetime coverage.
|
|
425
425
|
|
|
426
426
|
Raises ValueError if no source is selected. Slow consumers raise
|
|
427
|
-
PiSubscriptionOverflow.
|
|
428
|
-
checking status.
|
|
429
|
-
|
|
427
|
+
PiSubscriptionOverflow. Stop the observation or close the client, then
|
|
428
|
+
drain this iterator before checking final status. Known terminal process
|
|
429
|
+
failures remain in status.error even when stopped before cleanup ends.
|
|
430
430
|
"""
|
|
431
431
|
sources: set[OutputSource] = set()
|
|
432
432
|
if stderr:
|
|
@@ -486,6 +486,7 @@ class AsyncPiClient:
|
|
|
486
486
|
rpc_complete=rpc_complete,
|
|
487
487
|
complete=complete,
|
|
488
488
|
error=self._close_error if self._closed else self._terminal_error or error,
|
|
489
|
+
end_reason="process_end",
|
|
489
490
|
)
|
|
490
491
|
observer._finish()
|
|
491
492
|
|
|
@@ -551,6 +552,13 @@ class AsyncPiClient:
|
|
|
551
552
|
error = self._terminal_error
|
|
552
553
|
self._session = SessionInfo()
|
|
553
554
|
self._listeners.clear()
|
|
555
|
+
# Observers keep receiving shutdown output, but stop() must retain a
|
|
556
|
+
# failure already known before pipe drainage finishes. Normal close is
|
|
557
|
+
# not a process error for observation consumers.
|
|
558
|
+
for observer in self._observations:
|
|
559
|
+
observer._status = replace(
|
|
560
|
+
observer.status, error=self._close_error if self._closed else error
|
|
561
|
+
)
|
|
554
562
|
for task in tuple(self._ui_tasks):
|
|
555
563
|
if task is not asyncio.current_task() and task is not self._close_initiator:
|
|
556
564
|
task.cancel()
|
|
@@ -692,12 +700,23 @@ class AsyncPiClient:
|
|
|
692
700
|
)
|
|
693
701
|
if deadline is not None:
|
|
694
702
|
_validate_timeout(deadline)
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
703
|
+
|
|
704
|
+
def mark_submission() -> None:
|
|
705
|
+
if owner is not None:
|
|
706
|
+
owner._mark_submitted()
|
|
707
|
+
elif self._owner is None:
|
|
708
|
+
# Even rejection cannot prove extension preflight is idle.
|
|
709
|
+
self._unowned_submission = True
|
|
710
|
+
|
|
699
711
|
try:
|
|
700
|
-
response = await self._transport.request(
|
|
712
|
+
response = await self._transport.request(
|
|
713
|
+
command,
|
|
714
|
+
fields,
|
|
715
|
+
timeout=deadline,
|
|
716
|
+
before_write=(
|
|
717
|
+
mark_submission if command in {"prompt", "steer", "follow_up"} else None
|
|
718
|
+
),
|
|
719
|
+
)
|
|
701
720
|
if self._ui_error is not None:
|
|
702
721
|
error, self._ui_error = self._ui_error, None
|
|
703
722
|
raise error
|
pi_agent/sync.py
CHANGED
|
@@ -756,6 +756,16 @@ class SyncProcessObservation(_SyncSubscription[ProcessOutput]):
|
|
|
756
756
|
subscription = client._client.observe(stderr=stderr, stdout=stdout, rpc=rpc)
|
|
757
757
|
super().__init__(client, lambda: subscription)
|
|
758
758
|
|
|
759
|
+
def stop(self) -> None:
|
|
760
|
+
"""Unsubscribe and preserve buffered output for iteration; keep Pi running.
|
|
761
|
+
|
|
762
|
+
Raises:
|
|
763
|
+
RuntimeError: If the observation context has not been entered.
|
|
764
|
+
"""
|
|
765
|
+
if self._subscription is None:
|
|
766
|
+
raise RuntimeError("Enter the observation context before stopping")
|
|
767
|
+
self._client._close_context(cast(ProcessObservation, self._subscription).stop)
|
|
768
|
+
|
|
759
769
|
@property
|
|
760
770
|
def status(self) -> ObservationStatus:
|
|
761
771
|
if self._subscription is None:
|
pi_agent/types.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Pi 0.
|
|
1
|
+
"""Pi 0.86.1 wire annotations and small Python conveniences.
|
|
2
2
|
|
|
3
3
|
Wire fields retain Pi's spelling and remain ordinary dictionaries. These
|
|
4
4
|
annotations describe known shapes, not a recursive runtime validator: extensions
|
|
@@ -97,6 +97,26 @@ class AssistantMessageDiagnostic(TypedDict):
|
|
|
97
97
|
details: NotRequired[JSONObject]
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
class ToolDefinition(TypedDict):
|
|
101
|
+
name: str
|
|
102
|
+
description: str
|
|
103
|
+
parameters: JSONObject
|
|
104
|
+
constrainedSampling: NotRequired[Literal[False] | JSONObject]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ToolReference(TypedDict):
|
|
108
|
+
name: str
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class SystemMessage(TypedDict):
|
|
112
|
+
role: Literal["system"]
|
|
113
|
+
content: str | list[TextContent]
|
|
114
|
+
sections: NotRequired[dict[str, str | None]]
|
|
115
|
+
toolsAdded: NotRequired[list[ToolDefinition]]
|
|
116
|
+
toolsRemoved: NotRequired[list[ToolReference]]
|
|
117
|
+
timestamp: float
|
|
118
|
+
|
|
119
|
+
|
|
100
120
|
class UserMessage(TypedDict):
|
|
101
121
|
role: Literal["user"]
|
|
102
122
|
content: str | list[TextContent | ImageContent]
|
|
@@ -129,7 +149,7 @@ class ToolResultMessage(TypedDict):
|
|
|
129
149
|
content: list[TextContent | ImageContent]
|
|
130
150
|
details: NotRequired[JSONValue]
|
|
131
151
|
usage: NotRequired[Usage]
|
|
132
|
-
addedToolNames: NotRequired[list[str]]
|
|
152
|
+
addedToolNames: NotRequired[list[str]] # Legacy Pi 0.85.1 field.
|
|
133
153
|
isError: bool
|
|
134
154
|
timestamp: float
|
|
135
155
|
|
|
@@ -170,7 +190,8 @@ class CompactionSummaryMessage(TypedDict):
|
|
|
170
190
|
|
|
171
191
|
|
|
172
192
|
AgentMessage: TypeAlias = (
|
|
173
|
-
|
|
193
|
+
SystemMessage
|
|
194
|
+
| UserMessage
|
|
174
195
|
| AssistantMessage
|
|
175
196
|
| ToolResultMessage
|
|
176
197
|
| BashExecutionMessage
|
|
@@ -195,6 +216,11 @@ class ModelCost(ModelCostRates):
|
|
|
195
216
|
tiers: NotRequired[list[ModelCostTier]]
|
|
196
217
|
|
|
197
218
|
|
|
219
|
+
class ModelPromptCache(TypedDict, total=False):
|
|
220
|
+
short: float
|
|
221
|
+
long: float
|
|
222
|
+
|
|
223
|
+
|
|
198
224
|
class Model(TypedDict):
|
|
199
225
|
id: str
|
|
200
226
|
name: str
|
|
@@ -210,6 +236,7 @@ class Model(TypedDict):
|
|
|
210
236
|
samplingParams: NotRequired[JSONObject]
|
|
211
237
|
headers: NotRequired[dict[str, str]]
|
|
212
238
|
compat: NotRequired[JSONObject]
|
|
239
|
+
promptCache: NotRequired[ModelPromptCache]
|
|
213
240
|
|
|
214
241
|
|
|
215
242
|
class SessionState(TypedDict):
|
|
@@ -308,6 +335,15 @@ class ModelChangeEntry(_EntryBase):
|
|
|
308
335
|
modelId: str
|
|
309
336
|
|
|
310
337
|
|
|
338
|
+
class UsageEntry(_EntryBase):
|
|
339
|
+
type: Literal["usage"]
|
|
340
|
+
kind: str
|
|
341
|
+
provider: str
|
|
342
|
+
model: str
|
|
343
|
+
usage: Usage
|
|
344
|
+
note: NotRequired[str]
|
|
345
|
+
|
|
346
|
+
|
|
311
347
|
class CompactionEntry(_EntryBase):
|
|
312
348
|
type: Literal["compaction"]
|
|
313
349
|
summary: str
|
|
@@ -316,6 +352,7 @@ class CompactionEntry(_EntryBase):
|
|
|
316
352
|
details: NotRequired[JSONValue]
|
|
317
353
|
usage: NotRequired[Usage]
|
|
318
354
|
fromHook: NotRequired[bool]
|
|
355
|
+
systemMessage: NotRequired[SystemMessage]
|
|
319
356
|
|
|
320
357
|
|
|
321
358
|
class BranchSummaryEntry(_EntryBase):
|
|
@@ -356,6 +393,7 @@ SessionEntry: TypeAlias = (
|
|
|
356
393
|
MessageEntry
|
|
357
394
|
| ThinkingLevelChangeEntry
|
|
358
395
|
| ModelChangeEntry
|
|
396
|
+
| UsageEntry
|
|
359
397
|
| CompactionEntry
|
|
360
398
|
| BranchSummaryEntry
|
|
361
399
|
| CustomEntry
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pi-agent-python-sdk
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Typed async and synchronous Python clients for Pi's agent RPC runtime
|
|
5
5
|
Project-URL: Homepage, https://github.com/cheenulabs/pi-agent-python-sdk
|
|
6
|
-
Project-URL: Documentation, https://github.com/cheenulabs/pi-agent-python-sdk/tree/v0.2.
|
|
6
|
+
Project-URL: Documentation, https://github.com/cheenulabs/pi-agent-python-sdk/tree/v0.2.2/docs
|
|
7
7
|
Project-URL: Issues, https://github.com/cheenulabs/pi-agent-python-sdk/issues
|
|
8
|
-
Project-URL: Changelog, https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.
|
|
8
|
+
Project-URL: Changelog, https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.2/CHANGELOG.md
|
|
9
9
|
Author: cheenulabs
|
|
10
10
|
License-Expression: MIT
|
|
11
11
|
License-File: LICENSE
|
|
@@ -23,40 +23,69 @@ Description-Content-Type: text/markdown
|
|
|
23
23
|
|
|
24
24
|
# Pi Agent Python SDK
|
|
25
25
|
|
|
26
|
-
[][pypi]
|
|
26
|
+
[][pypi]
|
|
27
27
|
[][metadata]
|
|
28
28
|
[][license]
|
|
29
29
|
|
|
30
|
-
**
|
|
30
|
+
**Use your installed Pi coding agent as a Python API. Stream responses,
|
|
31
|
+
continue conversations, and control sessions from Python.**
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
and
|
|
35
|
-
|
|
33
|
+
[Pi](https://github.com/earendil-works/pi) is an open-source coding agent that
|
|
34
|
+
runs in your terminal. This unofficial community SDK launches the Pi CLI in RPC
|
|
35
|
+
mode and exposes prompts, streaming events, and sessions through typed
|
|
36
|
+
synchronous and asynchronous Python APIs.
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
```python
|
|
39
|
+
from pi_agent import PiClient
|
|
40
|
+
|
|
41
|
+
with PiClient() as pi:
|
|
42
|
+
print(pi.run("Explain the current project without changing files.").text)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Requires an installed, configured Pi CLI — see [Prerequisites](#prerequisites).
|
|
46
|
+
|
|
47
|
+
[Prerequisites](#prerequisites) · [Quick start](#quick-start) ·
|
|
48
|
+
[Streaming](#streaming) · [Async](#async-usage) ·
|
|
49
|
+
[Configuration](#configuration) · [Thinking](#models-and-thinking) ·
|
|
50
|
+
[RPC](#rpc-access) · [Documentation](#documentation) ·
|
|
51
|
+
[Troubleshooting](#troubleshooting) · [Support](#support)
|
|
52
|
+
|
|
53
|
+
## What you can do
|
|
40
54
|
|
|
41
|
-
|
|
42
|
-
|
|
55
|
+
- **Run prompts from Python** — get a final answer, or stream text, thinking, and
|
|
56
|
+
tool events as they happen.
|
|
57
|
+
- **Keep conversations going** — send follow-ups on the same client, or resume,
|
|
58
|
+
fork, and clone sessions.
|
|
59
|
+
- **Control the agent** — switch models, set thinking level, steer mid-run, and
|
|
60
|
+
compact context.
|
|
61
|
+
- **Reuse your Pi setup** — same configuration, tools, and extensions as the CLI,
|
|
62
|
+
with sync or async clients.
|
|
63
|
+
|
|
64
|
+
## Prerequisites
|
|
65
|
+
|
|
66
|
+
| Requirement | Notes |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| Python **3.11+** | Package metadata requires 3.11 or newer |
|
|
69
|
+
| Node.js **22.19.0+** | Required by Pi 0.86.1 |
|
|
70
|
+
| Pi **0.85.1+** | Minimum accepted version; **0.86.1** is the tested baseline |
|
|
71
|
+
| Model provider | Required; configure with the Pi CLI (see Quick start) |
|
|
43
72
|
|
|
44
73
|
## Quick start
|
|
45
74
|
|
|
46
|
-
Install Pi with Node.js **22.19.0 or newer
|
|
75
|
+
Install Pi with Node.js **22.19.0 or newer** (pin matches the tested baseline):
|
|
47
76
|
|
|
48
77
|
```sh
|
|
49
|
-
npm install -g @earendil-works/pi-coding-agent@0.
|
|
78
|
+
npm install -g @earendil-works/pi-coding-agent@0.86.1
|
|
50
79
|
pi --version
|
|
51
80
|
```
|
|
52
81
|
|
|
53
|
-
Run `pi` once to configure your provider and model
|
|
54
|
-
|
|
82
|
+
Run `pi` once to configure your provider and model. The SDK uses that
|
|
83
|
+
configuration when it starts Pi.
|
|
55
84
|
|
|
56
85
|
Install the Python package from [PyPI][pypi]:
|
|
57
86
|
|
|
58
87
|
```sh
|
|
59
|
-
python -m pip install pi-agent-python-sdk
|
|
88
|
+
python -m pip install pi-agent-python-sdk
|
|
60
89
|
```
|
|
61
90
|
|
|
62
91
|
The distribution is named `pi-agent-python-sdk`; import it as
|
|
@@ -71,8 +100,8 @@ with PiClient() as pi:
|
|
|
71
100
|
```
|
|
72
101
|
|
|
73
102
|
The context manager starts and closes Pi. `run()` waits for the conversation to
|
|
74
|
-
settle
|
|
75
|
-
|
|
103
|
+
settle. The result includes finalized messages, session identity, elapsed time,
|
|
104
|
+
and observed assistant usage.
|
|
76
105
|
|
|
77
106
|
Continue the conversation with another call on the same client:
|
|
78
107
|
|
|
@@ -87,15 +116,6 @@ with PiClient() as pi:
|
|
|
87
116
|
Pi keeps the conversation context. Each result contains only the messages and
|
|
88
117
|
answer from that call; a call with no assistant output has empty text.
|
|
89
118
|
|
|
90
|
-
## What you can do
|
|
91
|
-
|
|
92
|
-
- **Run and stream:** get a final answer or consume text, thinking, and tool events.
|
|
93
|
-
- **Keep a conversation:** send follow-up prompts, resume sessions, fork, or clone.
|
|
94
|
-
- **Control Pi:** select models, adjust thinking, steer work, compact context, and
|
|
95
|
-
call all 33 RPC commands in the pinned baseline.
|
|
96
|
-
- **Integrate with your application:** use sync or async clients, typed results,
|
|
97
|
-
raw event dictionaries, and extension UI callbacks.
|
|
98
|
-
|
|
99
119
|
## Streaming
|
|
100
120
|
|
|
101
121
|
```python
|
|
@@ -110,9 +130,15 @@ with PiClient() as pi:
|
|
|
110
130
|
print(f"\nStop reason: {result.stop_reason}")
|
|
111
131
|
```
|
|
112
132
|
|
|
133
|
+
<details>
|
|
134
|
+
<summary>Streaming details</summary>
|
|
135
|
+
|
|
113
136
|
`event.raw` contains the full Pi event, including fields the SDK does not yet
|
|
114
|
-
recognize. Keep the stream inside its context: leaving early cleans up unfinished
|
|
115
|
-
handling timeouts and partial
|
|
137
|
+
recognize. Keep the stream inside its context: leaving early cleans up unfinished
|
|
138
|
+
work. See [errors and cancellation][errors] for handling timeouts and partial
|
|
139
|
+
results.
|
|
140
|
+
|
|
141
|
+
</details>
|
|
116
142
|
|
|
117
143
|
## Async usage
|
|
118
144
|
|
|
@@ -135,8 +161,13 @@ if __name__ == "__main__":
|
|
|
135
161
|
asyncio.run(main())
|
|
136
162
|
```
|
|
137
163
|
|
|
138
|
-
|
|
139
|
-
|
|
164
|
+
<details>
|
|
165
|
+
<summary>Async streaming</summary>
|
|
166
|
+
|
|
167
|
+
Use `async with pi.stream(...)`, `async for event in stream`, and
|
|
168
|
+
`await stream.result()`. See the [complete streaming example][stream-example].
|
|
169
|
+
|
|
170
|
+
</details>
|
|
140
171
|
|
|
141
172
|
## Configuration
|
|
142
173
|
|
|
@@ -150,14 +181,70 @@ with PiClient(cwd=".", no_session=True) as pi:
|
|
|
150
181
|
```
|
|
151
182
|
|
|
152
183
|
Leave `no_session` unset to preserve Pi's normal session persistence. Use
|
|
153
|
-
`
|
|
154
|
-
|
|
155
|
-
|
|
184
|
+
`session=` to open an existing session. For model overrides, see
|
|
185
|
+
[Models and thinking](#models-and-thinking).
|
|
186
|
+
|
|
187
|
+
<details>
|
|
188
|
+
<summary>More configuration</summary>
|
|
189
|
+
|
|
190
|
+
See [constructor options][constructor-options] for environment overrides,
|
|
191
|
+
executable paths, and deadlines.
|
|
156
192
|
|
|
157
193
|
Extensions are installed and configured through Pi. To load your own extension,
|
|
158
|
-
pass `extra_args=["--extension", "/absolute/path/to/your-extension.ts"]` to
|
|
159
|
-
client. See [using your own extensions][extensions]
|
|
160
|
-
|
|
194
|
+
pass `extra_args=["--extension", "/absolute/path/to/your-extension.ts"]` to
|
|
195
|
+
either client. See [using your own extensions][extensions] for details and
|
|
196
|
+
[extension UI][ui-example] for an interactive example.
|
|
197
|
+
|
|
198
|
+
</details>
|
|
199
|
+
|
|
200
|
+
## Models and thinking
|
|
201
|
+
|
|
202
|
+
By default the SDK uses whatever model you already configured in the Pi CLI.
|
|
203
|
+
Override it when you construct the client, or switch later on the same client.
|
|
204
|
+
Set the model and thinking level **before** `run()` or `stream()`.
|
|
205
|
+
|
|
206
|
+
Select a model at construction:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from pi_agent import PiClient
|
|
210
|
+
|
|
211
|
+
with PiClient(provider="openai", model="gpt-5") as pi:
|
|
212
|
+
print(pi.run("Summarise this repo without changing files.").text)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Thinking levels depend on the current model. Query them, set one, then confirm
|
|
216
|
+
what Pi actually applied:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
from pi_agent import PiClient
|
|
220
|
+
|
|
221
|
+
with PiClient() as pi:
|
|
222
|
+
levels = pi.get_available_thinking_levels()
|
|
223
|
+
print("available:", levels)
|
|
224
|
+
if "high" in levels:
|
|
225
|
+
pi.set_thinking_level("high")
|
|
226
|
+
print("effective:", pi.get_state()["thinkingLevel"])
|
|
227
|
+
print(pi.run("Analyse this project's architecture without changing files.").text)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Replace the example provider/model ids with ones from your Pi install
|
|
231
|
+
(`pi --list-models` or `get_available_models()`).
|
|
232
|
+
|
|
233
|
+
<details>
|
|
234
|
+
<summary>List models and switch after start</summary>
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from pi_agent import PiClient
|
|
238
|
+
|
|
239
|
+
with PiClient() as pi:
|
|
240
|
+
for model in pi.get_available_models():
|
|
241
|
+
print(model["provider"], model["id"])
|
|
242
|
+
selected = pi.set_model("openai", "gpt-5")
|
|
243
|
+
print("using", selected["provider"], selected["id"])
|
|
244
|
+
print(pi.run("Summarise this repo without changing files.").text)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
</details>
|
|
161
248
|
|
|
162
249
|
## RPC access
|
|
163
250
|
|
|
@@ -170,7 +257,18 @@ Your Python application
|
|
|
170
257
|
Models · tools · extensions · sessions
|
|
171
258
|
```
|
|
172
259
|
|
|
173
|
-
|
|
260
|
+
Inspect session state without starting a model run:
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
from pi_agent import PiClient
|
|
264
|
+
|
|
265
|
+
with PiClient() as pi:
|
|
266
|
+
state = pi.get_state()
|
|
267
|
+
print(state["sessionId"])
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
<details>
|
|
271
|
+
<summary>Which interface to use</summary>
|
|
174
272
|
|
|
175
273
|
| You need | Use |
|
|
176
274
|
| --- | --- |
|
|
@@ -182,29 +280,20 @@ Choose the interface that fits the work:
|
|
|
182
280
|
| A specific Pi operation | `get_state()`, `set_model()`, `fork()`, and other command methods |
|
|
183
281
|
| A raw command response envelope | `request()` |
|
|
184
282
|
|
|
185
|
-
For example, inspect session state without starting a model run:
|
|
186
|
-
|
|
187
|
-
```python
|
|
188
|
-
from pi_agent import PiClient
|
|
189
|
-
|
|
190
|
-
with PiClient() as pi:
|
|
191
|
-
state = pi.get_state()
|
|
192
|
-
print(state["sessionId"])
|
|
193
|
-
```
|
|
194
|
-
|
|
195
283
|
Python arguments use `snake_case`; wire dictionaries retain Pi's `camelCase`
|
|
196
284
|
fields. `prompt()` acknowledges submission, which may be handled entirely by an
|
|
197
285
|
extension. It does not wait for a completed answer.
|
|
198
286
|
|
|
199
|
-
|
|
200
|
-
before starting the next. After using
|
|
201
|
-
use a fresh client for `run()` or
|
|
202
|
-
|
|
203
|
-
Pi terminal session.
|
|
287
|
+
Each client starts its own Pi subprocess. Use one client per conversation and
|
|
288
|
+
wait for each `run()` or stream to finish before starting the next. After using
|
|
289
|
+
`prompt()` for your own event handling, use a fresh client for `run()` or
|
|
290
|
+
`stream()` so results cannot include delayed events from earlier work.
|
|
204
291
|
|
|
205
292
|
See [RPC structure][rpc] for the protocol mapping and module layout, and
|
|
206
293
|
the [command reference][commands] for every method.
|
|
207
294
|
|
|
295
|
+
</details>
|
|
296
|
+
|
|
208
297
|
## Documentation
|
|
209
298
|
|
|
210
299
|
| Guide | Contents |
|
|
@@ -215,35 +304,46 @@ the [command reference][commands] for every method.
|
|
|
215
304
|
| [RPC structure][rpc] | How the Python client maps to Pi's protocol |
|
|
216
305
|
| [Compatibility][compatibility] | Runtime versions and platform validation |
|
|
217
306
|
| [Examples][examples] | Runnable sync, async, streaming, sessions, steering, and UI examples |
|
|
218
|
-
| [Release notes][release] | Changes included in 0.2.
|
|
307
|
+
| [Release notes][release] | Changes included in 0.2.2 |
|
|
219
308
|
|
|
220
309
|
Examples use your configured Pi and may make provider calls. Development checks
|
|
221
310
|
use an isolated local test provider; see [CONTRIBUTING.md][contributing] for
|
|
222
311
|
setup and validation commands.
|
|
223
312
|
|
|
224
|
-
|
|
225
|
-
|
|
313
|
+
## Troubleshooting
|
|
314
|
+
|
|
315
|
+
| Symptom | What to check |
|
|
316
|
+
| --- | --- |
|
|
317
|
+
| `pi` not found / launch fails | Ensure the Pi CLI is on `PATH`, or pass `executable=` to the client |
|
|
318
|
+
| Version rejected | Upgrade to Pi **0.85.1+**; prefer the tested baseline **0.86.1** |
|
|
319
|
+
| No model / auth errors | Run `pi` once and complete provider setup |
|
|
320
|
+
| Unexpected file or command changes | Review Pi tool/extension settings before automation |
|
|
321
|
+
|
|
322
|
+
## Support
|
|
323
|
+
|
|
324
|
+
- [Open an issue][issues] for bugs, questions, or compatibility reports.
|
|
325
|
+
- See [CONTRIBUTING.md][contributing] for local setup and pull requests.
|
|
326
|
+
- For security reports, open a [private security advisory][security].
|
|
226
327
|
|
|
227
328
|
## License
|
|
228
329
|
|
|
229
330
|
[MIT][license]
|
|
230
331
|
|
|
231
332
|
[pypi]: https://pypi.org/project/pi-agent-python-sdk/
|
|
232
|
-
[release]: https://github.com/cheenulabs/pi-agent-python-sdk/releases/tag/v0.2.
|
|
233
|
-
[metadata]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.
|
|
234
|
-
[license]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.
|
|
235
|
-
[compatibility]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.
|
|
236
|
-
[errors]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.
|
|
237
|
-
[stream-example]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.
|
|
238
|
-
[constructor-options]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.
|
|
239
|
-
[extensions]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.
|
|
240
|
-
[ui-example]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.
|
|
241
|
-
[rpc]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.
|
|
242
|
-
[commands]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.
|
|
243
|
-
[usage]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.
|
|
244
|
-
[api]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.
|
|
245
|
-
[examples]: https://github.com/cheenulabs/pi-agent-python-sdk/tree/v0.2.
|
|
246
|
-
[contributing]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.
|
|
247
|
-
[
|
|
248
|
-
[
|
|
249
|
-
[releasing]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.0/docs/releasing.md
|
|
333
|
+
[release]: https://github.com/cheenulabs/pi-agent-python-sdk/releases/tag/v0.2.2
|
|
334
|
+
[metadata]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.2/pyproject.toml
|
|
335
|
+
[license]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.2/LICENSE
|
|
336
|
+
[compatibility]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.2/docs/compatibility.md
|
|
337
|
+
[errors]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.2/docs/errors.md
|
|
338
|
+
[stream-example]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.2/examples/stream.py
|
|
339
|
+
[constructor-options]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.2/docs/api.md#constructor-options
|
|
340
|
+
[extensions]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.2/docs/usage.md#using-your-own-extensions
|
|
341
|
+
[ui-example]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.2/examples/ui.py
|
|
342
|
+
[rpc]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.2/docs/rpc.md
|
|
343
|
+
[commands]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.2/docs/api.md#all-33-rpc-commands
|
|
344
|
+
[usage]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.2/docs/usage.md
|
|
345
|
+
[api]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.2/docs/api.md
|
|
346
|
+
[examples]: https://github.com/cheenulabs/pi-agent-python-sdk/tree/v0.2.2/examples
|
|
347
|
+
[contributing]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.2/CONTRIBUTING.md
|
|
348
|
+
[issues]: https://github.com/cheenulabs/pi-agent-python-sdk/issues
|
|
349
|
+
[security]: https://github.com/cheenulabs/pi-agent-python-sdk/security/advisories/new
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
pi_agent/__init__.py,sha256=Tb-KJ6Ur9H0WfwGdk5DkPQtTNKsLPeqcJx3lEh67gJI,1117
|
|
2
|
+
pi_agent/_collections.py,sha256=1oSSh9u2OAXlVCu5t392go3wTQ2AruYm74prVfKBbgc,2174
|
|
3
|
+
pi_agent/_events.py,sha256=fkt5KAM5esgdYHxzGX6fLXrTDv4jhzhcypPYy9z3HvM,5068
|
|
4
|
+
pi_agent/_launch.py,sha256=u4MGYX9IiPUfiXzeiY8UUdKnJ5IBq2s1BFhsQMgwJlU,6965
|
|
5
|
+
pi_agent/_observation.py,sha256=GUe5VkoB5dFx1NY9VwAILjSRFa-th8O17OHMmcQ8q9Y,3297
|
|
6
|
+
pi_agent/_runs.py,sha256=hxKhs5Oi8NLMdufJ72cdXy8tffdZ04XEb2xJzWfOiaQ,14219
|
|
7
|
+
pi_agent/_transport.py,sha256=7mFkVjNSWBzvZw4zGwjtDNTe_FxF0Vxl0SD2z-58QuM,20040
|
|
8
|
+
pi_agent/_usage.py,sha256=PxK0WPrnd0RKoH_J8psoTGNYaNg71gmOqQiaY6hH0aA,3051
|
|
9
|
+
pi_agent/client.py,sha256=gsCV9CyDmjrUf21DpqX3TVKyHXMu5e_CXUzY4D1pcOE,43867
|
|
10
|
+
pi_agent/errors.py,sha256=FJtF8jwfGITMp_MzNHbUKutYRrdx_BH8kJI7-17WbJk,2573
|
|
11
|
+
pi_agent/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
pi_agent/sync.py,sha256=dPyGHpJGmhg09u3QFrYdSJYGUB7_8_kupBxXCm8gu6o,33658
|
|
13
|
+
pi_agent/types.py,sha256=R7LoyIyc0z7gzOL38ZOcbxOhL9ky7owL22RtELp6aMU,23389
|
|
14
|
+
pi_agent_python_sdk-0.2.2.dist-info/METADATA,sha256=5GvsUBJtw2fWBUBmyx02VEWwhmpwoYATaHBTO5PXA5I,12671
|
|
15
|
+
pi_agent_python_sdk-0.2.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
16
|
+
pi_agent_python_sdk-0.2.2.dist-info/licenses/LICENSE,sha256=VVC0d0PIT1GtbRUJluzmcIv-UYtsqsBfvWLFu5Qrbco,1067
|
|
17
|
+
pi_agent_python_sdk-0.2.2.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
pi_agent/__init__.py,sha256=Tb-KJ6Ur9H0WfwGdk5DkPQtTNKsLPeqcJx3lEh67gJI,1117
|
|
2
|
-
pi_agent/_collections.py,sha256=1oSSh9u2OAXlVCu5t392go3wTQ2AruYm74prVfKBbgc,2174
|
|
3
|
-
pi_agent/_events.py,sha256=kPZJPE_LpxXcMrhXb6KkomhmgQj3wFEVisnjunuxQYY,4936
|
|
4
|
-
pi_agent/_launch.py,sha256=jXi8M0DcLkB-FvKs3Nx72FN_Sy_S2KI7bqws_P11s10,6965
|
|
5
|
-
pi_agent/_observation.py,sha256=8BEMtud1tp2vG1-xFhY1Iw7KNqHX77CbbjkwIdWgpqg,2383
|
|
6
|
-
pi_agent/_runs.py,sha256=cUEpFSoGhmtVrbFG9XDx_gKGw_JXiFlVVQUQBlkl7HA,14292
|
|
7
|
-
pi_agent/_transport.py,sha256=0Huh6sK-_5PNYCVPgS7UPNy1_VSRt0zl2k8SHHsTO7E,19642
|
|
8
|
-
pi_agent/_usage.py,sha256=PxK0WPrnd0RKoH_J8psoTGNYaNg71gmOqQiaY6hH0aA,3051
|
|
9
|
-
pi_agent/client.py,sha256=9I1xR9tzXz2PGrDNpdRDA9EUrGKTRF_OwIzWRDpynqI,43240
|
|
10
|
-
pi_agent/errors.py,sha256=FJtF8jwfGITMp_MzNHbUKutYRrdx_BH8kJI7-17WbJk,2573
|
|
11
|
-
pi_agent/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
pi_agent/sync.py,sha256=gdTOHr7-plV7azEaYtkMmCqPWmcc3E5fufPTlVybgrE,33235
|
|
13
|
-
pi_agent/types.py,sha256=Oi0rixDVKeNHtfVCC6_vRgZ0c7a5M-M8Xco8fHIioJ0,22518
|
|
14
|
-
pi_agent_python_sdk-0.2.0.dist-info/METADATA,sha256=9J5XiasPvpncfL16wkyviV5S2XJPqUio7jbmUcSxPB8,10216
|
|
15
|
-
pi_agent_python_sdk-0.2.0.dist-info/WHEEL,sha256=THafob7ofN-NsuMN7Mg4qZyHaQI7KkD-QlcQatYhXPo,87
|
|
16
|
-
pi_agent_python_sdk-0.2.0.dist-info/licenses/LICENSE,sha256=VVC0d0PIT1GtbRUJluzmcIv-UYtsqsBfvWLFu5Qrbco,1067
|
|
17
|
-
pi_agent_python_sdk-0.2.0.dist-info/RECORD,,
|
{pi_agent_python_sdk-0.2.0.dist-info → pi_agent_python_sdk-0.2.2.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|