pi-agent-rb 0.1.17 → 0.2.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +52 -0
- data/README.md +27 -11
- data/lib/pi_agent/event.rb +5 -4
- data/lib/pi_agent/session.rb +55 -21
- data/lib/pi_agent/version.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 24f258d02d946aa8752944f4438581126665c94b5ee1253e61c8ebd16ecb6596
|
|
4
|
+
data.tar.gz: f98727ad546acbc6ea8dee3e2cf52af1cf7694f3875181fdec16c3773fd2e0b7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 32e5f3a97a2f6b890a1f1c8aecdd59125a4588b6879e815471ad2257942d291a6ccc5c46d32e4bd04abe13020e8ccb4cde1643dfb9230bea9e7974bb8008513f
|
|
7
|
+
data.tar.gz: '099b2a2267bd8a7212c6d90473775d54a22e7d80982471682ae339cd01875383561bd77364f9f9fe54c8a9514ae26023811ec24a8bcbe1b2dd035f1412f30377'
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2026-07-23
|
|
11
|
+
|
|
12
|
+
Minor (not patch) release: besides the stream-truncation fix, the block form
|
|
13
|
+
of `Session#follow_up` and stream concurrency behave differently (see
|
|
14
|
+
Changed).
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- `Session#prompt`, `#follow_up`, `#events`, and `#run` now drain through
|
|
18
|
+
`agent_settled` instead of stopping at the first `agent_end`. Since pi
|
|
19
|
+
0.80.4, `agent_end` marks only one low-level run and may be followed by an
|
|
20
|
+
automatic retry, compaction retry, or queued continuation; stopping there
|
|
21
|
+
could unsubscribe early and silently drop the final response events.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- The block form of `Session#follow_up` now uses pi's streaming-aware `prompt`
|
|
25
|
+
command, so sequential follow-ups start when the session is idle while
|
|
26
|
+
the blockless form remains a direct, queue-only `follow_up` RPC command for
|
|
27
|
+
active runs. High-level streams are now single-flight per session because
|
|
28
|
+
pi events have no run IDs; overlapping streams raise `SessionError` instead
|
|
29
|
+
of potentially consuming another run's `agent_settled` event.
|
|
30
|
+
|
|
31
|
+
## [0.1.18] - 2026-07-21
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- `Session#available_thinking_levels`, wrapping the upstream
|
|
35
|
+
`get_available_thinking_levels` RPC command (added in pi `0.81.0`).
|
|
36
|
+
Returns the thinking levels supported by the current model as an array of
|
|
37
|
+
strings (e.g. `["off", "low", "medium", "high"]`), or `["off"]` for a
|
|
38
|
+
model without reasoning support. Complements the existing `set_thinking`.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
- Bumped pinned upstream `pi-coding-agent` version to `0.81.1` (from
|
|
42
|
+
`0.80.10`), covering both `0.81.0` and `0.81.1`.
|
|
43
|
+
- `0.81.0` adds local llama.cpp model management, full provider
|
|
44
|
+
extensions (extensions can register complete pi-ai providers), Qwen
|
|
45
|
+
Token Plan built-in providers, and expanded usage accounting for
|
|
46
|
+
tools, compaction, and branch summaries. The one change touching the
|
|
47
|
+
JSONL RPC surface this gem drives is the new
|
|
48
|
+
`get_available_thinking_levels` command, now exposed as
|
|
49
|
+
`Session#available_thinking_levels`.
|
|
50
|
+
- `0.81.1` adds resilient compaction/branch summaries that retry
|
|
51
|
+
transient provider failures, emitting new `summarization_retry_*`
|
|
52
|
+
lifecycle events (`summarization_retry_scheduled`,
|
|
53
|
+
`summarization_retry_attempt_start`, `summarization_retry_finished`) to
|
|
54
|
+
RPC consumers, plus checksummed release source archives. The retry
|
|
55
|
+
events flow through `PiAgent::Event` transparently — it preserves the
|
|
56
|
+
native payload on `#raw` and exposes `#type` as a symbol — so no gem
|
|
57
|
+
change is needed to consume them.
|
|
58
|
+
- No RPC commands or responses changed shape across either release, so
|
|
59
|
+
the rest is provider/model/accounting concerns that leave the existing
|
|
60
|
+
contract unchanged.
|
|
61
|
+
|
|
10
62
|
## [0.1.17] - 2026-07-17
|
|
11
63
|
|
|
12
64
|
### Changed
|
data/README.md
CHANGED
|
@@ -13,8 +13,8 @@ building interactive agent UIs (web, TUI) on top of pi.
|
|
|
13
13
|
## Requirements
|
|
14
14
|
|
|
15
15
|
- Ruby 3.3+
|
|
16
|
-
- `pi` on `PATH` (install via `npm i -g @earendil-works/pi-coding-agent`)
|
|
17
|
-
- This gem is pinned against pi `0.
|
|
16
|
+
- `pi` 0.80.4+ on `PATH` (install via `npm i -g @earendil-works/pi-coding-agent`)
|
|
17
|
+
- This gem is pinned against pi `0.81.1`; other versions may work but are not verified.
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
20
20
|
|
|
@@ -47,8 +47,10 @@ end
|
|
|
47
47
|
A pi RPC process hosts one session, so there is no create/select step —
|
|
48
48
|
`PiAgent.session` spawns `pi --mode rpc` and the session *is* that process.
|
|
49
49
|
|
|
50
|
-
`prompt` yields each [`Event`](lib/pi_agent/event.rb) until the agent
|
|
51
|
-
finishes (`
|
|
50
|
+
`prompt` yields each [`Event`](lib/pi_agent/event.rb) until the agent fully
|
|
51
|
+
finishes (`agent_settled`). Unlike `agent_end`, this includes any automatic
|
|
52
|
+
retry, compaction retry, or queued continuation. Without a block it returns
|
|
53
|
+
an `Enumerator`:
|
|
52
54
|
|
|
53
55
|
```ruby
|
|
54
56
|
PiAgent.session do |session|
|
|
@@ -70,7 +72,7 @@ end
|
|
|
70
72
|
Other session methods:
|
|
71
73
|
|
|
72
74
|
- Prompting: `steer`, `follow_up`, `events`, `abort`
|
|
73
|
-
- Model: `set_model`, `cycle_model`, `available_models`, `set_thinking`
|
|
75
|
+
- Model: `set_model`, `cycle_model`, `available_models`, `set_thinking`, `available_thinking_levels`
|
|
74
76
|
- State: `get_state`, `messages`, `last_assistant_text`, `session_stats`
|
|
75
77
|
- Context: `compact`
|
|
76
78
|
- Sessions: `new_session`, `switch_session`, `fork`, `clone_session`,
|
|
@@ -79,9 +81,10 @@ Other session methods:
|
|
|
79
81
|
`set_model` accepts either `set_model("anthropic/claude-sonnet-4-5")` or
|
|
80
82
|
`set_model("anthropic", "claude-sonnet-4-5")`.
|
|
81
83
|
|
|
82
|
-
A `prompt` streams
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
A `prompt` streams the complete session-level run
|
|
85
|
+
(`agent_start`..`agent_settled`). Pass a block to `follow_up` for a sequential
|
|
86
|
+
follow-up that starts immediately when idle (or queues when an agent is
|
|
87
|
+
already running) and drains each `Event` through `agent_settled`:
|
|
85
88
|
|
|
86
89
|
```ruby
|
|
87
90
|
PiAgent.session do |session|
|
|
@@ -90,14 +93,27 @@ PiAgent.session do |session|
|
|
|
90
93
|
end
|
|
91
94
|
```
|
|
92
95
|
|
|
93
|
-
The block form is race-free: `follow_up` subscribes to the event stream
|
|
94
|
-
|
|
96
|
+
The block form is race-free: `follow_up` subscribes to the event stream before
|
|
97
|
+
sending a `prompt` with pi's `streamingBehavior: "followUp"`, so none of the
|
|
98
|
+
cycle's events are missed. Use it only after the previous high-level stream has
|
|
99
|
+
settled; pi events have no run IDs, so a session permits only one high-level
|
|
100
|
+
event stream at a time. While a stream is active, use blockless `follow_up` to
|
|
101
|
+
queue a continuation—the active stream will include it through
|
|
102
|
+
`agent_settled`. The block form accepts plain agent input, not slash commands.
|
|
103
|
+
|
|
104
|
+
Pi emits `agent_end` after each low-level agent run, but may then retry,
|
|
105
|
+
compact and retry, or process queued continuations. It emits
|
|
106
|
+
`agent_settled` only when no automatic work remains, so high-level streams
|
|
107
|
+
use that as their completion boundary. Upstream added the RPC event in pi
|
|
108
|
+
0.80.4; it is available in this gem's pinned pi 0.81.1.
|
|
95
109
|
|
|
96
110
|
`events` is a lower-level, prompt-less drain of the same stream. Because it
|
|
97
111
|
subscribes lazily when iteration begins, it only works when you subscribe
|
|
98
112
|
*before* the cycle starts — e.g. begin iterating it from a thread, then
|
|
99
113
|
trigger the cycle. For the common follow-up case, prefer the block form
|
|
100
|
-
above.
|
|
114
|
+
above. `prompt`, block-form `follow_up`, and `events` are single-flight on a
|
|
115
|
+
session; starting another before the current stream settles raises
|
|
116
|
+
`PiAgent::SessionError` rather than consuming an unrelated settlement event.
|
|
101
117
|
|
|
102
118
|
### Images
|
|
103
119
|
|
data/lib/pi_agent/event.rb
CHANGED
|
@@ -6,12 +6,13 @@ module PiAgent
|
|
|
6
6
|
# dedicated accessor yet.
|
|
7
7
|
#
|
|
8
8
|
# Event types are exposed as Ruby symbols (e.g. `:text_delta`,
|
|
9
|
-
# `:
|
|
9
|
+
# `:agent_settled`) matching the upstream protocol's `type` field.
|
|
10
10
|
class Event
|
|
11
11
|
# Event types that terminate a single prompt's event stream.
|
|
12
|
-
# `agent_end`
|
|
13
|
-
#
|
|
14
|
-
|
|
12
|
+
# `agent_end` only finishes one low-level run; retries, compaction, or
|
|
13
|
+
# queued continuations may follow. `agent_settled` is emitted once all
|
|
14
|
+
# automatic work has finished.
|
|
15
|
+
TERMINAL_TYPES = %i[agent_settled].freeze
|
|
15
16
|
|
|
16
17
|
attr_reader :raw, :type
|
|
17
18
|
|
data/lib/pi_agent/session.rb
CHANGED
|
@@ -13,12 +13,12 @@ module PiAgent
|
|
|
13
13
|
# A pi RPC process hosts exactly one session, so there is no
|
|
14
14
|
# create/select step — the Session *is* the running pi process.
|
|
15
15
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# `follow_up`
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
16
|
+
# `prompt` streams until the session-level run settles, including retries,
|
|
17
|
+
# automatic compaction retries, and queued continuations. A message queued
|
|
18
|
+
# with blockless `follow_up` runs after an active agent; pass a block to
|
|
19
|
+
# submit it as a streaming-aware prompt and drain it race-free. `events` is
|
|
20
|
+
# a prompt-less drain for when you have already subscribed before processing
|
|
21
|
+
# starts.
|
|
22
22
|
class Session
|
|
23
23
|
# Max time to wait for the next event before assuming the agent stalled.
|
|
24
24
|
DEFAULT_EVENT_TIMEOUT = 300
|
|
@@ -29,11 +29,14 @@ module PiAgent
|
|
|
29
29
|
|
|
30
30
|
def initialize(client)
|
|
31
31
|
@client = client
|
|
32
|
+
@stream_state_mutex = Mutex.new
|
|
33
|
+
@event_stream_active = false
|
|
32
34
|
end
|
|
33
35
|
|
|
34
|
-
# Submit a user prompt. With a block, yields each Event until the
|
|
35
|
-
#
|
|
36
|
-
#
|
|
36
|
+
# Submit a user prompt. With a block, yields each Event until the agent
|
|
37
|
+
# fully settles (agent_settled), then returns self. This includes any
|
|
38
|
+
# automatic retry, compaction retry, or queued continuation. Without a
|
|
39
|
+
# block, returns an Enumerator of Events.
|
|
37
40
|
#
|
|
38
41
|
# `images` accepts PiAgent::Image objects, file path strings, or
|
|
39
42
|
# raw ImageContent hashes — in any mix.
|
|
@@ -47,8 +50,8 @@ module PiAgent
|
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
# Drain the event stream without submitting a new prompt. With a block,
|
|
50
|
-
# yields each Event until the
|
|
51
|
-
# self; without a block, returns an Enumerator of Events.
|
|
53
|
+
# yields each Event until the run fully settles (agent_settled) and
|
|
54
|
+
# returns self; without a block, returns an Enumerator of Events.
|
|
52
55
|
#
|
|
53
56
|
# The subscription is established lazily, when iteration begins — so any
|
|
54
57
|
# cycle triggered *before* you call `events` may have already emitted
|
|
@@ -76,12 +79,12 @@ module PiAgent
|
|
|
76
79
|
|
|
77
80
|
# Queue a follow-up message, delivered only after the agent stops.
|
|
78
81
|
#
|
|
79
|
-
# Without a block this
|
|
80
|
-
# returns self
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
82
|
+
# Without a block this maps to pi's raw `follow_up` command: it only queues
|
|
83
|
+
# the message and returns self, so call it while an agent run is active.
|
|
84
|
+
# With a block it uses pi's streaming-aware `prompt` command, which starts
|
|
85
|
+
# a run when idle or queues a follow-up when busy. The subscription is
|
|
86
|
+
# established before sending, then yields through agent_settled and returns
|
|
87
|
+
# self. Prefer this form for a sequential, consumable follow-up.
|
|
85
88
|
def follow_up(message, images: nil, event_timeout: DEFAULT_EVENT_TIMEOUT, &block)
|
|
86
89
|
params = message_params(message, images)
|
|
87
90
|
unless block
|
|
@@ -89,7 +92,12 @@ module PiAgent
|
|
|
89
92
|
return self
|
|
90
93
|
end
|
|
91
94
|
|
|
92
|
-
|
|
95
|
+
if message.start_with?("/")
|
|
96
|
+
raise SessionError, "Block-form follow_up does not support slash commands; use prompt instead"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
params[:streamingBehavior] = "followUp"
|
|
100
|
+
event_stream("prompt", params, event_timeout: event_timeout).each(&block)
|
|
93
101
|
self
|
|
94
102
|
end
|
|
95
103
|
|
|
@@ -138,6 +146,13 @@ module PiAgent
|
|
|
138
146
|
self
|
|
139
147
|
end
|
|
140
148
|
|
|
149
|
+
# Thinking levels supported by the current model, as an array of strings
|
|
150
|
+
# (e.g. ["off", "low", "medium", "high"]). Returns ["off"] for a model
|
|
151
|
+
# without reasoning support.
|
|
152
|
+
def available_thinking_levels
|
|
153
|
+
request_data("get_available_thinking_levels").fetch("levels", [])
|
|
154
|
+
end
|
|
155
|
+
|
|
141
156
|
def get_state
|
|
142
157
|
@client.request("get_state").value!(timeout: DEFAULT_ACK_TIMEOUT)
|
|
143
158
|
end
|
|
@@ -273,17 +288,36 @@ module PiAgent
|
|
|
273
288
|
# iteration of the returned Enumerator so cleanup is deterministic.
|
|
274
289
|
def subscribed_stream(event_timeout:, &before_pump)
|
|
275
290
|
Enumerator.new do |yielder|
|
|
276
|
-
|
|
277
|
-
|
|
291
|
+
claim_event_stream!
|
|
292
|
+
|
|
293
|
+
handle = nil
|
|
278
294
|
begin
|
|
295
|
+
queue = Queue.new
|
|
296
|
+
handle = @client.subscribe { |msg| queue << msg }
|
|
279
297
|
before_pump&.call
|
|
280
298
|
pump_events(queue, yielder, event_timeout)
|
|
281
299
|
ensure
|
|
282
|
-
@client.unsubscribe(handle)
|
|
300
|
+
@client.unsubscribe(handle) if handle
|
|
301
|
+
release_event_stream
|
|
283
302
|
end
|
|
284
303
|
end
|
|
285
304
|
end
|
|
286
305
|
|
|
306
|
+
def claim_event_stream!
|
|
307
|
+
claimed = @stream_state_mutex.synchronize do
|
|
308
|
+
next false if @event_stream_active
|
|
309
|
+
|
|
310
|
+
@event_stream_active = true
|
|
311
|
+
end
|
|
312
|
+
return if claimed
|
|
313
|
+
|
|
314
|
+
raise SessionError, "Another event stream is active; queue with blockless follow_up or wait for it to settle"
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def release_event_stream
|
|
318
|
+
@stream_state_mutex.synchronize { @event_stream_active = false }
|
|
319
|
+
end
|
|
320
|
+
|
|
287
321
|
def pump_events(queue, yielder, event_timeout)
|
|
288
322
|
loop do
|
|
289
323
|
msg = queue.pop(timeout: event_timeout)
|
data/lib/pi_agent/version.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module PiAgent
|
|
4
|
-
VERSION = "0.
|
|
4
|
+
VERSION = "0.2.0"
|
|
5
5
|
|
|
6
6
|
# Pinned upstream pi-coding-agent version this gem is verified against.
|
|
7
7
|
# See: https://www.npmjs.com/package/@earendil-works/pi-coding-agent
|
|
8
|
-
SUPPORTED_PI_VERSION = "0.
|
|
8
|
+
SUPPORTED_PI_VERSION = "0.81.1"
|
|
9
9
|
end
|