pi-agent-rb 0.1.9 → 0.1.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14badc81403e563a04b5280ad81fb0205d16c5ff0e413c8f80025a1a57b559d9
4
- data.tar.gz: da9584bec2e0e637f4ca59162ce809a27168b1551d81236afdb4324b003eb255
3
+ metadata.gz: 14f4bc43c791c394b325afc3352d848f6859a69f6ec8ac5c84c4caf05fe2ab7c
4
+ data.tar.gz: '0707285b43d67a86e76f41cd6c64ed543da143d26a7d59ba0b707d624cf92e5b'
5
5
  SHA512:
6
- metadata.gz: ad277fa02847fffc564c066646fdadaa539e8781897aa81d93d15f666593ee3eb62ee9285034fbc28678739ec4f8054adbf285aeb8dc0a2374cf609d3343568b
7
- data.tar.gz: 5d72e811dfd4317954296f288f467b5c74ff81fc2a736b1661c0f1d1ee0beacac11615acbdba2c5892b65ae0d46be0982acae73e28c2fef57ec078cb04e66d9f
6
+ metadata.gz: aaf855bf9c7a2a579cb951e54f6f72a64239e0f21184649e895807a2e5f8d12e8bd7905c40c0c0b81ffc356ced87053919b7739a6535e9244c536a357d32a99d
7
+ data.tar.gz: eb80da11fc8315e93a29dfe59036b342642a50baa33f2d50047fa53158f47da84e6dfcb26eec3ec965155306cdea144702b5e4308ac86f6a5d70cfd74bf1b1af
data/CHANGELOG.md CHANGED
@@ -7,6 +7,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.11] - 2026-06-30
11
+
12
+ ### Changed
13
+ - Bumped pinned upstream `pi-coding-agent` version to `0.80.3`. This release
14
+ adds two RPC commands to the protocol surface (`get_entries`, `get_tree`),
15
+ a package `./rpc-entry` export for launching pi directly in RPC mode, and
16
+ a raft of SDK/library-level changes (Claude Sonnet 5 support, `gpt-5.5` as
17
+ the default OpenAI model, provider/streaming fixes). The existing RPC
18
+ commands this gem drives are unchanged.
19
+
20
+ ### Added
21
+ - `Session#entries(since:)` wraps the new `get_entries` RPC command: session
22
+ entries in append order, including pre-compaction history and abandoned
23
+ branches. Pass `since:` (a stable entry id) as a durable cursor to fetch
24
+ only entries after it. Returns `{ "entries" => [...], "leafId" => ... }`.
25
+ - `Session#tree` wraps the new `get_tree` RPC command: the session as a tree
26
+ of entries. Returns `{ "tree" => [...], "leafId" => ... }`.
27
+
28
+ ## [0.1.10] - 2026-06-25
29
+
30
+ ### Changed
31
+ - Bumped pinned upstream `pi-coding-agent` version to `0.80.2` (rolls up
32
+ 0.80.0–0.80.2: the old global `@earendil-works/pi-ai` API moved to the
33
+ `@earendil-works/pi-ai/compat` entrypoint and the selective `/base`
34
+ provider entrypoints were removed; plus provider/auth resolution fixes
35
+ (Bedrock, Cloudflare, Fireworks, OpenAI Responses/Codex), session-name
36
+ newline normalization, and a `Ctrl+J` newline keybinding). These are
37
+ SDK/library-level changes; no changes to the RPC protocol surface or the
38
+ `--approve`/`--no-approve` flags this gem drives.
39
+
40
+ ### Added
41
+ - `Session#follow_up` now accepts a block to drain the agent cycle the queued
42
+ message triggers, mirroring `prompt`'s block contract. It is race-free —
43
+ the event subscription is established before the message is sent, so none
44
+ of the cycle's events are missed.
45
+ - `Session#events`: a lower-level, prompt-less drain of the agent event
46
+ stream, for callers that subscribe before the cycle starts (e.g. from a
47
+ thread). Mirrors `prompt`'s block/Enumerator contract. (The class docstring
48
+ already referenced this method; it now exists.)
49
+
10
50
  ## [0.1.9] - 2026-06-22
11
51
 
12
52
  ### Changed
data/README.md CHANGED
@@ -14,7 +14,7 @@ building interactive agent UIs (web, TUI) on top of pi.
14
14
 
15
15
  - Ruby 3.3+
16
16
  - `pi` on `PATH` (install via `npm i -g @earendil-works/pi-coding-agent`)
17
- - This gem is pinned against pi `0.79.10`; other versions may work but are not verified.
17
+ - This gem is pinned against pi `0.80.3`; other versions may work but are not verified.
18
18
 
19
19
  ## Installation
20
20
 
@@ -69,7 +69,7 @@ end
69
69
 
70
70
  Other session methods:
71
71
 
72
- - Prompting: `steer`, `follow_up`, `abort`
72
+ - Prompting: `steer`, `follow_up`, `events`, `abort`
73
73
  - Model: `set_model`, `cycle_model`, `available_models`, `set_thinking`
74
74
  - State: `get_state`, `messages`, `last_assistant_text`, `session_stats`
75
75
  - Context: `compact`
@@ -79,6 +79,26 @@ Other session methods:
79
79
  `set_model` accepts either `set_model("anthropic/claude-sonnet-4-5")` or
80
80
  `set_model("anthropic", "claude-sonnet-4-5")`.
81
81
 
82
+ A `prompt` streams one agent cycle (`agent_start`..`agent_end`). A message
83
+ queued with `follow_up` runs in a *later* cycle; pass a block to `follow_up`
84
+ to drain that cycle. Like `prompt`, it yields each `Event` until `agent_end`:
85
+
86
+ ```ruby
87
+ PiAgent.session do |session|
88
+ session.prompt("Draft a haiku") { |e| print e.delta if e.type == :message_update }
89
+ session.follow_up("Now translate it to French") { |e| print e.delta if e.type == :message_update }
90
+ end
91
+ ```
92
+
93
+ The block form is race-free: `follow_up` subscribes to the event stream
94
+ *before* sending the message, so none of the cycle's events are missed.
95
+
96
+ `events` is a lower-level, prompt-less drain of the same stream. Because it
97
+ subscribes lazily when iteration begins, it only works when you subscribe
98
+ *before* the cycle starts — e.g. begin iterating it from a thread, then
99
+ trigger the cycle. For the common follow-up case, prefer the block form
100
+ above.
101
+
82
102
  ### Images
83
103
 
84
104
  `prompt`, `steer`, and `follow_up` accept an `images:` array. Entries
@@ -14,9 +14,11 @@ module PiAgent
14
14
  # create/select step — the Session *is* the running pi process.
15
15
  #
16
16
  # v1 limitation: `prompt` streams one agent cycle (agent_start..agent_end).
17
- # Messages queued mid-flight via `follow_up`/`steer` run in subsequent
18
- # cycles; consume those by calling `prompt`-less `events` or another
19
- # `prompt`. Bidirectional extension UI is not yet surfaced here.
17
+ # A message queued with `follow_up` runs in a later cycle; pass a block to
18
+ # `follow_up` to drain that cycle race-free (it subscribes before sending).
19
+ # `events` is a prompt-less drain of the same stream for when you have
20
+ # already subscribed before the cycle starts. Bidirectional extension UI
21
+ # is not yet surfaced here.
20
22
  class Session
21
23
  # Max time to wait for the next event before assuming the agent stalled.
22
24
  DEFAULT_EVENT_TIMEOUT = 300
@@ -44,6 +46,26 @@ module PiAgent
44
46
  self
45
47
  end
46
48
 
49
+ # Drain the event stream without submitting a new prompt. With a block,
50
+ # yields each Event until the cycle finishes (agent_end) and returns
51
+ # self; without a block, returns an Enumerator of Events.
52
+ #
53
+ # The subscription is established lazily, when iteration begins — so any
54
+ # cycle triggered *before* you call `events` may have already emitted
55
+ # events that are then missed. To drain a `follow_up` cycle race-free,
56
+ # pass a block to `follow_up` instead. Use `events` only when you
57
+ # subscribe before the cycle starts (e.g. from a thread that begins
58
+ # iterating, then trigger the cycle). With nothing queued, it blocks
59
+ # for `event_timeout` (300s default).
60
+ def events(event_timeout: DEFAULT_EVENT_TIMEOUT, &block)
61
+ stream = subscribed_stream(event_timeout: event_timeout)
62
+
63
+ return stream unless block
64
+
65
+ stream.each(&block)
66
+ self
67
+ end
68
+
47
69
  # Queue a steering message while the agent is running. Delivered after
48
70
  # the current assistant turn finishes its tool calls, before the next
49
71
  # LLM call. Fire-and-forget; raises on rejection.
@@ -53,8 +75,21 @@ module PiAgent
53
75
  end
54
76
 
55
77
  # Queue a follow-up message, delivered only after the agent stops.
56
- def follow_up(message, images: nil)
57
- @client.request("follow_up", message_params(message, images)).value!(timeout: DEFAULT_ACK_TIMEOUT)
78
+ #
79
+ # Without a block this is fire-and-forget: it queues the message and
80
+ # returns self. With a block it drains the resulting agent cycle
81
+ # race-free — the subscription is established *before* the message is
82
+ # sent, so no events are missed — yielding each Event until agent_end
83
+ # and returning self. Prefer the block form to consume a follow-up;
84
+ # the standalone `events` drain only works if you subscribe first.
85
+ def follow_up(message, images: nil, event_timeout: DEFAULT_EVENT_TIMEOUT, &block)
86
+ params = message_params(message, images)
87
+ unless block
88
+ @client.request("follow_up", params).value!(timeout: DEFAULT_ACK_TIMEOUT)
89
+ return self
90
+ end
91
+
92
+ event_stream("follow_up", params, event_timeout: event_timeout).each(&block)
58
93
  self
59
94
  end
60
95
 
@@ -153,6 +188,24 @@ module PiAgent
153
188
  request_data("get_fork_messages").fetch("messages", [])
154
189
  end
155
190
 
191
+ # Session entries in append order (excluding the session header). Unlike
192
+ # `messages`, this includes pre-compaction history and abandoned branches.
193
+ # Entry ids are stable, so pass `since:` (the last entry id you have seen)
194
+ # to get only entries strictly after it — a durable cursor across restarts.
195
+ # Returns { "entries" => [...], "leafId" => <id or nil> }.
196
+ def entries(since: nil)
197
+ params = {}
198
+ params[:since] = since if since
199
+ request_data("get_entries", params)
200
+ end
201
+
202
+ # The session as a tree of entries. Each node is
203
+ # { "entry" =>, "children" => [...], "label"? =>, "labelTimestamp"? => }.
204
+ # Returns { "tree" => [...], "leafId" => <id or nil> }.
205
+ def tree
206
+ request_data("get_tree")
207
+ end
208
+
156
209
  # Fork a new branch from a previous user message (an entryId from
157
210
  # `fork_messages`). Returns { "text" => <forked-from text>,
158
211
  # "cancelled" => bool }; `cancelled` is true if an extension vetoed it.
@@ -207,14 +260,23 @@ module PiAgent
207
260
  end
208
261
 
209
262
  # Subscribe, send the command, then yield Events from the notification
210
- # stream until a terminal event. The subscription is scoped to one
211
- # iteration of the returned Enumerator so cleanup is deterministic.
263
+ # stream until a terminal event.
212
264
  def event_stream(type, params, event_timeout:)
265
+ subscribed_stream(event_timeout: event_timeout) do
266
+ @client.request(type, params).value!(timeout: DEFAULT_ACK_TIMEOUT)
267
+ end
268
+ end
269
+
270
+ # Subscribe, run `before_pump` (e.g. send a command) once the
271
+ # subscription is live so no events are missed in the gap, then yield
272
+ # Events until a terminal event. The subscription is scoped to one
273
+ # iteration of the returned Enumerator so cleanup is deterministic.
274
+ def subscribed_stream(event_timeout:, &before_pump)
213
275
  Enumerator.new do |yielder|
214
276
  queue = Queue.new
215
277
  handle = @client.subscribe { |msg| queue << msg }
216
278
  begin
217
- @client.request(type, params).value!(timeout: DEFAULT_ACK_TIMEOUT)
279
+ before_pump&.call
218
280
  pump_events(queue, yielder, event_timeout)
219
281
  ensure
220
282
  @client.unsubscribe(handle)
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PiAgent
4
- VERSION = "0.1.9"
4
+ VERSION = "0.1.11"
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.79.10"
8
+ SUPPORTED_PI_VERSION = "0.80.3"
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pi-agent-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - chagel