llm.rb 16.0.0.pre.6 → 16.0.0.pre.8

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.
data/CHANGELOG.md CHANGED
@@ -13,6 +13,9 @@
13
13
  > Changelog <br>
14
14
  > [r.uby.dev](https://r.uby.dev) project
15
15
 
16
+ This file covers the v15 series and the unreleased changes that follow it.
17
+ Releases before v15 are kept in [changelog/old.md](changelog/old.md).
18
+
16
19
  ## What's next
17
20
 
18
21
  ### Breaking
@@ -23,251 +26,207 @@
23
26
  |-----|-----|
24
27
  | Ruby 3.3 or later | Ruby 3.4 or later |
25
28
  | `record.messages` returns the messages the runtime holds | a `:jsonb` record returns a relation of `LLM::ActiveRecord::Message` rows |
29
+ | a `set_tracer` method on an ORM model | `set tracer:` in the block, or `tracer:` on the wrapper |
30
+ | a `:thread` or `:fiber` cancel that arrives before the tool starts is dropped | the cancel is held and raised once the tool starts |
31
+ | a cancel that arrives between two requests does nothing | the turn ends there, and the caller sees `LLM::Interrupt` |
32
+ | a bare `rescue`, or `rescue => ex`, catches a cancel | `LLM::Interrupt` is outside `StandardError`, so both forms pass it through |
26
33
 
27
34
  * **drop Ruby 3.3 support** <br>
28
- The gem now requires Ruby 3.4 or later: `required_ruby_version` is
29
- `>= 3.4.0`, the CI matrix runs 3.4 and 4.0, and RuboCop targets 3.4.
30
- Ruby 3.3 cannot hold an interrupt for a ractor-backed tool call, so a
31
- cancel that arrives before the tool starts is not delivered there, while
32
- Ruby 3.4 and 4.0 both deliver it.
35
+ The gem now requires Ruby 3.4 or later. Ruby 3.3 cannot hold an
36
+ interrupt for a ractor-backed tool call, so a cancel can arrive
37
+ before the tool starts and never be delivered.
33
38
 
34
39
  * **activerecord: read a `:jsonb` record's messages from the column** <br>
35
- [`LLM::ActiveRecord`](https://r.uby.dev/api-docs/llm.rb/LLM/ActiveRecord.html)
36
- now answers `#messages` for a record whose `format` is `:jsonb` with an
37
- [`ActiveRecord::Relation`](https://api.rubyonrails.org/classes/ActiveRecord/Relation.html)
38
- over the column, through
39
- [`LLM::ActiveRecord::Message`](https://r.uby.dev/api-docs/llm.rb/LLM/ActiveRecord/Message.html),
40
- so a conversation can be filtered, counted, and ordered in SQL, and reading it
41
- needs no provider and no credentials. Before this, every record loaded the
42
- runtime and returned the messages it held, whatever the format. The relation
43
- is not a drop-in replacement for that buffer: it reads what is persisted
44
- rather than what a runtime holds in memory, it is unordered, so order by
45
- `position`, and its rows answer `unwrap!` rather than being
46
- [`LLM::Message`](https://r.uby.dev/api-docs/llm.rb/LLM/Message.html) objects.
47
- Every other format, and Sequel, still loads the runtime.
40
+ The `#messages` of a `:jsonb` record is now a relation over the stored
41
+ messages, so a conversation can be filtered and counted in SQL without a
42
+ provider or credentials. Before, every record loaded the runtime; reach
43
+ that again with `#messages!`, and order the relation by `position`
44
+ because it is unordered.
45
+
46
+ * **function: hold a cancel on `:thread` and `:fiber` instead of dropping it** <br>
47
+ A cancel that arrived before the tool started used to be dropped, and the call
48
+ ran to completion. It is now held and raised once the tool starts, so a caller
49
+ that cancelled a task it had not yet spawned sees
50
+ [`LLM::Interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Interrupt.html) where
51
+ it used to see a return.
52
+
53
+ * **interrupt: reach a turn that is between its requests** <br>
54
+ A cancel that arrived between two requests used to do nothing, because
55
+ there was nothing in flight to close and nothing running to raise into.
56
+ It now ends the turn where it is, raising `LLM::Interrupt` into the
57
+ caller the turn runs under and announcing `scope: :agent` to the tracer
58
+ first. `LLM::Agent#run_loop` records that caller for as long as the turn
59
+ lasts, and it answers `interrupt!` through
60
+ [`LLM::Agent::Interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent/Interrupt.html).
61
+ The raise lands in the thread or fiber the turn runs on, so it unwinds out
62
+ of the turn to whoever called it.
63
+
64
+ * **interrupt: move `LLM::Interrupt` outside `StandardError`** <br>
65
+ `LLM::Interrupt` is now an `Exception` rather than an `LLM::Error`, so a
66
+ bare `rescue` and a `rescue => ex` pass a cancel through instead of
67
+ swallowing it. Before, a broad rescue in a tool, a tracer, or an
68
+ application could eat an interrupt, and the turn looked like one that had
69
+ ignored its cancel. It is not a `SignalException`, since an interrupt has
70
+ to be catchable and a signal is a framework's own condition. A caller that
71
+ handles a cancel names `LLM::Interrupt`; `#wait` and the tool's
72
+ `on_interrupt` hook are unchanged, and a task still carries the interrupt
73
+ back to the caller rather than raising it inside its reactor.
48
74
 
49
75
  ### ActiveRecord
50
76
 
51
77
  * **activerecord: add `messages!` for the runtime's own messages** <br>
52
78
  [`LLM::ActiveRecord#messages!`](https://r.uby.dev/api-docs/llm.rb/LLM/ActiveRecord.html#messages!-instance_method)
53
- returns the messages the runtime holds whatever the storage format is, so a
54
- jsonb record can still reach them now that its `#messages` answers with the
55
- view over the column. It reads what the context holds, including state that
56
- has not been saved, where `#messages` reads the column. The Sequel plugin
57
- answers to `#messages!` too, where it is the same call as `#messages`.
79
+ returns the messages the runtime holds, including state that has not been
80
+ saved, where `#messages` reads the column. Use it to reach the live
81
+ conversation of a `:jsonb` record. The Sequel plugin answers to
82
+ `#messages!` too, where it is the same call as `#messages`.
58
83
 
59
84
  ### Agent
60
85
 
61
86
  * **agent: keep the instructions it injected in step** <br>
62
- [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) now brings
63
- the instructions it injected up to date before a request goes out, so a
87
+ An agent now refreshes its instructions before each request, so a
64
88
  conversation restored from saved state runs on the agent's current
65
- instructions rather than on the ones it was saved with. The message is
66
- found by a mark the agent puts on it, so a message the caller composed is
67
- never replaced, and the mark is written by
68
- [`LLM::Message#to_h`](https://r.uby.dev/api-docs/llm.rb/LLM/Message.html#to_h-instance_method)
69
- and read back on a restore, which is the conversation the refresh exists
70
- for. Before this, the instructions were injected once and the message
71
- stayed as it was for the life of the conversation.
89
+ instructions instead of the ones it was saved with. A message the caller
90
+ composed is never touched.
72
91
 
73
92
  * **agent: identify its own instructions by the mark, not by the role** <br>
74
- Fix a bug where the agent decided whether it had already injected its
75
- instructions by looking for a message with the `system` role. Google gives
76
- instructions the `user` role, so the agent did not recognise the message it
77
- had already injected and added another copy of the instructions on every
78
- turn. It now asks the provider which role instructions are given, and
79
- identifies the message it wrote by its mark, because on Google the message
80
- a caller seeds a conversation with is a user message too.
93
+ Fix a bug where an agent on Google added a fresh copy of its instructions
94
+ every turn, because it looked for a `system` message and Google gives
95
+ instructions the `user` role. It now marks the message it wrote and finds
96
+ it by that mark.
81
97
 
82
98
  ### Console
83
99
 
84
100
  * **console: erase with the backspace key on OpenBSD** <br>
85
- The console now erases a character for every code a backspace key sends. A
86
- terminal that follows the Linux convention sends DEL (127), the OpenBSD
87
- console sends ^H (8), and curses sends its own `KEY_BACKSPACE` when the
88
- terminal's terminfo describes the key. All three erase. Before this only 127
89
- did, so the backspace key did nothing on the OpenBSD console.
101
+ The console now erases for every code a backspace key sends: DEL (127),
102
+ ^H (8), and curses's `KEY_BACKSPACE`. Before, only 127 worked, so
103
+ backspace did nothing on the OpenBSD console.
90
104
 
91
105
  ### Fix
92
106
 
93
107
  * **fork: require xchan.rb `~> 0.24`** <br>
94
- The `:fork` concurrency strategy now requires the `xchan.rb` gem at `~> 0.24`
95
- instead of `~> 0.23`, and the gemspec's development dependency follows. An
96
- application that uses `:fork` needs `xchan.rb` 0.24 or later.
108
+ The `:fork` strategy now needs `xchan.rb` 0.24 or later, up from 0.23.
97
109
 
98
110
  ### Function
99
111
 
100
112
  * **function: deliver an interrupt to the tool, not to whatever is running** <br>
101
- [`LLM::Function::Window`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Window.html)
102
- is the stretch of a call that an interrupt belongs to. The `:fork` and `:ractor`
103
- strategies now raise
113
+ A cancel now raises
104
114
  [`LLM::Interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Interrupt.html) on the
105
- thread that runs the tool, and only while the tool runs: a cancel that arrives
106
- before the tool starts is held until it does, so the tool's own `rescue` can
107
- handle it, and one that arrives after the tool has returned is a no-op, which
108
- no longer throws the result away. Before this, the interrupt was raised on the
109
- child process's or the ractor's main thread wherever that thread had got to.
115
+ thread running the tool, and only while it runs: arriving early, it is held
116
+ until the tool starts, so the tool's own `rescue` sees it; arriving late, it
117
+ is a no-op instead of throwing the result away. Applies to `:fork` and
118
+ `:ractor`.
110
119
 
111
120
  * **function: answer a ractor-backed task once its ractor has gone** <br>
112
- A `:ractor` tool's result is handed to a ractor the task holds it in,
113
- rather than asked of the ractor that ran the tool, which answers `alive?`
114
- while the tool runs and goes once the result is in. So a wait that arrives
115
- after the tool has run is answered, a second wait is answered from memory,
116
- the way
117
- [`LLM::Function::Thread::Task#wait`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Thread/Task.html#wait-instance_method)
118
- answers from the thread's value, and `alive?` answers `false` once the task
119
- has been waited on. A cancel returns `nil` rather than raising, which is what
120
- lets
121
- [`LLM::Function::Ractor::Group#interrupt!`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Ractor/Group.html#interrupt!-instance_method)
122
- reach every task it holds. Before this, a wait that arrived once the tool had
123
- run raised `Ractor::ClosedError` or never came back, a cancel raised the same
124
- error, and a group's cancel stopped at the first task that had already
125
- returned, leaving the calls after it uncancelled.
121
+ A `:ractor` tool's result is now held by the task instead of asked of the
122
+ ractor that ran it, so a late wait is answered, a second wait comes from
123
+ memory, and a cancel returns `nil` instead of raising. A group's cancel now
124
+ reaches every task, not just the ones still running.
126
125
 
127
126
  * **function: tell an `:async` tool it was cancelled** <br>
128
- A cancel now raises `LLM::Interrupt` on the fiber the tool runs in, through the
129
- reactor's scheduler, so the tool is told and can answer with its own value. A
130
- cancel that arrives before the tool started stops it from running, and the
131
- reactor is stopped by whoever waits. Before this, the cancel pushed an
132
- interrupt sentinel to the task's result queue and left the tool running, its
133
- side effects happening and its result written to a queue nobody read, and the
134
- reactor thread stayed alive until a group's `wait` stopped it.
127
+ A cancel now raises `LLM::Interrupt` on the fiber the tool runs in, so the
128
+ tool is told and can clean up. Before, the cancel was queued and the tool
129
+ kept running, its result written to a queue nobody read.
135
130
 
136
131
  * **function: run the interrupt hook on every strategy** <br>
137
132
  A tool that implements
138
133
  [`LLM::Tool#on_interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html#on_interrupt-instance_method)
139
- or
140
- [`LLM::Tool#on_cancel`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html#on_cancel-instance_method)
141
- is now told on every concurrency strategy. `:thread`, `:fiber`, and `:async`
142
- run the hook on the thread or fiber that ran the call, `:fork` and `:ractor`
143
- run it inside the child or the ractor before the interrupt is delivered, and
144
- a sequential group's cancel tells every task it holds. Before this, `:async`,
145
- `:fork`, and `:ractor` never ran the hook, the others ran it on the thread
146
- that cancelled, and a class-backed tool ran it on no strategy at all, because
147
- `interrupt!` read the hook off the tool's definition and a class answers
148
- `respond_to?` with false for an instance method. A hook that raises becomes
149
- what the caller sees in place of the call's result.
134
+ is now told on every concurrency strategy. Before, `:async`, `:fork`, and
135
+ `:ractor` never ran it, and a class-backed tool ran it on no strategy at all.
136
+
137
+ * **function: hold a cancel that arrives before a thread or fiber starts** <br>
138
+ Fix a bug where a cancel that arrived before a `:thread` or `:fiber` tool
139
+ started was dropped, and the call ran to completion as if nobody had asked it
140
+ to stop. The cancel is now held on the task and raised once the body is live,
141
+ so the tool's `on_interrupt` hook runs and the caller sees `LLM::Interrupt`.
142
+
143
+ * **function: answer a task that has already answered** <br>
144
+ A second `#wait` on an `:async` or `:fork` task is answered from what the
145
+ first one took, which is what `Thread#value` does, so a task that has answered
146
+ answers again. Before, a second wait blocked on a queue nothing would fill, or
147
+ read a channel the task had already closed. An interrupt re-raises the same
148
+ exception rather than blocking.
150
149
 
151
150
  ### ORM
152
151
 
153
152
  * **orm: save a conversation after each request, not once per turn** <br>
154
- [`LLM::Step`](https://r.uby.dev/api-docs/llm.rb/LLM/Step.html) is prepended
155
- onto every stream by
156
- [`LLM::Stream.try`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#try-class_method),
157
- so a conversation bound to a record is written down as it goes and a turn
158
- that is interrupted can be continued rather than started over. It saves
159
- through
160
- [`LLM::Context#record`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#record-instance_method),
161
- an ActiveRecord model through `LLM::ActiveRecord::Utils.save!` and a Sequel
162
- model through `LLM::Sequel::Plugin::Utils.save!`, and the write is best
163
- effort, so a failure is swallowed rather than raised inside the request that
164
- has just completed. Before this, `talk` and `ask` on both wrappers saved
165
- once, when the whole turn had finished, so an interrupted turn persisted
166
- nothing.
153
+ A record-backed conversation is now written down as it goes, so an
154
+ interrupted turn can be continued instead of started over. Before, the
155
+ wrappers saved once when the whole turn finished, so an interrupted turn
156
+ persisted nothing.
157
+
158
+ * **orm: drop the `set_tracer` callback** <br>
159
+ The wrappers no longer resolve a tracer from a `set_tracer` method on the
160
+ model. Declare it where an agent's other defaults are declared, with
161
+ `set tracer:` in the block, or pass it to the wrapper as `tracer:`, which
162
+ is what a context has. A model that implements `set_tracer` today loses
163
+ its tracer silently.
167
164
 
168
165
  ### Prompt
169
166
 
170
167
  * **prompt: let a message carry fields a caller attaches to it** <br>
171
168
  [`LLM::Prompt#system`](https://r.uby.dev/api-docs/llm.rb/LLM/Prompt.html#system-instance_method),
172
- [`#user`](https://r.uby.dev/api-docs/llm.rb/LLM/Prompt.html#user-instance_method),
173
- [`#developer`](https://r.uby.dev/api-docs/llm.rb/LLM/Prompt.html#developer-instance_method),
174
- and [`#talk`](https://r.uby.dev/api-docs/llm.rb/LLM/Prompt.html#talk-instance_method),
175
- along with its `#chat` alias, now take an `extra:` keyword, so a message a
176
- caller composes can carry fields of its own. The runtime reads those fields
177
- back rather than inferring provenance from a role, which is how an agent
178
- tells the instructions it injected from a message the caller wrote.
169
+ `#user`, `#developer`, and `#talk` (and its `#chat` alias) now take an
170
+ `extra:` keyword, so a caller can tag a message with fields of its own. The
171
+ runtime reads those fields back instead of guessing provenance from the role.
179
172
 
180
173
  ### Provider
181
174
 
182
175
  * **deepseek: support image attachments in chat completions** <br>
183
- DeepSeek's vision models now take an image through the chat completions
184
- adapter. A tagged prompt object of kind `:image_url` is sent as an `image_url`
185
- content item, and a local image file is sent as a base64 encoded data URI.
186
- Anything else is rejected with a reason of its own: a remote file or an
187
- `LLM::Response` raises
176
+ DeepSeek's vision models now accept an image. A `:image_url` object is sent
177
+ as an `image_url` item, and a local image file as a base64 data URI.
178
+ Anything else is rejected with a reason: a remote file or an `LLM::Response`
179
+ raises
188
180
  [`LLM::PromptError`](https://r.uby.dev/api-docs/llm.rb/LLM/PromptError.html)
189
- because DeepSeek has no Files API, so a file id cannot be resolved, and a
190
- local file that is not an image raises because DeepSeek's models read images
191
- only.
181
+ because DeepSeek has no Files API, and a non-image local file raises because
182
+ its models read images only.
192
183
 
193
184
  ### Registry
194
185
 
195
186
  * **refresh model metadata** <br>
196
- Update `data/` with current model listings, limits, and pricing. Alibaba adds
197
- `qwen3.5-flash`, `qwen3.7-flash`, and `qwen3.8-omni-flash`, and gives
198
- `qwen3.7-plus` attachments, structured output, and a lower price; OpenAI adds
199
- `gpt-daybreak-blue-latest`, `gpt-daybreak-red-latest`, and `gpt-6.1-sol`, and
200
- gives `gpt-6-astra` an `ultrafast` mode; Bedrock adds `openai.gpt-6-sol`,
201
- `openai.gpt-6-luna`, `global.anthropic.claude-sonnet-5-5`,
202
- `anthropic.claude-sonnet-5-5`, and the Grok 4.7 entries in both the global
203
- and US regions; DeepInfra adds `tencent/Hy4-preview` and the Xiaomi MiMo
204
- V2.6 Pro and V2.6 Flash models, and marks four older entries deprecated.
205
- Bedrock also corrects the context and output limits of fifteen models, Google
206
- lowers the limits of `gemini-2.5-computer-use-preview-10-2025` and
207
- `gemini-3-pro-image`, and OpenRouter adds seven models, `openai/gpt-6.1-sol`
208
- and `openai/gpt-6.1-sol-pro` among them, drops six, and reprices or corrects
209
- the limits of many of the DeepSeek, Z.ai, Qwen, and Tencent entries.
187
+ Update `data/` with current listings, limits, and pricing. Alibaba, OpenAI,
188
+ Bedrock, DeepInfra, Google, and OpenRouter gain models and correct limits or
189
+ prices, and OpenRouter drops six entries.
210
190
 
211
191
  ### Schema
212
192
 
213
193
  * **schema: apply every `parameter` option to the leaf** <br>
214
- [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html) now applies each
215
- option given to `parameter` to the leaf its type resolves to, so settings such
216
- as `min:`, `max:`, `multiple_of:`, and `const:` reach the schema instead of
217
- being dropped. Before this only `required`, `default`, and `enum` were read, so
218
- `parameter :age, Integer, "Age", min: 1, max: 9` sent a plain integer and lost
219
- the range. A `default:` of `false` survives now as well, where the old code
220
- discarded a false default, and `required: false` marks the parameter optional
221
- rather than being ignored.
194
+ Every option given to `parameter` now reaches the schema, so `min:`, `max:`,
195
+ `multiple_of:`, and `const:` are no longer dropped, a `default: false`
196
+ survives, and `required: false` marks a parameter optional. Before, only
197
+ `required`, `default`, and `enum` were read, so a tool that declared a range
198
+ sent a plain number and lost it.
222
199
 
223
200
  ### Stream
224
201
 
225
202
  * **stream: add `on_step`, called when a request completes** <br>
226
203
  [`LLM::Stream#on_step`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_step-instance_method)
227
- is a new callback, emitted from
228
- [`LLM::Context#talk`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#talk-instance_method)
229
- once the prompt and the response are in the conversation. It marks the
230
- boundary between one request and the next, which is the point a stream can
231
- checkpoint the conversation at, because a provider will accept it again
232
- there. It fires once per successful request, so a request retried after a
233
- rate limit or a timeout calls it when it lands and not once per attempt, and
234
- it fires even when the stream is disabled.
204
+ is a new callback, fired once the prompt and response are in the
205
+ conversation. It marks where one request ends and the next begins, which is
206
+ where a stream can checkpoint, and it fires once per successful request even
207
+ when streaming is off.
235
208
 
236
209
  ### Tracers
237
210
 
238
211
  * **tracer: report a request that failed** <br>
239
212
  [`LLM::Tracer#on_request_error`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#on_request_error-instance_method)
240
- is now called wherever a request ends without a response, so a span that
241
- [`LLM::Tracer#on_request_start`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#on_request_start-instance_method)
242
- returned is closed. Before this, a request was reported only when a provider
243
- turned an error response into an exception, so a dropped connection or a
244
- socket read that failed left the tracer holding a span it had opened and never
245
- closed, which in a trace reads the same as a process that died. A failure that
246
- surfaces as one of the transport's own error classes is reported the same way
247
- unless the request was interrupted, and the exception the caller receives is
248
- unchanged.
213
+ is now called whenever a request ends without a response, so a span is always
214
+ closed. Before, only provider errors that became exceptions were reported, so
215
+ a dropped connection left a span open, which reads like a process that died.
249
216
 
250
217
  * **tracer: add `on_interrupt`, called when a request is interrupted** <br>
251
218
  [`LLM::Tracer#on_interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#on_interrupt-instance_method)
252
- is a new hook, called with a `scope:` of `:request` or `:tool` before
253
- `LLM::Interrupt` reaches the caller, so a tracer that has to record what
254
- happened to a turn does it while the work is still in flight. An interrupt is
255
- the third ending a scope can have, beside `on_request_finish` and
256
- `on_request_error`, and it is not reported as a failure. Unlike the other
257
- request and tool hooks it does nothing by default, because an interrupt is
258
- delivered to whatever tracer happens to be bound, and a hook that raised would
259
- replace the interrupt every caller is written against. A request is announced
260
- with the span that `on_request_start` returned and its `request_id`; a tool
261
- pass has neither, so both arrive as `nil`.
219
+ is a new hook, called with `scope: :request`, `:tool`, or `:agent` before
220
+ `LLM::Interrupt` reaches the caller, so a tracer can record an interrupted
221
+ turn. An `:agent` scope means the interrupt landed between a turn's
222
+ requests, where there was nothing more precise to interrupt. It does
223
+ nothing by default, since a raise here would replace the interrupt
224
+ callers are written against.
262
225
 
263
226
  * **tracer: announce an interrupted tool phase once** <br>
264
227
  [`LLM::Context#wait`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#wait-instance_method)
265
- now calls the hook once when an interrupt unwinds through it, rather than once
266
- per tool, because a cancel reaches every tool that is running and the caller
267
- hears a single exception, so the announcement belongs to the phase rather than
268
- to a tool. The hook runs before the caller is given the interrupt. A `:ractor`
269
- call that answers `cancelled: true` instead of raising is not announced,
270
- because the phase was not interrupted; one call was retired.
228
+ now calls `on_interrupt` once when an interrupt unwinds through it, rather
229
+ than once per tool, matching the single exception the caller receives.
271
230
 
272
231
  ## v15.5.0
273
232
 
@@ -1482,1752 +1441,3 @@ rate-limited requests, and a range of REPL and skills improvements.
1482
1441
  Update `data/deepinfra.json` with current pricing for the DeepSeek
1483
1442
  V4, DeepSeek-V3, DeepSeek-R1-0528, and Kimi-K3 models, and mark
1484
1443
  `structured_output` support for one model.
1485
-
1486
- ## v14.0.0
1487
-
1488
- Changes since `v13.1.0`.
1489
-
1490
- This release replaces the `transformer=` setter with the new
1491
- `LLM::Transformer` class hierarchy, refactors guards into the
1492
- `LLM::Guard` superclass with per-tool-call interception, and replaces
1493
- the agent `tool_attempts` parameter with the `tool_budget` class DSL.
1494
- It also adds the Moonshot (Kimi) provider, the `LLM::Tool.set`
1495
- bulk-assignment DSL, a `LLM::Function#return` shorthand, and a wide
1496
- range of REPL improvements.
1497
-
1498
- ### Breaking
1499
-
1500
- #### Migration
1501
-
1502
- | Old | New |
1503
- |-----|-----|
1504
- | `ctx.transformer = MyTransformer` | `LLM::Context.new(transformer: MyTransformer)` |
1505
- | `transformer.call(ctx, prompt, params)` | `transformer.call(message:, **opts)` |
1506
- | `~/.llm.rb/session.json` (shared across providers) | `~/.llm.rb/<provider>/<uuid>.json` (scoped per provider and directory) |
1507
- | `agent.talk(tool_attempts: 25)` | `set :tool_budget => 50` (disabled by default) |
1508
- | `LLM::LoopGuard` | `LLM::Guard::Loop` |
1509
- | `guard: true` / `ctx.guard = MyGuard` | `guard: MyGuard, guard_options: {}` |
1510
- | `guard.call(ctx)` (warning string) | `guard.call(function:)` (`LLM::Function::Return` or nil) |
1511
- | `LLM::GuardError` | `"guard_error"` |
1512
-
1513
- * **replace the transformer setter with `LLM::Transformer`** <br>
1514
- The previous `transformer=` setter and 3-argument
1515
- `call(ctx, prompt, params)` interface on `LLM::Context` have been
1516
- replaced by the new
1517
- [`LLM::Transformer`](https://r.uby.dev/api-docs/llm.rb/LLM/Transformer.html)
1518
- class interface. Configure a transformer class through `transformer:`
1519
- and options through `transformer_options:` instead.
1520
-
1521
- * **cli: scope session persistence per provider and directory** <br>
1522
- `bin/llm.rb` no longer shares a single session file between providers.
1523
- Each provider now has a `~/.llm.rb/<provider>.json` file that maps the
1524
- current working directory to a UUID-scoped session file under
1525
- `~/.llm.rb/<provider>/<uuid>.json`, so sessions are scoped to both the
1526
- provider and the directory they were started in.
1527
-
1528
- * **cli: harden the executable against bad inputs** <br>
1529
- `bin/llm.rb` now prints an error message followed by the help menu and
1530
- exits with status 1 when the `-p` switch is given without an argument or
1531
- when an unknown option is passed. Previously unknown options produced a
1532
- warning but the run continued. The session-file lookup also no longer
1533
- rewrites `~/.llm.rb/<provider>.json` when it already exists.
1534
-
1535
- * **agent: replace `tool_attempts` with the `tool_budget` class DSL** <br>
1536
- [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#tool_budget-class_method)
1537
- replaces the `tool_attempts` parameter with a `tool_budget` class DSL
1538
- (`tool_budget { 50 }`) that caps the number of tool calls allowed in a
1539
- single turn. Once the budget is spent, the agent sends an in-band
1540
- advisory message back through the model telling it to solve the problem
1541
- with fewer tool calls.
1542
- <br><br>
1543
- The feature is now disabled by default; the old `tool_attempts`
1544
- parameter defaulted to 25, which long-horizon agents could easily
1545
- exhaust in a single turn.
1546
-
1547
- * **guard: replace `LLM::LoopGuard` with the `LLM::Guard` class hierarchy** <br>
1548
- [`LLM::Guard`](https://r.uby.dev/api-docs/llm.rb/LLM/Guard.html)
1549
- is a new superclass for context-level supervisors, with
1550
- [`LLM::Guard::Loop`](https://r.uby.dev/api-docs/llm.rb/LLM/Guard/Loop.html)
1551
- (replacing `LLM::LoopGuard`) and
1552
- [`LLM::Guard::Null`](https://r.uby.dev/api-docs/llm.rb/LLM/Guard/Null.html)
1553
- as the built-in implementations.
1554
- [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html)
1555
- now accepts `guard:` (a guard class defaulting to `LLM::Guard::Null`)
1556
- and `guard_options:` (a hash forwarded to the guard's `call` method),
1557
- matching the transformer and compactor interfaces. The old boolean and
1558
- hash forms of `guard` and the `guard=` setter are removed. `LLM::Agent`
1559
- enables `LLM::Guard::Loop` by default.
1560
-
1561
- * **guard: block individual tool calls instead of the whole batch** <br>
1562
- [`LLM::Guard#call`](https://r.uby.dev/api-docs/llm.rb/LLM/Guard.html#call-instance_method)
1563
- now receives the pending `function:` and returns an
1564
- [`LLM::Function::Return`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Return.html)
1565
- (or nil) instead of a warning string for the entire batch, so a guard
1566
- can block a single tool call while the rest of the batch still
1567
- executes. Custom guards that implemented the old `call(ctx)`
1568
- warning-string interface must be updated to return a
1569
- `LLM::Function::Return` instead.
1570
-
1571
- * **errors: drop `LLM::GuardError`** <br>
1572
- Remove `LLM::GuardError`. The constant was never raised as an
1573
- exception; it only named the in-band error type for guarded tool
1574
- returns. Guarded tool returns now use the string `"guard_error"` as
1575
- their error type.
1576
-
1577
- ### Core
1578
-
1579
- * **add `LLM::Provider#build_messages` for assembling outgoing messages** <br>
1580
- [`LLM::Provider#build_messages`](https://r.uby.dev/api-docs/llm.rb/LLM/Provider.html#build_messages-instance_method)
1581
- normalizes a prompt into `LLM::Message` objects and prepends the existing
1582
- history, replacing the per-provider `build_complete_messages`
1583
- implementation. The method is idempotent: prompts that are already
1584
- [`LLM::Message`](https://r.uby.dev/api-docs/llm.rb/LLM/Message.html)
1585
- instances or arrays of messages are returned as-is.
1586
-
1587
- * **copy the `params` hash in `LLM::Context` and `LLM::Agent`** <br>
1588
- [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html) and
1589
- [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) now copy
1590
- the `params` hash in their constructors before mutating it, leaving the
1591
- caller's hash untouched. Previously the constructors deleted keys from
1592
- the caller's hash in place.
1593
-
1594
- * **gemspec: ship the deepdive sub-files in the gem** <br>
1595
- The gemspec now includes `resources/deepdive/*/*.md` in the gem
1596
- package, so the full deepdive guide (fundamentals, advanced,
1597
- protocols, and everything-else chapters) is available after
1598
- installation.
1599
-
1600
- * **add short aliases to `LLM::Cost`** <br>
1601
- [`LLM::Cost`](https://r.uby.dev/api-docs/llm.rb/LLM/Cost.html) now
1602
- offers short aliases for its cost accessors: `input`, `output`,
1603
- `input_audio`, `output_audio`, `input_image`, `cache_read`,
1604
- `cache_write`, and `reasoning`. Each alias matches the key used by
1605
- `#to_h`, so `cost.input` reads the same value as `cost.input_costs`.
1606
-
1607
- ### Provider
1608
-
1609
- * **add `LLM::Moonshot` for the Moonshot AI provider** <br>
1610
- [`LLM::Moonshot`](https://r.uby.dev/api-docs/llm.rb/LLM/Moonshot.html)
1611
- is a new provider that talks to
1612
- [Moonshot AI](https://platform.moonshot.ai) through its
1613
- OpenAI-compatible Kimi API. Create an instance with
1614
- [`LLM.moonshot`](https://r.uby.dev/api-docs/llm.rb/LLM.html#moonshot-class_method),
1615
- which accepts the same `key:`, `host:`, and `base_path:` options as the
1616
- OpenAI provider. The provider defaults to the `kimi-k3` model and
1617
- supports chat completions, streaming, tool calls, and structured output
1618
- through the shared OpenAI-compatible path; image, audio, moderation,
1619
- responses, and vector store endpoints raise `NotImplementedError`.
1620
- Model metadata ships in `data/moonshot.json` for the registry.
1621
-
1622
- ### Transformer
1623
-
1624
- * **add `LLM::Transformer` for rewriting messages before they reach the provider** <br>
1625
- [`LLM::Transformer`](https://r.uby.dev/api-docs/llm.rb/LLM/Transformer.html)
1626
- is a new superclass for message transformers. A transformer is bound to a
1627
- context and rewrites a single message before it is sent to the provider,
1628
- which makes it possible to redact personal information or rewrite any
1629
- message before it goes out over the wire. Each subclass implements
1630
- `call(message:, **opts)` and returns the message to send, either by
1631
- mutating it in place or returning a new one.
1632
- [`LLM::Transformer::Null`](https://r.uby.dev/api-docs/llm.rb/LLM/Transformer/Null.html)
1633
- is a no-op transformer used as the default.
1634
-
1635
- * **hook the transformer API into `LLM::Context`** <br>
1636
- [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html) now
1637
- accepts `transformer:` (a transformer class defaulting to
1638
- `LLM::Transformer::Null`) and `transformer_options:` (a hash forwarded to
1639
- the transformer's `call` method). The transformer runs on the most recent
1640
- message in both chat and responses turns.
1641
- [`LLM::Stream#on_transform`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_transform-instance_method)
1642
- and
1643
- [`LLM::Stream#on_transform_finish`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_transform_finish-instance_method)
1644
- now receive the transformer instance as their single argument.
1645
-
1646
- ### Tool
1647
-
1648
- * **add `LLM::Tool.set` for bulk-assigning tool properties** <br>
1649
- [`LLM::Tool.set`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html#set-class_method)
1650
- accepts a hash of `name`, `description`, `parameters`, `required`, and
1651
- `defaults` to configure a tool subclass in a single call. Parameters are
1652
- defined as tuples of `[name, type, description, options]`, matching the
1653
- same interface as the existing `parameter` DSL. Unknown keys raise
1654
- `KeyError`.
1655
-
1656
- ### Function
1657
-
1658
- * **add `LLM::Function#return` for building tool returns** <br>
1659
- [`LLM::Function#return`](https://r.uby.dev/api-docs/llm.rb/LLM/Function.html#return-instance_method)
1660
- returns an
1661
- [`LLM::Function::Return`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Return.html)
1662
- built from the function's own id and name, using the given hash as its
1663
- value. It is a shorthand mainly useful inside a
1664
- [`LLM::Guard`](https://r.uby.dev/api-docs/llm.rb/LLM/Guard.html)
1665
- subclass and is defined via `define_method` because `return` is a Ruby
1666
- keyword.
1667
-
1668
- ### Guard
1669
-
1670
- * **run the guard for streamed tool calls** <br>
1671
- Fix a gap where the guard was not consulted when a tool call was queued
1672
- while a response was still streaming. The guard is now stamped onto the
1673
- functions a context binds, so it runs wherever a task is spawned,
1674
- including tool calls queued from a stream. A blocked call yields its
1675
- `guard_error` return without executing.
1676
-
1677
- ### Agent
1678
-
1679
- * **add `LLM::Agent#compacted?`** <br>
1680
- [`LLM::Agent#compacted?`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#compacted%3F-instance_method)
1681
- delegates to the wrapped
1682
- [`LLM::Context#compacted?`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#compacted%3F-instance_method)
1683
- and reports whether the conversation has been compacted, so callers
1684
- can detect when history was trimmed.
1685
-
1686
- ### Change
1687
-
1688
- * **openai: default to `gpt-5.6-luna`** <br>
1689
- The default OpenAI chat model has changed from `gpt-5.4-mini` to
1690
- `gpt-5.6-luna`. The new model is OpenAI's fastest and most affordable
1691
- option, matching the kind of default llm.rb aims for.
1692
-
1693
- ### Repl
1694
-
1695
- * **show an unknown context state after `/compact`** <br>
1696
- After running `/compact`, the REPL status line now renders `Context
1697
- compacted` and the context-usage bar shows `???` instead of a percentage,
1698
- because the used context is unknown until the next response.
1699
-
1700
- * **land on a blank line after Ctrl+N at the end of history** <br>
1701
- When recalling history with Ctrl+P and Ctrl+N, Ctrl+N at the last item
1702
- now advances to a blank line so you can start typing new input, instead
1703
- of staying stuck on the last item in history (the previous behavior).
1704
- Recalling with Ctrl+P or Ctrl+N also no longer overwrites the input when
1705
- there is no history to show.
1706
-
1707
- * **restore history wrap for Ctrl+P and Ctrl+N** <br>
1708
- Fix a regression where Ctrl+P and Ctrl+N recalled history text without
1709
- reflowing it into rows, so recalled lines wider than the terminal were
1710
- clipped. Recalled text now flows through the same word-wrap path as
1711
- typed input and wraps at the terminal width.
1712
-
1713
- * **restore Ctrl+D deletion across rows** <br>
1714
- Fix a bug where Ctrl+D at the end of an input row was a no-op, so
1715
- multiline input could not be joined by deleting a row break. Deleting
1716
- at the end of a row now consumes the break and pulls the next row up,
1717
- restoring the split space so merged words do not run together.
1718
-
1719
- * **center the buffer with 20% gutters** <br>
1720
- The curses-based REPL now centers
1721
- [`LLM::Repl::Buffer`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Buffer.html)
1722
- in a content area that is 60% of the terminal width, with an unused 20%
1723
- gutter on each side. The drawing area is based on the available rows and
1724
- columns instead of a fixed 80-column width, and `Buffer#wrap` now
1725
- hard-breaks words that overflow the width onto the next row, fixing a
1726
- bug where a word could be cut off between rows.
1727
-
1728
- * **apply markdown to previous messages** <br>
1729
- The curses-based REPL now renders every message in the buffer with
1730
- markdown styling, including messages that were already present when
1731
- the session started or restored from disk. Previously only newly
1732
- streamed responses were styled; older messages fell back to plain
1733
- text.
1734
-
1735
- * **add `LLM::Repl#sender` for the user label** <br>
1736
- [`LLM::Repl#sender`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl.html#sender-instance_method)
1737
- returns the label used for user messages in the curses-based REPL. It
1738
- defaults to `"You"` (previously `"user"`), and the buffer layout now
1739
- places each label on its own line followed by the message content and a
1740
- blank line.
1741
-
1742
- * **add `LLM::Repl::Color` for coloring the curses UI** <br>
1743
- Add
1744
- [`LLM::Repl::Color`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Color.html)
1745
- as a new module that returns Curses color bitmasks. `Color.enable`
1746
- initializes 8 color pairs, and methods like `Color.blue` return the
1747
- corresponding `Curses.color_pair(X)` bitmask, which can be bitwise
1748
- OR'ed with other attributes such as `Curses::A_BOLD`. User labels in
1749
- the REPL are now rendered in blue instead of plain bold text.
1750
-
1751
- * **split on words rather than characters** <br>
1752
- `LLM::Repl::Buffer#wrap` now breaks text on word boundaries instead of
1753
- wrapping one character at a time. A word that does not fit on the
1754
- current row moves to the next, and only a single word longer than the
1755
- whole width is hard-broken, so text is never clipped by the window.
1756
-
1757
- * **render kramdown typographic symbols and smart quotes** <br>
1758
- Fix a bug where certain character sequences such as `...` were not
1759
- rendered at all in the curses-based REPL. Kramdown parses them into
1760
- `:typographic_sym` and `:smart_quote` nodes, which previously fell
1761
- through to the children clause and were dropped. The markdown renderer
1762
- now maps them to their unicode equivalents: ellipsis, en and em
1763
- dashes, guillemets, and single and double quotation marks.
1764
-
1765
- * **apply colors to the markdown renderer** <br>
1766
- The curses-based REPL now renders markdown with the `LLM::Repl::Color`
1767
- palette: headers and strong text in white, code spans and code blocks
1768
- in green, and links in underlined green, on the black background.
1769
- Previously markdown styling used bold, underline, and reverse video
1770
- attributes only.
1771
-
1772
- * **wrap the input line at word boundaries** <br>
1773
- The curses-based REPL input line now wraps words whole onto the next
1774
- row at the terminal width instead of cutting them in half. A word that
1775
- does not fit on the current row moves to the next row, and only a
1776
- single word longer than the whole width is hard-broken, so typed text
1777
- is never clipped by the window.
1778
-
1779
- * **distinguish the connecting and thinking status bar phases** <br>
1780
- The curses-based REPL status bar now shows `Connecting • Esc to
1781
- cancel` while the model is establishing a connection, then switches
1782
- to `Thinking • Esc to cancel` once a tool call or text fragment
1783
- arrives on the stream. Active tool calls appear in the status bar
1784
- with a lambda indicator.
1785
-
1786
- * **add emoji to the status bar phases** <br>
1787
- The curses-based REPL status bar now uses emoji to identify each
1788
- phase at a glance: a globe (`🌐`) while the model is connecting, and
1789
- a brain (`🧠`) while it is thinking. The text after the emoji still
1790
- reads `Connecting • Esc to cancel` and `Thinking • Esc to cancel`
1791
- respectively.
1792
-
1793
- * **render the status bar with color and attributes** <br>
1794
- The curses-based REPL status bar now supports colored and attributed
1795
- status text, so the lambda indicator for active tool calls is drawn
1796
- in bold red.
1797
-
1798
- ### Registry
1799
-
1800
- * **refresh model metadata across providers** <br>
1801
- Update `data/*.json` files with current provider model listings and
1802
- pricing. Remove the deprecated Claude Opus 4.1 entries from the
1803
- Anthropic registry, add `Qwen/Qwen3.8-Max` to DeepInfra, and add a
1804
- `low` reasoning-effort option to DeepSeek.
1805
-
1806
- ## v13.1.0
1807
-
1808
- Changes since `v13.0.0`.
1809
-
1810
- This release adds `LLM::Agent` class DSL attributes (`path`, `description`),
1811
- extends skills with file-path loading and the `tools: all` directive, adds new
1812
- built-in tools (`LLM::Tool::Ruby`, `LLM::Tool::EditFile`), introduces the
1813
- `LLM::Tracer::PrettyLogger` for human-readable tracing, renames `Transcript`
1814
- to `Buffer` across the REPL, ships a `bin/llm.rb` CLI entry point, and fixes
1815
- several agent and tool bugs around persistence, interruption, and naming.
1816
-
1817
- ### Core
1818
-
1819
- * **add post install message with deepdive link** <br>
1820
- The gemspec now includes a `post_install_message` that points users to
1821
- the deepdive guide at `https://r.uby.dev/llm/deepdive` after installation,
1822
- making it easier for new users to discover the project documentation.
1823
-
1824
- ### Agent
1825
-
1826
- * **add `description` class DSL and instance method** <br>
1827
- [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) now
1828
- has a `description` class DSL (`description "release engineer"`) and a
1829
- corresponding `#description` instance method. The description is an
1830
- optional self-documenting string that serves as a brief summary of the
1831
- agent's purpose. It can be set via the class DSL,
1832
- `LLM::Agent.set(description: ...)`, or `LLM::Agent.new(description: ...)`.
1833
-
1834
- * **add `path` class DSL and instance method** <br>
1835
- [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) now
1836
- has a `path` class DSL (`path "contexts/admin.json"`) and a
1837
- corresponding `#path` instance method. When a path is set, the agent
1838
- automatically restores its conversation history from that file on
1839
- initialization and saves it back after each `talk` or `ask` turn,
1840
- making session persistence across process restarts transparent.
1841
-
1842
- ### Skills
1843
-
1844
- * **accept a path to a markdown file** <br>
1845
- [`LLM::Skill.load`](https://r.uby.dev/api-docs/llm.rb/LLM/Skill.html#load-class_method)
1846
- now accepts a path to a markdown file in addition to a directory path.
1847
- When given a file path, the file is read directly instead of looking for
1848
- a `SKILL.md` inside a directory. This makes it possible to load a single
1849
- markdown file as a skill without placing it in a dedicated directory.
1850
-
1851
- * **extend with `all` keyword for loading the full tool registry** <br>
1852
- `LLM::Skill` now supports `tools: all` (or `tools: "*"`) in the frontmatter
1853
- to load all tools from the global
1854
- [`LLM::Tool.registry`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html#registry-class_method).
1855
- Previously, the `tools:` frontmatter only accepted `inherit`, an array of tool
1856
- names, or nothing. The new `all` keyword makes it possible to give a skill
1857
- access to every registered tool without listing them individually.
1858
-
1859
- ### Tools
1860
-
1861
- * **add `LLM::Tool::Ruby` for executing Ruby code in a subprocess** <br>
1862
- [`LLM::Tool::Ruby`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Ruby.html)
1863
- is a new built-in tool that runs a string of Ruby code in a separate
1864
- Ruby process with a configurable timeout (default 15s). The code runs
1865
- in an isolated address space unaware of its parent, making it useful
1866
- for safe(ish) dynamic code execution. It must be required explicitly
1867
- with `require "llm/tools/ruby"` and requires the `test-cmd.rb` gem.
1868
-
1869
- * **rename `LLM::Tool::SwapText` to `LLM::Tool::EditFile`** <br>
1870
- The `SwapText` tool has been renamed to
1871
- [`LLM::Tool::EditFile`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/EditFile.html)
1872
- to better match the naming of sibling tools (`ReadFile`, `WriteFile`).
1873
- The old `require "llm/tools/swap_text"` path no longer exists; use
1874
- `require "llm/tools/edit-file"` instead.
1875
-
1876
- ### Tracer
1877
-
1878
- * **add `LLM::Tracer::PrettyLogger` for human-readable tracing** <br>
1879
- [`LLM::Tracer::PrettyLogger`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer/PrettyLogger.html)
1880
- is a new tracer that writes human-readable request and tool-call logs to a
1881
- console or file. Unlike the structured JSON output of
1882
- `LLM::Tracer::Logger`, the pretty logger emits single-line entries with
1883
- inline context, making it easier to follow agent activity at a glance.
1884
- It writes to `$stderr` by default and accepts an `io:` option for file
1885
- output.
1886
-
1887
- ### Repl
1888
-
1889
- * **rename `LLM::Repl::Transcript` to `LLM::Repl::Buffer`** <br>
1890
- `LLM::Repl::Transcript` has been renamed to
1891
- [`LLM::Repl::Buffer`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Buffer.html)
1892
- to better reflect its role as a conversation state manager. The old
1893
- `start` and `finish` methods have been renamed to `open` and `close`
1894
- respectively. The public accessor on
1895
- [`LLM::Repl`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl.html) has been
1896
- renamed from `transcript` to `buffer`.
1897
-
1898
- * **add `write_message` for formatted message writing** <br>
1899
- [`LLM::Repl::Buffer#write_message`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Buffer.html#write_message-instance_method)
1900
- and
1901
- [`LLM::Repl#write_message`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl.html#write_message-instance_method)
1902
- provide a convenience method that takes a username and content string,
1903
- formatting the output with a bold `user:` label and a trailing newline.
1904
- This is simpler than the equivalent sequence of `write` calls.
1905
-
1906
- * **add `Command#write_message` and refactor `Command#write`** <br>
1907
- [`LLM::Command#write_message`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Command.html#write_message-instance_method)
1908
- provides a convenience method that takes a username and content string,
1909
- matching the same interface on `LLM::Repl` and `LLM::Buffer`. The
1910
- `Command#write` method is now implemented on top of `write_message`,
1911
- always prefixing output with `command(<name>): `. The `who:` keyword
1912
- argument previously accepted by `write` has been removed; use
1913
- `write_message` instead.
1914
-
1915
- * **display pre-existing agent messages when the repl starts** <br>
1916
- When
1917
- [`LLM::Agent#repl`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#repl-instance_method)
1918
- starts, any messages already in the agent's buffer are now rendered
1919
- in the REPL window. Previously the REPL started with an empty
1920
- transcript even when the agent carried prior conversation history,
1921
- making it harder to resume a session. Tool-call and tool-return
1922
- messages are skipped to avoid visual noise.
1923
-
1924
- ### CLI
1925
-
1926
- * **add `bin/llm.rb` for launching the REPL from the command line** <br>
1927
- A new executable script (`bin/llm.rb`) provides a convenient way to start
1928
- an interactive REPL session directly from the terminal. It auto-detects
1929
- the provider from environment variables like `OPENAI_API_KEY`, supports
1930
- a `-p PROVIDER` flag for explicit provider selection, a `-t` flag for
1931
- temporary (non-persistent) sessions, and `-h` for help. Sessions are
1932
- automatically saved to `~/.llm.rb/` by default.
1933
-
1934
- ### Fix
1935
-
1936
- * **agent: fix `path` restore on first run** <br>
1937
- Fix a bug where [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
1938
- called `@ctx.restore(path:)` even when the path's file did not exist.
1939
- The fix checks `File.readable?(@path)` before attempting to restore,
1940
- so the agent starts with a blank conversation on first use instead of
1941
- failing with a file-not-found error.
1942
-
1943
- * **tools: re-raise `LLM::Interrupt` to abort the turn** <br>
1944
- [`LLM::Tool::Git`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Git.html),
1945
- [`LLM::Tool::Mkdir`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Mkdir.html),
1946
- [`LLM::Tool::Rg`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Rg.html),
1947
- [`LLM::Tool::Ruby`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Ruby.html),
1948
- and
1949
- [`LLM::Tool::Shell`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Shell.html)
1950
- now re-raise `LLM::Interrupt` after killing their running command. The
1951
- previous behavior rescued the interrupt and killed the child process but
1952
- let the turn continue, which meant a cancelled tool call did not abort
1953
- the conversation turn. Re-raising ensures the entire turn is interrupted.
1954
-
1955
- * **tools: rescue `LLM::Interrupt` in shell-based tools** <br>
1956
- [`LLM::Tool::Shell`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Shell.html),
1957
- [`LLM::Tool::Git`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Git.html),
1958
- [`LLM::Tool::Mkdir`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Mkdir.html),
1959
- and
1960
- [`LLM::Tool::Rg`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Rg.html)
1961
- now rescue `LLM::Interrupt` and kill their running command, preventing
1962
- orphaned child processes when a tool is interrupted during execution.
1963
-
1964
- * **agent: fix default name derivation** <br>
1965
- Fix a bug where `LLM::Agent` used without a subclass derived its default
1966
- name as `"l-lm-agent"` instead of `"agent"`. The fix replaces the
1967
- regex-based parameterization with a pattern that correctly handles
1968
- single-word class names and multi-word namespaced names.
1969
-
1970
- * **function: `#params` always returns an `LLM::Object`** <br>
1971
- [`LLM::Function#params`](https://r.uby.dev/api-docs/llm.rb/LLM/Function.html#params-instance_method)
1972
- now always returns an `LLM::Object` representing the function's parameter
1973
- schema. Previously it returned `nil` when a function defined no parameters,
1974
- forcing every caller to guard against `nil`. All provider adapters now use
1975
- `fn.params.to_h` instead of `fn.params || {type: "object", properties: {}}`.
1976
-
1977
- ## v13.0.0
1978
-
1979
- v13.0.0 is released under the MIT license. Commercial, personal,
1980
- educational, and all other uses are permitted under the standard
1981
- MIT terms.
1982
-
1983
- Seven breaking changes. Concurrency strategies have been renamed
1984
- (`:call` → `:sequential`, `:task` → `:async`), `spawn` is now
1985
- `task`, and the `:async` strategy has been rebuilt from the ground
1986
- up. It no longer blocks and now supports interruption. The compactor
1987
- has been refactored into pluggable strategies. Interruption is now
1988
- reliable across all six concurrency backends. The `functions` and
1989
- `functions?` methods have been renamed to `pending_functions` and
1990
- `pending_functions?`.
1991
-
1992
- ### Migration from v12.6.0
1993
-
1994
- | Old | New |
1995
- |-----|-----|
1996
- | `fn.spawn(:call)` | `fn.task(:sequential)` |
1997
- | `fn.spawn(:task)` | `fn.task(:async)` |
1998
- | `ctx.wait(:call)` | `ctx.wait(:sequential)` |
1999
- | `agent.concurrency :task` | `agent.concurrency :async` |
2000
- | `LLM::Function::FiberGroup` | `LLM::Function::Fiber::Group` |
2001
- | `LLM::Function::CallGroup` | `LLM::Function::Sequential::Group` |
2002
- | `LLM::Function::TaskGroup` | `LLM::Function::Async::Group` |
2003
- | `Compactor.new(model:, token_threshold:)` | `Compactor::Truncate.new(ctx)` |
2004
- | `on_compaction(ctx, compactor)` | `on_compaction(compactor)` |
2005
- | `ctx.functions` / `ctx.functions?` | `ctx.pending_functions` / `ctx.pending_functions?` |
2006
- | `agent.functions` / `agent.functions?` | `agent.pending_functions` |
2007
-
2008
- ### Breaking
2009
-
2010
- * **rename `LLM::Function#spawn` as `LLM::Function#task`** <br>
2011
- [`LLM::Function#task`](https://r.uby.dev/api-docs/llm.rb/LLM/Function.html#task-instance_method)
2012
- (previously `spawn`) now consistently returns a
2013
- [`LLM::Function::Task`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Task.html)
2014
- object that can be spawned, waited on, and passed to
2015
- [`LLM::Function::Group`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Group.html).
2016
- The old implementation alternated between spawning immediately or
2017
- returning a raw thread or fiber.
2018
-
2019
- * **rename concurrency strategies (`:call` → `:sequential`,**
2020
- **`:task` → `:async`)** <br>
2021
- The `:call` concurrency strategy is now `:sequential`, and the `:task`
2022
- strategy is now `:async`.
2023
- [`LLM::Agent.concurrency`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#concurrency-class_method),
2024
- [`LLM::Context#wait`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#wait-instance_method),
2025
- `LLM::Function::Array#task`, and
2026
- [`LLM::Function#task`](https://r.uby.dev/api-docs/llm.rb/LLM/Function.html#task-instance_method)
2027
- all accept the new names. The old names raise `ArgumentError`.
2028
-
2029
- * **rename group classes** <br>
2030
- Group classes have been moved into their strategy's namespace:
2031
- `FiberGroup` → `Fiber::Group`, `ThreadGroup` → `Thread::Group`,
2032
- `CallGroup` → `Sequential::Group`, `TaskGroup` → `Async::Group`,
2033
- `Fork::Group` → `Fork::Group`, `Ractor::Group` → `Ractor::Group`.
2034
-
2035
- * **repurpose `LLM::Function::Task` as a task interface superclass** <br>
2036
- `LLM::Function::Task` has been repurposed from a general-purpose class
2037
- that tried to support multiple concurrency strategies into an abstract
2038
- base class that defines the task interface. Individual strategies
2039
- (`Sequential::Task`, `Thread::Task`, `Fiber::Task`, `Async::Task`,
2040
- `Fork::Task`, `Ractor::Task`) now subclass it and implement
2041
- `spawn`, `alive?`, `interrupt!`, and `wait`.
2042
-
2043
- * **fix `:async` concurrency (now backed by a managed**
2044
- **`LLM::Function::Async::Reactor` on a background thread)** <br>
2045
- The `:async` strategy previously used `Async {}` which blocked the
2046
- caller until all tasks completed and did not support interruption.
2047
- The fix replaces it with a per-turn
2048
- `LLM::Function::Async::Reactor` on a background thread. Work is
2049
- submitted via `submit(&block)` and consumed by the reactor's event
2050
- loop through a thread-safe `Queue`. `Async::Group` manages the
2051
- reactor lifecycle and spawns tasks lazily on `wait`.
2052
- <br><br>
2053
- Interruption pushes [`LLM::Interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Interrupt.html)
2054
- to the task's result queue instead of using `Fiber#raise`, and
2055
- results are bridged back to the caller through a second `Queue`.
2056
- This work drove the broader refactor of strategy naming, the
2057
- spawn/wait split, and the `Task` superclass. The `:async` strategy
2058
- needed the same interface the other strategies already had.
2059
-
2060
- * **compactor: refactor to strategy-based interface** <br>
2061
- [`LLM::Compactor`](https://r.uby.dev/api-docs/llm.rb/LLM/Compactor.html)
2062
- has been refactored from a single class that performed LLM-based
2063
- summarization into a strategy-based superclass. Each subclass
2064
- implements a different compaction strategy via `call(**opts)`. The old
2065
- summarization approach (using `model:`, `token_threshold:`,
2066
- `message_threshold:`, and `retention_window:` options) has been removed.
2067
- The built-in
2068
- [`LLM::Compactor::Truncate`](https://r.uby.dev/api-docs/llm.rb/LLM/Compactor/Truncate.html)
2069
- strategy drops the oldest messages when the conversation exceeds a
2070
- configured size.
2071
-
2072
- * **rename `LLM::Context#{functions,functions?}` and `LLM::Agent#{functions,functions?}`** <br>
2073
- [`LLM::Context#functions`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#functions-instance_method)
2074
- and
2075
- [`LLM::Context#functions?`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#functions%3F-instance_method)
2076
- have been renamed to
2077
- [`LLM::Context#pending_functions`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#pending_functions-instance_method)
2078
- and
2079
- [`LLM::Context#pending_functions?`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#pending_functions%3F-instance_method)
2080
- respectively. The same rename applies to
2081
- [`LLM::Agent#functions`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#functions-instance_method)
2082
- (now
2083
- [`LLM::Agent#pending_functions`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#pending_functions-instance_method)).
2084
- The `pending_functions` name was already available as an alias in
2085
- v12.5.0; this change removes the old `functions` name entirely.
2086
-
2087
- ### Core
2088
-
2089
- * **extend `LLM.require` with an optional version argument** <br>
2090
- `LLM.require` now accepts a second `version` parameter that is passed
2091
- to `Kernel#gem` before loading, enabling version constraints for
2092
- optional runtime dependencies. For example,
2093
- `LLM.require "test-cmd.rb", "~> 2.2"` ensures a minimum gem version
2094
- is available. This is used internally by the `Git`, `Rg`, `Mkdir`,
2095
- and `Shell` tools to enforce compatibility with the `test-cmd.rb` gem.
2096
-
2097
- ### Compactor
2098
-
2099
- * **add `Truncate` strategy for dropping oldest messages** <br>
2100
- [`LLM::Compactor::Truncate`](https://r.uby.dev/api-docs/llm.rb/LLM/Compactor/Truncate.html)
2101
- is a new built-in compaction strategy that drops the oldest messages
2102
- when the conversation exceeds a configured size. It preserves tool
2103
- call/return pairs so the algorithm never breaks in the middle of a
2104
- sequence. Configured with `keep:` (default 64), it emits the standard
2105
- `on_compaction` and `on_compaction_finish`
2106
- [`LLM::Stream`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html)
2107
- lifecycle callbacks. No LLM call is made; the strategy is purely
2108
- lossy but fast and requires no network.
2109
-
2110
- * **raise when given an unparseable `keep:` value** <br>
2111
- `LLM::Compactor::Truncate` now raises `ArgumentError` when the `keep:`
2112
- parameter cannot be parsed as an integer or percentage string, instead
2113
- of failing with an obscure error later during execution.
2114
-
2115
- * **accept percentage string for the `keep:` parameter** <br>
2116
- `LLM::Compactor::Truncate#call` now accepts a percentage string such
2117
- as `"80%"` for the `keep:` parameter, which keeps approximately 80%
2118
- of the most recent messages. Integer values continue to work as
2119
- before. This makes it easy to trim proportionally rather than to an
2120
- absolute number of messages.
2121
-
2122
- * **add `Null` strategy for no-op compaction** <br>
2123
- [`LLM::Compactor::Null`](https://r.uby.dev/api-docs/llm.rb/LLM/Compactor/Null.html)
2124
- is a new built-in compaction strategy that does nothing. It is used as
2125
- the default compactor when no strategy is configured on a context,
2126
- ensuring the compactor interface is always present without requiring a
2127
- separate nil check.
2128
-
2129
- * **accept both `LLM::Agent` and `LLM::Context`** <br>
2130
- [`LLM::Compactor#initialize`](https://r.uby.dev/api-docs/llm.rb/LLM/Compactor.html#initialize-instance_method)
2131
- now accepts both `LLM::Agent` and `LLM::Context` instances. When given
2132
- an agent, the internal context is unwrapped automatically, making the
2133
- compactor API more flexible when working with agents.
2134
-
2135
- #### Context integration
2136
-
2137
- * **accept `compactor` and `compactor_options` parameters** <br>
2138
- [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html)
2139
- now accepts `compactor:` (a compactor class defaulting to
2140
- [`LLM::Compactor::Null`](https://r.uby.dev/api-docs/llm.rb/LLM/Compactor/Null.html))
2141
- and `compactor_options:` (a hash of options forwarded to the
2142
- compactor's `call` method) parameters. The compactor is automatically
2143
- invoked at the beginning of each `talk` turn. The previous `compactor=`
2144
- setter has been removed in favour of constructor-driven configuration.
2145
-
2146
- * **`on_compaction` and `on_compaction_finish` receive a single argument** <br>
2147
- [`LLM::Stream#on_compaction`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_compaction-instance_method)
2148
- and
2149
- [`LLM::Stream#on_compaction_finish`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_compaction_finish-instance_method)
2150
- now accept a single argument (the compactor instance) instead of two
2151
- arguments (context and compactor). The context is still available via
2152
- `LLM::Compactor#ctx`, so access to the context is not lost. This
2153
- simplifies the callback interface for compaction lifecycle observers.
2154
-
2155
- ### Tools
2156
-
2157
- * **add `LLM::Tool::Utils` module for shared command execution logic** <br>
2158
- A new
2159
- [`LLM::Tool::Utils`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Utils.html)
2160
- module provides shared `wait(command:, timeout:)` and `now` helper
2161
- methods for tools that execute commands. Tools that include `Utils` can
2162
- wait on a running command and automatically kill it when it exceeds the
2163
- configured timeout, using `Process.clock_gettime` with `CLOCK_MONOTONIC`
2164
- for precise timing. The module is used by both the `Shell` and `Rg`
2165
- tools internally.
2166
-
2167
- * **shell: add `timeout` parameter for command execution deadlines** <br>
2168
- The
2169
- [`LLM::Tool::Shell`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Shell.html)
2170
- tool now accepts a `timeout` parameter (default 60s) that automatically
2171
- kills commands exceeding the specified time limit, preventing hung
2172
- processes from blocking the agent indefinitely.
2173
-
2174
- * **rg: add `timeout` parameter for search execution deadlines** <br>
2175
- The
2176
- [`LLM::Tool::Rg`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Rg.html)
2177
- tool now accepts a `timeout` parameter (default 5s) that automatically
2178
- kills search commands exceeding the specified time limit, preventing
2179
- long-running searches from blocking the agent indefinitely.
2180
-
2181
- * **git: add `timeout` parameter for command execution deadlines** <br>
2182
- The
2183
- [`LLM::Tool::Git`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Git.html)
2184
- tool now accepts a `timeout` parameter (default 5s) that automatically
2185
- kills git commands exceeding the specified time limit, preventing hung
2186
- processes from blocking the agent indefinitely.
2187
-
2188
- ### Schema
2189
-
2190
- * **properties are now ordered and support indifferent access** <br>
2191
- [`LLM::Schema::Leaf`](https://r.uby.dev/api-docs/llm.rb/LLM/Schema/Leaf.html)
2192
- tracks property definition order in a new `index` attribute, matching
2193
- the convention already used by
2194
- [`LLM::Command::Parameter`](https://r.uby.dev/api-docs/llm.rb/LLM/Command/Parameter.html).
2195
- Internally, `@properties` is stored as an
2196
- [`LLM::Object`](https://r.uby.dev/api-docs/llm.rb/LLM/Object.html)
2197
- instead of a plain `Hash`, so lookups with both string and symbol keys
2198
- work.
2199
-
2200
- ### Buffer
2201
-
2202
- * **more array-like message management** <br>
2203
- [`LLM::Buffer`](https://r.uby.dev/api-docs/llm.rb/LLM/Buffer.html)
2204
- now exposes `first`, `reject!`, `select!`, `shift`, `clear`, `drop`,
2205
- `take`, and `reverse`, making it easier to query and mutate
2206
- `LLM::Context#messages` like an ordinary Array. `reject!` is aliased
2207
- as `delete_if` for familiarity.
2208
-
2209
- * **`last(nil)` no longer returns the last message** <br>
2210
- `LLM::Buffer#last` now uses an internal `UNDEFINED` sentinel to
2211
- distinguish between no argument (`last` returns the last message)
2212
- and `nil` (`last(nil)` is treated as an argument). Previously `nil`
2213
- was indistinguishable from no argument.
2214
-
2215
- ### Function
2216
-
2217
- * **consolidate `call` and `call!` into one method** <br>
2218
- The private `call!` method has been merged into the public
2219
- [`LLM::Function#call`](https://r.uby.dev/api-docs/llm.rb/LLM/Function.html#call-instance_method).
2220
- The separate `call!` method existed for tracer-scoping logic now
2221
- handled directly inside `call`. All internal call sites now use
2222
- `function.call` instead of `function.call!`.
2223
-
2224
- * **add `LLM::Function::Group` as an abstract base class** <br>
2225
- A new abstract base class
2226
- ([`LLM::Function::Group`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Group.html))
2227
- defines the interface that all concurrency strategy groups must
2228
- implement: `alive?`, `interrupt!`, and `wait`. Each strategy group
2229
- (`Sequential::Group`, `Thread::Group`, `Fiber::Group`,
2230
- `Async::Group`, `Fork::Group`, `Ractor::Group`) now subclasses
2231
- this base.
2232
-
2233
- * **split `spawn` and `wait` across all strategies** <br>
2234
- `spawn` now starts execution without blocking, and `wait` collects
2235
- the result. `on_tool_start` moved into each task's `spawn` so the
2236
- tracer span covers execution rather than construction. Each task and
2237
- group now exposes a public `spawn` method alongside the existing
2238
- `wait`/`value` methods.
2239
-
2240
- * **spawn tasks lazily in `Group#wait`** <br>
2241
- All concurrency strategy groups (Fiber::Group, Fork::Group,
2242
- Ractor::Group, Thread::Group) now automatically spawn their tasks
2243
- when `wait` is called if they haven't been spawned yet, matching
2244
- the existing `Async::Group` behavior. This makes the spawn/wait
2245
- contract consistent across all six concurrency backends.
2246
-
2247
- ### Agent
2248
-
2249
- * **add `name` class DSL and instance method** <br>
2250
- [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) now
2251
- has a `name` class DSL (`name "admin"`) and a corresponding `#name`
2252
- instance method. The name is resolved through the same lazy-resolution
2253
- path as other agent attributes. It can be set via
2254
- `LLM::Agent.set(name: ...)`, `LLM::Agent.new(name: ...)`, or the class
2255
- DSL. When no name is given, a default is derived from the class name
2256
- (e.g., `SystemAdmin` becomes `system-admin`). The REPL uses the name
2257
- as the prompt label and transcript prefix, making it easier to
2258
- distinguish multiple sessions.
2259
-
2260
- ### REPL
2261
-
2262
- * **agent identity in the prompt** <br>
2263
- [`LLM::Agent#repl`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#repl-instance_method)
2264
- and
2265
- [`LLM::Repl.new`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl.html#initialize-instance_method)
2266
- accept a `name:` parameter (defaulting to `LLM::Agent#name`) that sets
2267
- the input prompt to `provider(name)> ` and labels transcript messages
2268
- with the agent's name instead of a hardcoded `agent:`. Useful when
2269
- running multiple sessions.
2270
-
2271
- * **`/compact` command** <br>
2272
- New built-in `/compact` command frees context window space by dropping
2273
- the oldest messages via
2274
- [`LLM::Compactor::Truncate`](https://r.uby.dev/api-docs/llm.rb/LLM/Compactor/Truncate.html).
2275
- Supports both integer (`/compact 32`) and percentage (`/compact 75%`)
2276
- arguments. Defaults to keeping the last 128 messages.
2277
-
2278
- * **tab-completion for `/` commands** <br>
2279
- Pressing Tab on an input line starting with `/` autocompletes the
2280
- command name. Repeated Tab presses cycle through matching commands.
2281
- Powered by
2282
- [`LLM::Command.complete(str)`](https://r.uby.dev/api-docs/llm.rb/LLM/Command.html#complete-class_method)
2283
- which is available outside the REPL too.
2284
-
2285
- * **command system enhancements** <br>
2286
- Commands can set parameter defaults in their `call` method signature
2287
- (e.g., `def call(n: 128)`). Aliases like `/quit` now inherit their
2288
- parent's description and parameters. Commands also have access to
2289
- the active `agent` and `repl` via public readers.
2290
-
2291
- * **tool argument sorting** <br>
2292
- Tool parameters in the status bar are now displayed in definition
2293
- order (using the new `index` attribute), regardless of the order
2294
- the model returns them.
2295
-
2296
- * **expanded markdown rendering** <br>
2297
- The curses-based markdown renderer now handles lists (`<ul>`, `<ol>`),
2298
- blockquotes, horizontal rules, hyperlinks (underline), images
2299
- (`[image: alt text]`), and tables (aligned columns).
2300
-
2301
- * **input improvements** <br>
2302
- Ctrl+P and Ctrl+N walk through conversation history (user messages
2303
- only, managed by
2304
- [`LLM::Repl::Walker`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Walker.html)).
2305
- Page Up/Down scroll the transcript by a page. ENTER and BACKSPACE are
2306
- now mapped to raw character codes from `Curses.getch` instead of
2307
- `Curses::Key` constants.
2308
-
2309
- ### Object
2310
-
2311
- * **preserve the original key name in `KeyError` messages** <br>
2312
- `LLM::Object#fetch` now preserves the original key name when a key
2313
- is not found, instead of raising `KeyError` with `key not found: nil`.
2314
- The previous behavior occurred when the given key was not found in
2315
- the stored hash, causing internal lookup to return `nil` and lose
2316
- the original key reference.
2317
-
2318
- ### Registry
2319
-
2320
- * **refresh model metadata across providers** <br>
2321
- Update `data/*.json` files with current provider model listings and
2322
- pricing. Mark several DeepInfra models as deprecated (`meta-llama/
2323
- Meta-Llama-3.1-8B-Instruct`, `Qwen/Qwen1.5-110B-Chat`, and
2324
- `mistralai/Mixtral-8x7B-Instruct-v0.1`). Correct xAI cache-read
2325
- pricing from $0.50 to $0.30 per million input tokens.
2326
-
2327
- ### Fix
2328
-
2329
- * **agent: fix default name resolution when name is not explicitly set** <br>
2330
- Fix a bug where `LLM::Agent` derived its default name from
2331
- `self.class` instead of `self`, causing the name to be `"class"`
2332
- instead of a parameterized version of the actual class name
2333
- (e.g., `"system-admin"` for `SystemAdmin`). The fix uses `self`
2334
- directly, which correctly resolves the class name at the instance
2335
- level.
2336
-
2337
- * **function: make Fork::Task and Ractor::Task inherit LLM::Function::Task** <br>
2338
- `LLM::Function::Fork::Task` and `LLM::Function::Ractor::Task` now
2339
- explicitly subclass `LLM::Function::Task` and accept an options hash
2340
- as their second argument, matching the constructor signature used
2341
- by the other four task classes. The interface was already compatible
2342
- but the inheritance was missing by mistake. It is now consistent across
2343
- all six concurrency backends.
2344
-
2345
- * **google: fix `stream` parameter leakage that broke the provider** <br>
2346
- Fix a bug in the Google provider where `stream: stream.enabled?` was
2347
- being merged into request parameters, causing API-level errors. The
2348
- Google provider does not use a `stream` parameter. Streaming is
2349
- controlled via the URL path (`streamGenerateContent` vs
2350
- `generateContent`). The fix removes the leaked parameter and correctly
2351
- routes streaming requests through the appropriate path.
2352
-
2353
- * **fork: fix deadlock on xchan.rb channel** <br>
2354
- Fix a deadlock in the `:fork` concurrency strategy where both the
2355
- writer and reader could get stuck on the xchan channel, preventing the
2356
- reader from draining the channel. The deadlock surfaced as an errno
2357
- failure, especially with large tool returns. The fix requires xchan.rb
2358
- v0.22.0 and uses the `SOCK_STREAM` socket type for communicating a
2359
- tool's return value.
2360
-
2361
- ## v12.6.0
2362
-
2363
- Changes since `v12.5.1`.
2364
-
2365
- This release adds bulk defaults for tools and agents: `LLM::Tool.defaults`
2366
- for setting parameter defaults and `LLM::Agent.set` for mass-assigning
2367
- class-level defaults, both mirrored on ActiveRecord and Sequel agent models.
2368
-
2369
- It also makes `LLM::Interrupt` reliable across every concurrency strategy
2370
- (:thread, :call, :fiber, :task, :fork, and :ractor) so tool cancellation
2371
- works consistently regardless of execution backend, and fixes a stale fiber
2372
- reference in `LLM::Context#talk` that could prevent interruption after a
2373
- prior call.
2374
-
2375
- ### Add
2376
-
2377
- * **tool: add `defaults` method for setting parameter defaults** <br>
2378
- Add `LLM::Tool.defaults(properties)` for bulk-setting default values
2379
- on tool parameters, matching the same interface as `LLM::Schema.defaults`.
2380
- Each key maps to a parameter name; unknown keys raise `KeyError`.
2381
-
2382
- * **agent: add `set` method for bulk-assigning class-level defaults** <br>
2383
- Add `LLM::Agent.set(properties)` for mass-assigning agent defaults
2384
- from a Hash. Each key maps to a class-level accessor; unknown keys
2385
- raise `KeyError`.
2386
-
2387
- * **active_record: expose `set` on `acts_as_agent` models** <br>
2388
- ActiveRecord models using `acts_as_agent` can call `set` to
2389
- bulk-assign agent class-level defaults.
2390
-
2391
- * **sequel: expose `set` on `plugin :agent` models** <br>
2392
- Sequel models using `plugin :agent` can call `set` to bulk-assign
2393
- agent class-level defaults.
2394
-
2395
- ### Fix
2396
-
2397
- * **function: raise `LLM::Interrupt` on thread where tool is running** <br>
2398
- On cancel, `LLM::Interrupt` is now raised on the thread that is
2399
- running a tool. The tool can rescue `LLM::Interrupt` and gracefully
2400
- terminate (e.g., clean up resources). The previous approach used
2401
- `Thread#interrupt` which was less reliable. It did not interrupt a
2402
- sleeping thread.
2403
-
2404
- * **function: suppress thread exception reporting in `:thread` concurrency** <br>
2405
- Threads spawned by the `:thread` concurrency strategy now have
2406
- `report_on_exception` set to `false`, preventing noisy exception
2407
- messages from appearing on stderr when a thread is interrupted
2408
- during tool execution.
2409
-
2410
- * **context: clear `@owner` after `talk` completes** <br>
2411
- `LLM::Context#talk` now clears the `@owner` reference in an
2412
- `ensure` block after the method completes, so `interrupt!` does
2413
- not attempt to interrupt a stale fiber reference from a prior
2414
- call.
2415
-
2416
- * **function: raise `LLM::Interrupt` on thread waiting in `CallGroup#wait`** <br>
2417
- When using `ctx.wait(:call)`, `LLM::Interrupt` is now raised on the
2418
- thread executing the sequential tool wait. `CallGroup#wait` tracks the
2419
- active thread and `interrupt!` raises `LLM::Interrupt` on it, enabling
2420
- interruption of the `:call` concurrency strategy just like the existing
2421
- `:thread` strategy.
2422
-
2423
- * **function: raise `LLM::Interrupt` on fiber-backed tool tasks** <br>
2424
- `LLM::Interrupt` is now raised on the active fiber via `Fiber#raise`
2425
- when interrupting `:fiber`-concurrency tools.
2426
- <br><br>
2427
- `Task#interrupt!` now dispatches by task type: `Thread#raise` for
2428
- threads, `Fiber#raise` for fibers. Making interruption reliable
2429
- across all concurrency strategies.
2430
-
2431
- * **function: raise `LLM::Interrupt` on fork-backed tool tasks** <br>
2432
- `LLM::Interrupt` is now raised on the main thread of a fork child
2433
- process via `Thread.main.raise(LLM::Interrupt)` when interrupting
2434
- `:fork`-concurrency tools, and the fork `Task#wait` re-raises the
2435
- interrupt on the parent side. Making interruption reliable across
2436
- all concurrency strategies including `:fork`.
2437
-
2438
- * **function: raise `LLM::Interrupt` on `Async::Task`-backed tool tasks** <br>
2439
- `LLM::Interrupt` is now raised on the underlying fiber of an
2440
- `Async::Task` via `Fiber#raise` when interrupting `:task`-concurrency
2441
- tools. `Task#interrupt!` now detects `Async::Task` instances and
2442
- dispatches to `Fiber#raise`, extending reliable interruption to the
2443
- `:task` concurrency strategy under the Async runtime.
2444
-
2445
- * **function: raise `LLM::Interrupt` on ractor-backed tool tasks** <br>
2446
- `LLM::Interrupt` is now raised on the main thread inside a ractor
2447
- via `Thread.main.raise(LLM::Interrupt)` when interrupting
2448
- `:ractor`-concurrency tools. A listener thread inside the tool
2449
- ractor waits for an interrupt message via `Ractor.receive` and
2450
- raises `LLM::Interrupt` on the ractor's main thread.
2451
- <br><br>
2452
- `Task#interrupt!` delegates to the mailbox to send the interrupt
2453
- message. Extending reliable interruption to the `:ractor`
2454
- concurrency strategy.
2455
-
2456
- ## v12.5.1
2457
-
2458
- Changes since `v12.5.0`.
2459
-
2460
- This release reverts the global `LLM::Function` registry fallback for tool
2461
- resolution that was added in v12.5.0.
2462
-
2463
- ### Change
2464
-
2465
- * **function: remove the global registry fallback for tool resolution** <br>
2466
- Remove the `LLM::Function.find_by_name` fallback that was added in
2467
- v12.5.0 as an intermediate step between available-tools lookup and
2468
- raising `LLM::NoSuchToolError`. Tool calls not found in the available
2469
- tools list now go directly to `function_missing` (which raises
2470
- `LLM::NoSuchToolError`) without first checking the global
2471
- `LLM::Function` registry.
2472
-
2473
- ## v12.5.0
2474
-
2475
- Changes since `v12.4.0`.
2476
-
2477
- This release extends the REPL command system with typed parameters, a
2478
- built-in `/help` command, command aliases (`/quit`), and cancellation
2479
- via the 'Esc' key.
2480
-
2481
- The default HTTP timeout is increased to 15 minutes (900s) to better
2482
- accommodate reasoning models and large structured outputs.
2483
- `LLM::Agent#deserialize` and `LLM::Agent#restore` now return `self` for
2484
- method chaining, and `LLM::Buffer#pop` is added for tail-end message
2485
- removal.
2486
-
2487
- Tool resolution gains a fallback to the global `LLM::Function` registry
2488
- before raising `LLM::NoSuchToolError`, and `pending_functions` aliases
2489
- are added on both contexts and agents for a consistent interface.
2490
-
2491
- Several REPL bugs are fixed including parameter state leakage across
2492
- turns and invalid tool-call error routing.
2493
-
2494
- Model metadata is refreshed across all providers with new Anthropic,
2495
- OpenAI, Google, DeepInfra, DeepSeek, and xAI model entries.
2496
-
2497
- ### Add
2498
-
2499
- #### Buffer & function internals
2500
-
2501
- * **buffer: add `LLM::Buffer#pop`** <br>
2502
- Add `LLM::Buffer#pop` for removing the last message from the tail
2503
- of the buffer, complementing the existing `#<<` and array-style
2504
- message management.
2505
-
2506
- * **function: add registry fallback for tool resolution** <br>
2507
- When resolving tool calls from a message, if the tool is not found
2508
- in the available tools list, it now also looks up the global
2509
- `LLM::Function` registry via `LLM::Function.find_by_name` before
2510
- creating a placeholder function. This improves tool resolution for
2511
- tools that are registered globally but not passed directly through
2512
- the request tool set.
2513
-
2514
- #### Consistent `pending_functions` aliases
2515
-
2516
- * **context: alias `LLM::Context#functions` as `LLM::Context#pending_functions`** <br>
2517
- Add `LLM::Context#pending_functions` as an alias for `LLM::Context#functions`,
2518
- so callers that prefer the more descriptive `pending_functions` name can use
2519
- it instead of `functions` when checking for unresolved tool work.
2520
-
2521
- * **agent: alias `LLM::Agent#functions` as `LLM::Agent#pending_functions`** <br>
2522
- Add `LLM::Agent#pending_functions` as an alias for `LLM::Agent#functions`,
2523
- matching the same alias on `LLM::Context`, so callers have a consistent
2524
- `pending_functions` interface across both contexts and agents.
2525
-
2526
- #### REPL command system
2527
-
2528
- * **repl: extend command system with parameter support** <br>
2529
- Commands can now declare typed parameters using the `parameter`
2530
- DSL, modelled after `LLM::Tool` and `LLM::Schema` conventions.
2531
- Parameters can be marked as required with `required %i[...]`,
2532
- and values are type-checked before being passed to `call`.
2533
- Argument parsing is handled by the repl: arguments are split
2534
- from the input string and assigned to parameters by position.
2535
-
2536
- ```ruby
2537
- class Greeter < LLM::Command
2538
- name "greet"
2539
- description "Greets the given name"
2540
- parameter :name, String, "The person's name"
2541
- required %i[name]
2542
-
2543
- def call(name:)
2544
- write("Welcome #{name}!\n")
2545
- end
2546
- end
2547
- ```
2548
-
2549
- * **repl: add `help` command** <br>
2550
- Add `LLM::Repl::Help` as a new built-in command, registered
2551
- automatically via the command registry. Typing `/help` shows
2552
- the `help` command's own name, description, and parameters,
2553
- while `/help <name>` shows details for a specific command,
2554
- including its parameters and whether each is required or
2555
- optional. Unknown command names produce an error message.
2556
-
2557
- ```ruby
2558
- class Help < Command
2559
- name "help"
2560
- description "show help for a given command"
2561
- parameter :name, String, "The name of a command"
2562
-
2563
- def call(name: nil)
2564
- if name.nil?
2565
- write("\n#{self.class.help}\n\n")
2566
- elsif command = LLM::Command.find_by(name:)
2567
- write("\n#{command.help}\n\n")
2568
- else
2569
- write "\nNo help for #{name} was found" \
2570
- "\nThat command doesn't exist.\n\n"
2571
- end
2572
- end
2573
- end
2574
- ```
2575
-
2576
- * **repl: add support for command aliases** <br>
2577
- Commands can now be aliased by creating a subclass of another
2578
- command (with `LLM::Command` as an indirect ancestor). The
2579
- first alias introduced is `/quit` as an alias of `/exit`.
2580
-
2581
- ```ruby
2582
- class Quit < Command::Exit
2583
- name "quit"
2584
- end
2585
- ```
2586
-
2587
- * **repl: add `Command::Parameter#optional?`** <br>
2588
- Parameters now expose an `#optional?` method that returns `true`
2589
- when a parameter has not been marked as required, making it
2590
- possible to query parameter optionality programmatically.
2591
-
2592
- * **repl: add `LLM::Repl::Command#write`** <br>
2593
- Commands can now write output to the transcript via the `write`
2594
- method. Commands also receive a reference to the active repl
2595
- through their `#initialize` method, making it possible to
2596
- interact with the repl window from within a command.
2597
-
2598
- * **repl: display command errors in the curses UI** <br>
2599
- Commands invoked with too few arguments now display an error
2600
- message: `command(<name>): too few arguments`. Displayed directly in
2601
- the curses transcript area, giving immediate feedback instead
2602
- of silently failing.
2603
-
2604
- * **repl: add `LLM::Command` convenience constant** <br>
2605
- Add `LLM::Command = LLM::Repl::Command` as a shorter alias,
2606
- available once `"llm/repl"` is required.
2607
-
2608
- #### Misc
2609
-
2610
- * **repl: implement cancellation with the 'Esc' key** <br>
2611
- The curses-based REPL now supports cancelling an active model
2612
- request by pressing the 'Esc' key. When a request is in progress,
2613
- the status line shows `thinking • Esc to cancel`, and pressing
2614
- Esc calls `LLM::Agent#cancel!` to interrupt the request. The
2615
- transcript displays `request cancelled!` to confirm the
2616
- cancellation.
2617
-
2618
- ### Change
2619
-
2620
- #### Misc
2621
-
2622
- * **provider: increase default timeout to 900s** <br>
2623
- The default HTTP timeout for all providers has been increased from
2624
- 180 to 900 seconds (15 minutes) to better accommodate long-running
2625
- requests such as reasoning models and large structured outputs.
2626
-
2627
- * **agent: `deserialize` and `restore` return `self`** <br>
2628
- `LLM::Agent#deserialize` and `LLM::Agent#restore` now return `self`
2629
- (the agent instance) instead of forwarding the context's return
2630
- value, enabling method chaining after restoring agent state.
2631
-
2632
- * **context: discard all messages from a cancelled turn** <br>
2633
- When `LLM::Context#cancel!` is called, all messages added during
2634
- that turn are now discarded via `Buffer#slice!`, preventing edge
2635
- cases where dangling tool calls between turns caused repeated
2636
- cancellation loops. The `#repair!` method now handles tool call
2637
- cancellations on the next turn instead of mutating the conversation
2638
- buffer directly at cancellation time.
2639
-
2640
- * **stream: drop the `error` argument from `on_tool_call`** <br>
2641
- The `on_tool_call` callback no longer accepts an `error` argument.
2642
- Previously, stream parsers passed both a tool and an optional error,
2643
- requiring boilerplate like `if error; queue << error; end` in every
2644
- callback. Error handling is now pushed directly onto the stream queue
2645
- inside each provider's stream parser, so `on_tool_call(tool)` is the
2646
- only signature. The REPL stream and base `LLM::Stream` class have
2647
- been updated accordingly.
2648
-
2649
- #### REPL internals
2650
-
2651
- * **repl: pass the repl instance to command constructors** <br>
2652
- `LLM::Repl::Command` subclasses now receive the active repl
2653
- instance via `initialize(repl)`, enabling commands to write
2654
- to the transcript and interact with the repl window.
2655
-
2656
- * **repl: `Command#write` prefixes messages with the command name** <br>
2657
- The `#write` method now prefixes output with `command(<name>): `
2658
- so command messages are consistent with the `user:` and `agent:`
2659
- labels in the transcript. The prefix can be customised with the
2660
- `who:` keyword argument, or set to `who: nil` to disable it
2661
- entirely.
2662
-
2663
- ### Fix
2664
-
2665
- #### Misc
2666
-
2667
- * **function: avoid silent skip of tools not found in available tools** <br>
2668
- When a model calls a tool that is not present in the available tools
2669
- list, instead of silently skipping the tool call (via `next`), a
2670
- `LLM::NoSuchToolError` is now raised so the model receives feedback
2671
- about the invalid tool call and can correct course.
2672
- <br><br>
2673
- An additional fallback to the global `LLM::Function` registry is
2674
- tried before raising, so globally registered tools are still
2675
- resolved even when not in the per-request tool set.
2676
-
2677
- #### REPL bugs
2678
-
2679
- * **repl: don't persist parameter state between turns** <br>
2680
- Parameter state (such as `Parameter#value`) was leaking across
2681
- turns because the same parameter objects were being mutated
2682
- in place. A duplicate set of parameters is now created for each
2683
- turn, keeping the original parameter definitions intact and
2684
- preventing stale state from carrying over.
2685
-
2686
- * **repl: reply with error when given an invalid tool** <br>
2687
- When the model tries to call a tool that does not exist, the
2688
- error is now pushed onto the stream queue so the model can
2689
- see the error and correct course, instead of silently dropping
2690
- the invalid tool call and leaving it to `Context#repair` to
2691
- remove it from history.
2692
-
2693
- * **repl: fix save of initial runtime state** <br>
2694
- Fix a bug in `LLM::Repl#configure` where a non-existent path
2695
- argument was treated as no path at all, preventing the initial
2696
- runtime state from being saved after the first turn. The correct
2697
- behavior is to create the file so it can be written to after
2698
- the first turn completes.
2699
-
2700
- ### Refresh
2701
-
2702
- * **Refresh model metadata across all providers** <br>
2703
- Update model listings, pricing, capabilities, reasoning options,
2704
- modality support, context limits, and release dates across all
2705
- provider registries (Anthropic, AWS Bedrock, DeepInfra, DeepSeek,
2706
- Google, Mistral, OpenAI, xAI, and ZAI). Notable changes include
2707
- Anthropic claude-opus-4-8 and claude-sonnet-4-6 additions with
2708
- effort-based reasoning, OpenAI gpt-5.6-sol/terra/luna and
2709
- gpt-5-codex additions, Google gemini-3-pro-preview and
2710
- gemini-3-flash-preview additions, DeepInfra Qwen3.5 and DeepSeek
2711
- V4 model additions, and updated xAI Grok model entries.
2712
-
2713
- ## v12.4.0
2714
-
2715
- Changes since `v12.3.1`.
2716
-
2717
- This release brings major improvements to the curses-based REPL
2718
- (`LLM::Agent#repl`). The REPL now supports saving and restoring runtime
2719
- state across sessions, automatic paste-mode detection for fast bulk input,
2720
- a command system foundation with the `/exit` command, and several new
2721
- keybindings (Ctrl+F, Ctrl+K, Ctrl+Y). Tool calls are rendered with a
2722
- compact function-call syntax in the status bar.
2723
-
2724
- Two new built-in tools: `LLM::Tool::Ls` and `LLM::Tool::Which` are
2725
- available as opt-in additions for file listing and executable lookup.
2726
-
2727
- Model metadata has been refreshed across providers, the REPL loop
2728
- internals have been refactored to use `catch`/`throw` for cleaner command
2729
- routing, and several bugs have been fixed including a tracer restoration
2730
- issue in the agent ensure clause and a missing cursor in the REPL input
2731
- area.
2732
-
2733
- ### Add
2734
-
2735
- * **repl: allow runtime state to be saved and restored** <br>
2736
- `LLM::Agent#repl` now accepts a `path:` option that serializes
2737
- runtime state to the filesystem. When the path already exists,
2738
- runtime state is restored when the read-eval-print loop starts.
2739
- Otherwise the path is written after the first turn, making it
2740
- possible to resume a session across process restarts.
2741
-
2742
- * **repl: scroll to the bottom on submit** <br>
2743
- The curses-based REPL now scrolls the transcript to the bottom when
2744
- the user submits their input, so the latest response is visible
2745
- without needing to scroll down manually.
2746
-
2747
- * **repl: add Ctrl+F to move the cursor forward** <br>
2748
- The curses-based REPL input now supports Ctrl+F to move the cursor
2749
- forward by one column, matching common terminal editing conventions
2750
- found in shells like `/bin/sh`.
2751
-
2752
- * **repl: add Ctrl+K to erase from cursor to end of line** <br>
2753
- The curses-based REPL input now supports Ctrl+K to erase all text
2754
- from the cursor position to the end of the input buffer, matching
2755
- common terminal editing conventions found in shells like `/bin/sh`.
2756
-
2757
- * **repl: add Ctrl+Y to paste previously killed text** <br>
2758
- The curses-based REPL input now supports Ctrl+Y to insert the most
2759
- recently killed text (via Ctrl+K) at the current cursor position,
2760
- matching the yank/paste convention found in shells like `/bin/sh`.
2761
- The killed text is stored in an internal copy buffer so it can be
2762
- pasted multiple times or at different cursor positions.
2763
-
2764
- * **repl: add command system foundation** <br>
2765
- Add `LLM::Repl::Command` as a new base class for REPL commands,
2766
- along with the first built-in command `LLM::Repl::Command::Exit`
2767
- which exits the read-eval-print loop via `throw(:exit)`.
2768
- Commands are identified by a name and can be looked up through
2769
- `Command.find_by`. This is the foundation for the `/` command
2770
- syntax used in the REPL input line.
2771
-
2772
- * **repl: connect the command system to user input** <br>
2773
- The curses-based REPL now routes user input through the command
2774
- system. Any input string beginning with `"/"` is matched against
2775
- the command registry via `Command.find_by`, and the corresponding
2776
- command is executed instead of being forwarded to the model.
2777
- This makes built-in commands like `/exit` functional from the
2778
- input line. Command arguments are not yet supported.
2779
-
2780
- * **repl: add `LLM::Repl::Command.registry`** <br>
2781
- Add `LLM::Repl::Command.registry` for auto-registering command
2782
- subclasses. The `inherited` hook captures each new subclass and
2783
- stores it in the registry, making it possible to enumerate all
2784
- available commands at runtime. Built-in commands like Exit are
2785
- automatically registered when the command file is loaded.
2786
-
2787
- * **repl: detect and handle paste mode in the input line** <br>
2788
- The curses-based REPL input now detects paste operations by tracking
2789
- the rate at which characters arrive. A paste rate of ≤50ms is
2790
- assumed to be a burst of characters that could only be explained by
2791
- a paste. No human types that fast. Multiline pastes are supported
2792
- through internal refactoring of the input handling logic.
2793
-
2794
- * **repl: optimize paste mode rendering** <br>
2795
- Track the paste state with an internal `@paste` variable and switch
2796
- to a faster input path during paste operations. While in paste mode,
2797
- the input buffer is drained via `Curses.getch`, bypassing the more
2798
- expensive char-by-char render path used for ordinary interactive
2799
- input. This makes pasting large amounts of text noticeably faster.
2800
-
2801
- * **Add `LLM::Tool::Ls`** <br>
2802
- Add a built-in tool for listing files and directories, with optional
2803
- glob pattern filtering to narrow results. <br>
2804
- It must be required explicitly with `require "llm/tools/ls"`.
2805
-
2806
- * **Add `LLM::Tool::Which`** <br>
2807
- Add a built-in tool for locating an executable on the system PATH.
2808
- This lets an agent check whether a command is available before
2809
- attempting to run it, avoiding failed subprocess calls. <br>
2810
- It must be required explicitly with `require "llm/tools/which"`.
2811
-
2812
- * **repl: render tool calls in a function-call syntax** <br>
2813
- The curses-based REPL status bar now renders tool calls with a
2814
- compact function-call syntax: `tool(key: value)` instead of
2815
- `tool: name`. Strings are quoted and truncated, arrays show their
2816
- first two elements, and hashes collapse to `{…}`, making it easier
2817
- to see what arguments the model is passing. The `tool done` status
2818
- message has been removed since the tool call itself conveys
2819
- completion information.
2820
-
2821
- ### Change
2822
-
2823
- * **Refresh model metadata** <br>
2824
- Update model listings, pricing, and capabilities across providers.
2825
- Fix GPT-5.6 model family names in the OpenAI registry (`gpt` to
2826
- `gpt-sol`, `gpt-nano` to `gpt-luna`, `gpt-mini` to `gpt-terra`).
2827
- Add OpenAI models (`gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`)
2828
- to the AWS Bedrock registry. Update DeepInfra pricing for
2829
- `DeepSeek-V3` and `Sky-T1-32B-Preview`. Fix Google model knowledge
2830
- cutoff dates.
2831
-
2832
- * **repl: control the loop with catch & throw** <br>
2833
- The curses-based REPL input loop now uses `catch(:exit)` and
2834
- `throw(:exit)` instead of returning the `:exit` symbol and
2835
- breaking out of the loop. This enables the `/command` syntax
2836
- without requiring an `:exit` return value to be propagated
2837
- through a potentially deeply nested call path.
2838
-
2839
- * **repl: replace Ctrl+D with shell-like delete-at-cursor** <br>
2840
- The curses-based REPL input now treats Ctrl+D as a delete action
2841
- that removes the character at the current cursor position, matching
2842
- the shell/Emacs convention where Ctrl+D deletes the character under
2843
- the cursor instead of signalling end-of-file. The previous Ctrl+D
2844
- behaviour (exiting the REPL) is superseded by the `/exit` command.
2845
-
2846
- * **repl: switch to 'Thinking' mode after tool return** <br>
2847
- The curses-based REPL status line now switches to "Thinking" mode
2848
- after a tool returns, so the user can see the agent is processing
2849
- the tool result rather than showing a stale tool-call status.
2850
-
2851
- ### Fix
2852
-
2853
- * **agent: fix a subtle typo in the ensure clause** <br>
2854
- Fix a subtle typo in `LLM::Agent` where the deprecated `trace` local
2855
- variable was given preference over `tracer` (the preferred local name)
2856
- in an `ensure` clause. The `trace` local was supported for backward
2857
- compatibility but the ensure clause still referenced `trace` instead of
2858
- `tracer`, which meant the previous tracer was never restored when the
2859
- REPL session ended.
2860
-
2861
- * **repl: restore the cursor in the input area** <br>
2862
- Remove the `Curses.curs_set(0)` call from the REPL redraw method,
2863
- which was inadvertently hiding the cursor and making it impossible
2864
- to see the current position in the input area. The input field is
2865
- now always drawn at its full height so the cursor position is
2866
- correctly maintained after each redraw.
2867
-
2868
- ## v12.3.1
2869
-
2870
- Changes since `v12.3.0`.
2871
-
2872
- This release fixes a flickering issue in the curses-based REPL redraw.
2873
- The full-screen clear that caused visible flickering has been replaced
2874
- with a targeted cursor-hide approach, and stale rows from a larger
2875
- transcript are now explicitly cleared to prevent ghost text from
2876
- lingering when the transcript shrinks.
2877
-
2878
- ### Fix
2879
-
2880
- * **repl: fix redraw flicker** <br>
2881
- Replace `Curses.clear` with `Curses.curs_set(0)` in the REPL redraw
2882
- method to avoid a full screen clear that caused visible flickering
2883
- during redraws. The drawing order is also adjusted so the status line
2884
- is drawn before the divider, and stale rows left over from a larger
2885
- transcript are now explicitly cleared to prevent ghost text from
2886
- lingering when the transcript shrinks.
2887
-
2888
- ## v12.3.0
2889
-
2890
- Changes since `v12.2.0`.
2891
-
2892
- This release brings major improvements to the curses-based REPL
2893
- (`LLM::Agent#repl`). The status line now shows a context-usage bar and
2894
- running cost counter, the input field expands to three rows with
2895
- full cursor navigation, model responses are rendered as styled markdown,
2896
- and the UI stays responsive while the agent is working by running
2897
- requests in a separate thread. A new `LLM::Stream::IO` and
2898
- `LLM::Stream::Disabled` provide a uniform stream representation across
2899
- all stream types.
2900
-
2901
- Mistral OCR support is added for extracting text from images and
2902
- documents via the `/v1/ocr` endpoint. The `skills:` and `tools:` options
2903
- on `LLM::Agent#repl` let you attach additional tools or skill directories
2904
- for the duration of a session. `LLM::Object#merge!` rounds out the
2905
- in-place merge API, and a new `LLM.logger` convenience method creates
2906
- tracer logger instances with less verbosity.
2907
-
2908
- ### Add
2909
-
2910
- * **Add `LLM.logger` convenience method** <br>
2911
- Add `LLM.logger(llm, ...)` as a shorter, less verbose way to create an
2912
- `LLM::Tracer::Logger` instance. Takes a provider and optional keyword
2913
- arguments forwarded to the logger constructor.
2914
-
2915
- * **Add `skills:` option to `LLM::Agent#repl`** <br>
2916
- `LLM::Agent#repl` now accepts a `skills:` keyword argument that attaches
2917
- one or more skill directories (containing `SKILL.md`) for the duration of
2918
- the repl session. Skills are loaded and converted to tools, combining with
2919
- any tools already configured on the agent, and are discarded when the
2920
- session ends.
2921
-
2922
- * **Add `LLM::Provider#ocr` base method** <br>
2923
- Add a base `ocr(...)` method to `LLM::Provider` that raises `NotImplementedError`
2924
- by default, establishing a common interface for providers that support OCR
2925
- (Optical Character Recognition) on images and documents.
2926
-
2927
- * **Add Mistral OCR endpoint support** <br>
2928
- The Mistral provider now supports OCR via its `/v1/ocr` endpoint. Call
2929
- `mistral.ocr(image_url: ...)` for images or `mistral.ocr(document_url: ...)`
2930
- for documents (e.g., PDFs). Returns an `LLM::Response` with extracted pages,
2931
- markdown content, and structured block data.
2932
-
2933
- * **Add `LLM::Object#merge!`** <br>
2934
- Add `LLM::Object#merge!` for in-place merging of hash data into an
2935
- `LLM::Object` instance, complementing the existing `#merge` method.
2936
-
2937
- * **Add `LLM::Stream::IO` and `LLM::Stream::Disabled`** <br>
2938
- `LLM::Stream::IO` wraps IO-like objects as stream targets, forwarding
2939
- streamed content via `#<<`. `LLM::Stream::Disabled` represents an explicitly
2940
- disabled stream with no-op callbacks.
2941
-
2942
- This is part of an internal refactoring that lets all stream values: IO
2943
- objects, `true`, `false`, `nil`, and `LLM::Stream` instances themselves
2944
- be represented by the same `LLM::Stream` interface via the new
2945
- `LLM::Stream.try` factory method.
2946
-
2947
- Before this change the codebase had to perform ad-hoc type checks
2948
- (e.g. `if LLM::Stream === stream`) scattered throughout. After this
2949
- change all stream handling goes through a single uniform path, and
2950
- providers check `#enabled?` to decide whether to request streaming
2951
- from the API.
2952
-
2953
- ### Change
2954
-
2955
- * **repl: rename `trace:` to `tracer:`** <br>
2956
- The `trace:` keyword argument in `LLM::Agent#repl` has been renamed to
2957
- `tracer:` for consistency with the rest of the codebase. The old `trace:`
2958
- name still works with a deprecation warning.
2959
-
2960
- * **repl: add context-usage bar and cost counter to the status line** <br>
2961
- The curses-based REPL status line now shows a small progress bar that
2962
- indicates how much of the model's context window remains as a percentage,
2963
- alongside a running cost estimate rendered on the right side of the status
2964
- line. The input line has been updated to show the provider name as a prefix.
2965
- Estimates are best-effort and depend on registry pricing data (see `data/`).
2966
-
2967
- * **repl: keep the UI responsive while a request is in progress** <br>
2968
- The curses-based REPL now spawns the agent request in a separate thread
2969
- and communicates streamed output through a queue, so the curses UI stays
2970
- responsive during model processing. Users can continue to scroll through
2971
- the transcript while the agent is working.
2972
-
2973
- * **repl: style transcript rows as structured data with bold labels** <br>
2974
- The curses-based REPL transcript now stores rows as structured data with
2975
- style metadata instead of plain strings, enabling bold rendering of the
2976
- `user:` and `agent:` labels for improved readability during interactive
2977
- sessions.
2978
-
2979
- * **repl: render a small subset of markdown** <br>
2980
- The curses-based REPL now renders model responses as styled markdown.
2981
- Headers and strong text render in bold, emphasis renders in underline,
2982
- and code spans and blocks are highlighted with inverted colors. Streaming
2983
- content is buffered and re-rendered on each tick so the transcript reads
2984
- cleanly as the agent responds. Requires the optional `kramdown` gem.
2985
-
2986
- * **repl: add cursor LEFT/RIGHT movement to the input line** <br>
2987
- The curses-based REPL input now supports cursor movement with the left
2988
- and right arrow keys, enabling in-place text editing before submitting
2989
- a prompt. The cursor position is tracked visually and moves backwards
2990
- on left-arrow and forwards on right-arrow.
2991
-
2992
- * **repl: add Ctrl+A and Ctrl+E keybindings to the input line** <br>
2993
- The curses-based REPL input now supports Ctrl+A to jump the cursor to
2994
- the start of the input line and Ctrl+E to jump it to the end, matching
2995
- common terminal editing conventions.
2996
-
2997
- * **repl: add `tools:` option to `LLM::Agent#repl`** <br>
2998
- `LLM::Agent#repl` now accepts a `tools:` keyword argument that attaches
2999
- additional tool classes or instances for the duration of the repl session.
3000
- These tools are combined with any tools already configured on the agent,
3001
- and are discarded when the session ends.
3002
-
3003
- * **repl: add repl support to ActiveRecord and Sequel agent models** <br>
3004
- `acts_as_agent` (ActiveRecord) and `plugin :agent` (Sequel) models now
3005
- expose a `repl` method that delegates to the underlying agent's
3006
- read-eval-print loop. This allows interactive debugging and inspection
3007
- of persisted agent state at runtime. Note that changes made during a
3008
- repl session do not persist back to the database.
3009
-
3010
- * **repl: add extra padding between markdown nodes** <br>
3011
- The curses-based REPL markdown renderer now adds extra vertical spacing
3012
- between certain markdown elements: paragraphs, headers, and codeblocks
3013
- for improved readability of model responses.
3014
-
3015
- * **repl: add a visual divider between transcript and the rows below it** <br>
3016
- The curses-based REPL now draws a horizontal divider line (using a unicode
3017
- `─` character) to separate the transcript area from the status and input
3018
- rows below it. A single empty buffer row is also added between the
3019
- transcript and the divider, preventing transcript text from running too
3020
- close to the status and input rows.
3021
-
3022
- * **repl: expand input field to 3 rows** <br>
3023
- The curses-based REPL input field now spans three rows instead of one,
3024
- wrapping text that exceeds the terminal width onto subsequent lines. A
3025
- scrollable viewport follows the cursor so the active line stays visible,
3026
- and common navigation commands (Ctrl+A, Ctrl+E, cursor keys) work across
3027
- all three rows of the expanded input area.
3028
-
3029
- * **Refresh OpenAI model metadata** <br>
3030
- Add new OpenAI models to the registry, including `gpt-5.6`,
3031
- `gpt-5.6-luna`, `gpt-5.6-terra`, `gpt-5.6-sol`, and
3032
- `gpt-realtime-2.1`, with associated pricing, capabilities, and
3033
- limits.
3034
-
3035
- ### Fix
3036
-
3037
- * **Fix Ollama non-streaming response handling** <br>
3038
- Fix the Ollama provider to properly handle the non-streaming path. When
3039
- the provider returns a raw NDJSON response body (instead of streaming),
3040
- the response is now parsed and merged into a single `LLM::Object` before
3041
- being returned to the caller. Previously the non-streaming path was
3042
- effectively broken and would fail to produce a valid completion response.
3043
-
3044
- * **repl: handle a negative context window allowance in the usage bar** <br>
3045
- Fix a crash in the curses-based REPL context-usage bar when the context
3046
- window allowance is exceeded (used > total). The negative width value that
3047
- resulted from this edge case could cause curses errors; it now gracefully
3048
- defaults to `0%` and zero bar width.
3049
-
3050
- * **Fix YARD documentation across provider and tool files** <br>
3051
- Fix unnamed, misnamed, and missing `@param` tags in `LLM::Repl::Status`,
3052
- `LLM::Tool::Git`, `LLM::Tool::Pwd`, `LLM::Tool::Rg`, and
3053
- `LLM::Tool::SwapText`.
3054
-
3055
- ## v12.2.0
3056
-
3057
- Changes since `v12.1.0`.
3058
-
3059
- This release adds Mistral as a new provider with chat completions, streaming, tool calls,
3060
- structured outputs, file/image attachments, and embeddings support. It introduces
3061
- the `trace:` option to `LLM::Agent#repl` for keeping the tracer active during
3062
- interactive sessions.
3063
-
3064
- Several fixes land for the Google provider (generationConfig parameter leakage),
3065
- `LLM::Context#tracer=` (always assigning nil), `LLM::Provider#with_tracer(nil)`
3066
- (nil fallback), and `LLM::Context#repair!` (dropping Struct returns).
3067
-
3068
- The default HTTP timeout has been increased from 60s to 180s to better accommodate
3069
- reasoning models and large structured outputs, and the Anthropic default model has
3070
- been updated to `claude-opus-4-8`. Model metadata has been refreshed across
3071
- Anthropic, AWS Bedrock, DeepInfra, Google, and xAI, with Mistral model data added
3072
- to the registry.
3073
-
3074
- ### Add
3075
-
3076
- * **Add `trace:` option to `LLM::Agent#repl`** <br>
3077
- `LLM::Agent#repl` now accepts a `trace:` keyword argument. By default
3078
- the tracer is disabled for the duration of the repl session to prevent
3079
- curses UI interference from output written to `$stdout` or `$stderr`.
3080
- Set `trace: true` to keep the tracer active during the session, which
3081
- is useful when the tracer writes to a file rather than the terminal.
3082
-
3083
- * **Add a new provider: LLM::Mistral** <br>
3084
- [Mistral](https://mistral.ai) is now supported through its
3085
- OpenAI-compatible API. The provider supports chat completions,
3086
- streaming, tool calls, structured output (schema), file/image
3087
- attachments, and embeddings. Use `LLM.mistral(...)` to create a
3088
- provider instance.
3089
-
3090
- * **Add `LLM.mistral(...)` convenience method** <br>
3091
- A new top-level accessor (`LLM.mistral`) returns an `LLM::Mistral`
3092
- provider instance, matching the pattern used by other providers.
3093
-
3094
- ### Fix
3095
-
3096
- * **Fix Google `generationConfig` parameter leakage** <br>
3097
- Fix a bug in the Google provider where non-generation parameters
3098
- (`role`, `model`, `messages`, `stream`) were leaking into the
3099
- `generationConfig` object alongside legitimate generation config
3100
- parameters such as `temperature`. Non-config parameters are now
3101
- filtered out before constructing `generationConfig`.
3102
-
3103
- * **Fix `LLM::Context#tracer=` always assigning `nil`** <br>
3104
- The `LLM::Context#tracer=` setter had a bug where it always assigned
3105
- `nil` regardless of the tracer value passed. It now correctly assigns
3106
- the given tracer or falls back to `LLM::Tracer::Null`.
3107
-
3108
- * **Fix `LLM::Provider#with_tracer(nil)` fallback** <br>
3109
- `LLM::Provider#with_tracer(nil)` now falls back to
3110
- `LLM::Tracer::Null` instead of setting a `nil` tracer directly.
3111
-
3112
- * **Fix `LLM::Context#repair!` dropping `Struct` returns** <br>
3113
- `LLM::Context#repair!` used `[*prompt]` to wrap the prompt before
3114
- grepping for return objects. Since `LLM::Function::Return` is a
3115
- `Struct`, the splat operator expanded it into its member values
3116
- instead of wrapping it, causing the grep to silently drop returns.
3117
- The fix wraps both sources in an array before flattening.
3118
-
3119
- ### Change
3120
-
3121
- * **Increase default provider timeout from 60s to 180s** <br>
3122
- The default HTTP timeout for all providers has been increased from
3123
- 60 to 180 seconds to better accommodate long-running requests such
3124
- as reasoning models and large structured outputs.
3125
-
3126
- * **Change Anthropic default model to `claude-opus-4-8`** <br>
3127
- The default Anthropic chat model has been updated from
3128
- `claude-sonnet-4-20250514` to `claude-opus-4-8`, reflecting the
3129
- latest model release from Anthropic.
3130
-
3131
- * **Refresh model metadata** <br>
3132
- Update model listings, pricing, and capabilities for Anthropic,
3133
- AWS Bedrock, DeepInfra, Google, and xAI. Add Mistral model data
3134
- to the registry.
3135
-
3136
- ## v12.1.0
3137
-
3138
- Changes since `v12.0.0`.
3139
-
3140
- This release adds `LLM::Agent#repl` with a curses-based
3141
- interactive read-eval-print loop. It requires the optional
3142
- dependency `curses` and it is probably the most notable
3143
- feature in this release.
3144
-
3145
- Multiple _opt-in_ tools have been added to the `llm/tools/*.rb`
3146
- directory. They serve as examples and as general-purpose tools
3147
- that happen to power the repository's agents.
3148
-
3149
- Other changes include small-ish bug fixes. <br>
3150
- As always, see the changelog details for a thorough overview.
3151
-
3152
- ### Add
3153
-
3154
- * **Add `LLM::Agent#repl`** <br>
3155
- Add a curses-based read-eval-print loop for `LLM::Agent` that lets
3156
- developers interact with an agent after it has been set up or has
3157
- performed a task. It is similar to `binding.irb`: once you exit,
3158
- you can continue with the rest of your program. It requires the
3159
- `curses` gem.
3160
-
3161
- * **Add `#tracer=` setter on Provider, Context and Agent** <br>
3162
- `LLM::Provider`, `LLM::Context` and `LLM::Agent` can now configure
3163
- the tracer after initialization via the `#tracer=` setter. It accepts
3164
- a subclass of `LLM::Tracer` or `nil` to disable the tracer.
3165
-
3166
- * **Add `LLM::Tool::Shell`** <br>
3167
- Add a built-in shell tool that can run a command with arguments. <br>
3168
- It must be required explicitly with `require "llm/tools/shell"` and
3169
- requires the `test-cmd.rb` gem.
3170
-
3171
- * **Add `LLM::Tool::ReadFile`** <br>
3172
- Add a built-in tool for reading the contents of a file, with optional
3173
- `start` and `stop` line offsets. <br>
3174
- It must be required explicitly with `require "llm/tools/read_file"`.
3175
-
3176
- * **Add `LLM::Tool::Chdir`** <br>
3177
- Add a built-in tool for changing the current working directory. <br>
3178
- It must be required explicitly with `require "llm/tools/chdir"`.
3179
-
3180
- * **Add `LLM::Tool::Git`** <br>
3181
- Add a built-in tool that can perform git actions (`log`, `diff`,
3182
- `show`, `commit`, `checkout`, `branch`). <br>
3183
- It must be required explicitly with `require "llm/tools/git"` and requires the `test-cmd.rb` gem.
3184
-
3185
- * **Add `LLM::Tool::Rg`** <br>
3186
- Add a built-in tool that wraps the `rg` (ripgrep) command for
3187
- recursively searching the current directory for patterns. <br>
3188
- It must be required explicitly with `require "llm/tools/rg"` and requires the `test-cmd.rb` gem.
3189
-
3190
- * **Add `LLM::Tool::SwapText`** <br>
3191
- Add a built-in tool that can replace an exact snippet of text in a
3192
- file with a new piece of text. <br>
3193
- It must be required explicitly with `require "llm/tools/swap_text"`.
3194
-
3195
- * **Add `LLM::Tool::Pwd`** <br>
3196
- Add a built-in tool that returns the current working directory. <br>
3197
- It must be required explicitly with `require "llm/tools/pwd"`.
3198
-
3199
- * **Add `LLM::Tool::WriteFile`** <br>
3200
- Add a built-in tool that can write a given string to a given file
3201
- path. <br>
3202
- It must be required explicitly with `require "llm/tools/write_file"`.
3203
-
3204
- * **Add `LLM::Tool::Mkdir`** <br>
3205
- Add a built-in tool that can create a tree of new directories. <br>
3206
- It must be required explicitly with `require "llm/tools/mkdir"` and
3207
- requires the `test-cmd.rb` gem.
3208
-
3209
- ### Change
3210
-
3211
- * **Change LlamaCpp default port (8080 => 8013)** <br>
3212
- The default port for the LlamaCpp provider has changed from `8080` to
3213
- `8013` since llamacpp itself defaults to that port.
3214
-
3215
- * **Change LlamaCpp default model to `nil`** <br>
3216
- The default model for LlamaCpp is now `nil`, letting whatever model
3217
- is served by the llamacpp server act as the default. Previously it
3218
- defaulted to `qwen3`.
3219
-
3220
- ### Fix
3221
-
3222
- * **Fix `LLM::Agent.tools` Symbol resolution** <br>
3223
- When an agent defined tools via `tools :method_name`, the resolved
3224
- symbol was incorrectly forwarded as `[:method_name]` (an array) to
3225
- `LLM::Context`. This fix copies the same pattern used by other
3226
- attribute resolvers (e.g., `skills`) so a single Symbol is resolved
3227
- through the agent instance correctly.
3228
-
3229
- * **Encode strings as UTF-8 in the JSON adapter** <br>
3230
- The `json` gem will reject BINARY-encoded strings from version 3 and
3231
- beyond. The `LLM::JSONAdapter.dump` method now walks serialized data
3232
- and encodes every string into UTF-8, using `String#scrub` to replace
3233
- bytes that are not valid UTF-8.