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 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
- # This retains at most one queue's byte budget in the blocking facade.
121
- while self._records and len(records) < 64:
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.85.1"
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
- self._status = ObservationStatus(
62
- started_at_ns=self._status.started_at_ns,
77
+ overflow = isinstance(error, PiSubscriptionOverflow)
78
+ self._status = replace(
79
+ self._status,
63
80
  ended_at_ns=time.time_ns(),
64
- from_start=self._status.from_start,
65
- lost=self._status.lost or isinstance(error, PiSubscriptionOverflow),
66
- error=error,
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(self._status, complete=False, lost=True)
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({"type": command, "id": request_id, **(fields or {})})
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(self, record: dict[str, Any]) -> None:
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. Close the client and drain this iterator before
428
- checking status.complete and status.error; terminal process failures are
429
- reported through status.error after output drains.
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
- if self._owner is None and command in {"prompt", "steer", "follow_up"}:
696
- # Set before awaiting: input handlers can start work long after acknowledgement.
697
- # Even rejection/cancellation cannot prove arbitrary extension preflight is idle.
698
- self._unowned_submission = True
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(command, fields, timeout=deadline)
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.85.1 wire annotations and small Python conveniences.
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
- UserMessage
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.0
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.0/docs
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.0/CHANGELOG.md
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](https://img.shields.io/pypi/v/pi-agent-python-sdk?style=flat-square)][pypi]
26
+ [![PyPI](https://img.shields.io/pypi/v/pi-agent-python-sdk.svg?style=flat-square)][pypi]
27
27
  [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-3776AB?style=flat-square)][metadata]
28
28
  [![MIT license](https://img.shields.io/badge/license-MIT-blue?style=flat-square)][license]
29
29
 
30
- **Run Pi from Python. Stream responses, continue conversations, and control sessions.**
30
+ **Use your installed Pi coding agent as a Python API. Stream responses,
31
+ continue conversations, and control sessions from Python.**
31
32
 
32
- A community Python SDK for [Pi coding agent](https://github.com/earendil-works/pi).
33
- Use synchronous or asynchronous clients with typed access to Pi's RPC commands
34
- and no third-party Python runtime dependencies. Pi runs as a subprocess and keeps
35
- its usual models, authentication, tools, extensions, skills, and configuration.
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
- > Requires Python **3.11+** and a separate Pi installation. The tested protocol
38
- > baseline is **Pi 0.85.1**; see [compatibility][compatibility] for version and
39
- > platform scope.
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
- [Quick start](#quick-start) · [Streaming](#streaming) · [Async](#async-usage) ·
42
- [Configuration](#configuration) · [RPC](#rpc-access) · [Documentation](#documentation)
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.85.1
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 using Pi's normal setup.
54
- The SDK uses that configuration when it starts Pi.
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==0.2.0
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, including retries and queued follow-ups. The result includes finalized
75
- messages, session identity, elapsed time, and observed assistant usage.
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 work. See [errors and cancellation][errors] for
115
- handling timeouts and partial results.
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
- Async streaming uses `async with pi.stream(...)`, `async for event in stream`,
139
- and `await stream.result()`. See the [complete streaming example][stream-example].
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
- `provider=` and `model=` to override Pi's configured model, or `session=` to open
154
- an existing session. See [constructor options][constructor-options]
155
- for environment overrides, executable paths, and deadlines.
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 either
159
- client. See [using your own extensions][extensions]
160
- for details and [extension UI][ui-example] for an interactive example.
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
- Choose the interface that fits the work:
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
- Use one client per conversation and wait for each `run()` or stream to finish
200
- before starting the next. After using `prompt()` for your own event handling,
201
- use a fresh client for `run()` or `stream()` so results cannot include delayed
202
- events from earlier work. The SDK launches a new process and cannot attach to an existing
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.0 |
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
- Maintainers: [protocol discovery][discovery] ·
225
- [maintenance][maintenance] · [release checklist][releasing].
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.0
233
- [metadata]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.0/pyproject.toml
234
- [license]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.0/LICENSE
235
- [compatibility]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.0/docs/compatibility.md
236
- [errors]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.0/docs/errors.md
237
- [stream-example]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.0/examples/stream.py
238
- [constructor-options]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.0/docs/api.md#constructor-options
239
- [extensions]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.0/docs/usage.md#using-your-own-extensions
240
- [ui-example]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.0/examples/ui.py
241
- [rpc]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.0/docs/rpc.md
242
- [commands]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.0/docs/api.md#all-33-rpc-commands
243
- [usage]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.0/docs/usage.md
244
- [api]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.0/docs/api.md
245
- [examples]: https://github.com/cheenulabs/pi-agent-python-sdk/tree/v0.2.0/examples
246
- [contributing]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.0/CONTRIBUTING.md
247
- [discovery]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.0/docs/discovery.md
248
- [maintenance]: https://github.com/cheenulabs/pi-agent-python-sdk/blob/v0.2.0/docs/maintenance.md
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,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.32.3
2
+ Generator: hatchling 1.32.4
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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,,