llm.rb 15.1.0 → 15.2.1

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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +336 -11
  3. data/README.md +90 -52
  4. data/bin/llm.rb +12 -4
  5. data/data/alibaba.json +912 -823
  6. data/data/anthropic.json +234 -187
  7. data/data/bedrock.json +3611 -2217
  8. data/data/deepinfra.json +1164 -989
  9. data/data/deepseek.json +65 -81
  10. data/data/google.json +668 -666
  11. data/data/mistral.json +501 -460
  12. data/data/moonshot.json +43 -248
  13. data/data/openai.json +1008 -914
  14. data/data/openrouter.json +7798 -7661
  15. data/data/xai.json +194 -194
  16. data/data/zai.json +242 -149
  17. data/docs/deepdive/advanced/compaction.md +5 -5
  18. data/docs/deepdive/advanced/guard.md +2 -2
  19. data/docs/deepdive/features/builtin_tools.md +93 -22
  20. data/docs/deepdive/features/{repl.md → console.md} +28 -28
  21. data/docs/deepdive/features/database.md +3 -3
  22. data/docs/deepdive/fundamentals/agents.md +2 -2
  23. data/docs/deepdive/fundamentals/providers.md +91 -6
  24. data/docs/deepdive/fundamentals/skills.md +14 -6
  25. data/docs/deepdive/fundamentals/tools.md +63 -31
  26. data/docs/deepdive/reference/cost.md +2 -2
  27. data/docs/deepdive/reference/model_registry.md +2 -2
  28. data/docs/deepdive/reference/tracer.md +15 -13
  29. data/docs/deepdive.md +2 -2
  30. data/lib/llm/active_record/acts_as_agent.rb +10 -6
  31. data/lib/llm/agent.rb +64 -30
  32. data/lib/llm/{repl → console}/bar.rb +3 -3
  33. data/lib/llm/{repl → console}/buffer.rb +4 -4
  34. data/lib/llm/{repl → console}/color.rb +2 -2
  35. data/lib/llm/{repl → console}/command.rb +12 -12
  36. data/lib/llm/{repl → console}/commands/exit.rb +4 -4
  37. data/lib/llm/{repl → console}/commands/help.rb +1 -1
  38. data/lib/llm/{repl/commands/compact.rb → console/commands/keep.rb} +11 -9
  39. data/lib/llm/{repl → console}/commands/model.rb +2 -2
  40. data/lib/llm/{repl → console}/input/cache.rb +2 -2
  41. data/lib/llm/{repl → console}/input/char.rb +2 -2
  42. data/lib/llm/{repl → console}/input/row.rb +1 -1
  43. data/lib/llm/{repl → console}/input.rb +13 -6
  44. data/lib/llm/console/markdown/parser.rb +78 -0
  45. data/lib/llm/{repl → console}/markdown/table.rb +3 -3
  46. data/lib/llm/{repl → console}/markdown.rb +13 -30
  47. data/lib/llm/{repl → console}/node.rb +3 -3
  48. data/lib/llm/{repl → console}/status.rb +11 -11
  49. data/lib/llm/{repl → console}/stream.rb +9 -9
  50. data/lib/llm/{repl → console}/walker.rb +1 -1
  51. data/lib/llm/{repl → console}/window.rb +10 -10
  52. data/lib/llm/{repl.rb → console.rb} +38 -19
  53. data/lib/llm/context/deserializer.rb +2 -1
  54. data/lib/llm/context.rb +1 -0
  55. data/lib/llm/function/async/reactor.rb +20 -1
  56. data/lib/llm/function.rb +8 -9
  57. data/lib/llm/json_adapter.rb +40 -28
  58. data/lib/llm/message.rb +7 -0
  59. data/lib/llm/provider.rb +2 -2
  60. data/lib/llm/providers/alibaba.rb +1 -1
  61. data/lib/llm/providers/deepseek.rb +1 -1
  62. data/lib/llm/providers/openai.rb +1 -0
  63. data/lib/llm/schema/leaf.rb +34 -2
  64. data/lib/llm/schema.rb +4 -2
  65. data/lib/llm/sequel/agent.rb +10 -6
  66. data/lib/llm/tool/param.rb +5 -1
  67. data/lib/llm/tool.rb +5 -0
  68. data/lib/llm/tools/bundle.rb +53 -0
  69. data/lib/llm/tools/edit-file.rb +7 -2
  70. data/lib/llm/tools/exec.rb +78 -0
  71. data/lib/llm/tools/git.rb +27 -26
  72. data/lib/llm/tools/mkdir.rb +12 -19
  73. data/lib/llm/tools/read_file.rb +69 -9
  74. data/lib/llm/tools/rg.rb +20 -24
  75. data/lib/llm/tools/ruby.rb +17 -25
  76. data/lib/llm/tools/utils.rb +74 -1
  77. data/lib/llm/tools/write_file.rb +4 -1
  78. data/lib/llm/tracer/logger.rb +2 -2
  79. data/lib/llm/tracer/pretty_logger.rb +4 -4
  80. data/lib/llm/tracer/telemetry.rb +2 -2
  81. data/lib/llm/tracer.rb +33 -0
  82. data/lib/llm/transport/utils.rb +1 -1
  83. data/lib/llm/version.rb +1 -1
  84. data/lib/llm.rb +4 -13
  85. data/llm.gemspec +7 -8
  86. metadata +64 -37
  87. data/lib/llm/tools/shell.rb +0 -55
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b496d3aec5a309af0ff9eaac32cabb6c7191bf2bac1f3b664c02a56c593402e
4
- data.tar.gz: 0ae83d7794b4dad881de7b9866b3894746efa1853e349fc5707416e367802db5
3
+ metadata.gz: 8437f48c9a995054696648dfbe3386a5f0d8c8a62379765d2eca8694cf3adb3b
4
+ data.tar.gz: d2c36a69342e2fef94472abc25e813a0ee8bbef9aba15e68b275d89d307a6405
5
5
  SHA512:
6
- metadata.gz: 9f890dd0720d0acbc79f9df2196f3595a68cd618fd0d09dc02fcab9a6fe27758ae72efd4b764614d5511bdb7914f586fba7194b3d1a5bec186562b9196753783
7
- data.tar.gz: f25bc47b9bd5dcb0afea4f2ddbcb1ad37f74863cff962de6ceefa6157af073997589fd08fad0b2df11288aa24db41685127542b4d1748a71c4fef607d6b76ba2
6
+ metadata.gz: d1d120fe448ee693ba627cfdfe213cd42030fe8448f72c8cf6ecaddb62d2ac5f7b07ded6191142ec28571b41acbdf8c96b458420889e7fb75990f53f51b3ab43
7
+ data.tar.gz: 816b6feffe0339f52798062de3f36188b3d6574203289f450fd07de2e1e600b29aa956a8ba50947d3ae27540a4132222f3b31bb865784f88e24074388a852058
data/CHANGELOG.md CHANGED
@@ -11,10 +11,335 @@
11
11
  </p>
12
12
 
13
13
  > Changelog <br>
14
- > a [r.uby.dev](https://r.uby.dev) project
14
+ > [r.uby.dev](https://r.uby.dev) project
15
15
 
16
16
  ## What's next
17
17
 
18
+ *No unreleased changes yet. Check back after the next release.*
19
+
20
+ ## v15.2.1
21
+
22
+ Changes since `v15.2.0`.
23
+
24
+ This release makes the agent count its tool budget across the whole turn
25
+ and emit tool returns to the stream once that budget is spent, and lets
26
+ `LLM::Function#cancel` carry extra return fields.
27
+
28
+ ### Agent
29
+
30
+ * **agent: count the tool budget across the whole turn** <br>
31
+ [`LLM::Agent.tool_budget`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#tool_budget-class_method)
32
+ now caps the tool calls a turn runs in total. A batch of calls is
33
+ spent as a batch, and a batch that would take the turn past its
34
+ budget is not run at all; once the budget is spent no tool call runs,
35
+ and the agent keeps sending its in-band advisory until the model
36
+ answers without requesting more tools. Previously the agent ran up to
37
+ the budget, then ran further batches after each advisory, so a turn
38
+ could run more tool calls than its budget allowed.
39
+
40
+ * **agent: emit tool returns when the budget is spent** <br>
41
+ Fix a bug where, once a turn's tool budget was spent, the agent passed
42
+ its in-band returns to the model without emitting them to the stream.
43
+ The stream went silent, so a stream that tracked tool-call state left
44
+ the calls in the `call` state. The returns are now emitted through
45
+ [`LLM::Stream#on_tool_return`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_tool_return-instance_method)
46
+ like any other tool return.
47
+
48
+ ### Function
49
+
50
+ * **function: `LLM::Function#cancel` takes extra return fields** <br>
51
+ [`LLM::Function#cancel`](https://r.uby.dev/api-docs/llm.rb/LLM/Function.html#cancel-instance_method)
52
+ now accepts keywords beyond `reason:` and merges them into the
53
+ [`LLM::Function::Return`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Return.html)
54
+ it builds. `LLM::Function#budget_spent` now builds its return through
55
+ `cancel` instead of hand-rolling an error, so a budget-spent return is
56
+ marked `cancelled: true` and carries `action:` and `advice:` hints. A
57
+ callback that receives returns can now tell a cancellation apart from
58
+ an ordinary error.
59
+
60
+ ## v15.2.0
61
+
62
+ Changes since `v15.1.0`.
63
+
64
+ This release renames the REPL to `LLM::Console` (with `/keep` replacing
65
+ `/compact`) and routes every shell-out tool through a shared, bounded
66
+ `exec` runner. It also adds `LLM::Message#created_at`, the `LLM::Tracer`
67
+ factory methods, a `bundle` tool, per-tool `max_bytes` output limits,
68
+ and a `-v` switch to the CLI, and refreshes the model registry.
69
+
70
+ ### Core
71
+
72
+ * **message: add `LLM::Message#created_at`** <br>
73
+ [`LLM::Message#created_at`](https://r.uby.dev/api-docs/llm.rb/LLM/Message.html#created_at-instance_method)
74
+ returns the time the message was created, defaulting to the moment the
75
+ message is initialized. The timestamp is serialized into
76
+ [`LLM::Context#to_json`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#to_json-instance_method)
77
+ as an ISO-8601 string and restored on deserialization, so it can be
78
+ stored alongside the rest of the conversation.
79
+
80
+ ### Agent
81
+
82
+ * **agent: inherit the ORM model's name** <br>
83
+ An `acts_as_agent` (ActiveRecord) or `plugin :agent` (Sequel) model now
84
+ names its generated
85
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) after
86
+ the model class. Previously the agent was an anonymous subclass, so
87
+ without an explicit name it defaulted to a gibberish `#<Class:0x...>`
88
+ string. The wrapper now initializes the agent's name before `.agent`
89
+ returns, and
90
+ [`LLM::Agent.name`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#name-class_method)
91
+ kebab-cases a `Class` argument, so an `AdminUser` model yields an agent
92
+ named `admin-user`.
93
+
94
+ ### Cli
95
+
96
+ * **cli: add a `-v` switch** <br>
97
+ `bin/llm.rb` gains a `-v` switch that prints the version (`llm.rb v#{LLM::VERSION}`) and exits.
98
+
99
+ ### Console
100
+
101
+ * **console: raise `LLM::Interrupt` on the agent's thread** <br>
102
+ Pressing Esc to cancel now also raises `LLM::Interrupt` on the agent's
103
+ thread. `LLM::Agent#cancel!` alone can be a no-op at some stages of the
104
+ request lifecycle, so the console backs it up by interrupting the thread
105
+ that runs the agent.
106
+
107
+ * **console: add a `/keep` command and retire `/compact`** <br>
108
+ The console now offers `/keep` for freeing space in the context window;
109
+ the `/compact` command is removed. `/keep` takes the same argument, so
110
+ `/keep 20%` keeps 20% of the context window. Closes
111
+ [issue #161](https://github.com/r-uby-dev/llm.rb/issues/161).
112
+
113
+ * **console: keep the UI responsive during long streams** <br>
114
+ A model can emit many chunks in a single turn. The console now draws
115
+ at most four streamed chunks at a time, then checks for input, so the
116
+ UI stays responsive even when a turn produces a large amount of
117
+ output.
118
+
119
+ * **console: persist the conversation when a turn is done** <br>
120
+ The console now saves the agent's state after the turn finishes,
121
+ rather than while the response is still streaming. State is still
122
+ saved every turn, but not until the turn has completed.
123
+
124
+ * **console: render markdown text as typed** <br>
125
+ Fix a bug where [`LLM::Console::Markdown`](https://r.uby.dev/api-docs/llm.rb/LLM/Console/Markdown.html)
126
+ mangled the model's output: HTML could render invisible, and
127
+ sequences like `...` were converted to unicode glyphs. The renderer
128
+ now uses a custom kramdown parser that disables the HTML, smart-quote,
129
+ and typographic-symbol parsers, so tags and punctuation come through
130
+ exactly as written.
131
+
132
+ * **console: fix a crash in the markdown parser** <br>
133
+ Fix a bug where the markdown renderer raised an error on an unclosed
134
+ HTML tag or a partial tag taken out of context, such as `4 < 5`. The
135
+ parser now emits the `<...` run literally when there is no closing
136
+ `>`, so the text renders instead of crashing.
137
+
138
+ * **console: stop rendering bare pipes as tables** <br>
139
+ Fix a bug where the markdown renderer treated a lone `|foo|` in prose
140
+ as a table and mangled its output. A pipe line now parses as a table
141
+ only when a header row is followed by a delimiter row, so bare pipes
142
+ come through literally while real tables still render.
143
+
144
+ * **console: find the worker thread when cancelling** <br>
145
+ Fix a bug where pressing Esc to cancel raised `LLM::Interrupt` on an
146
+ instance variable that does not exist, so the interrupt was a no-op and
147
+ a cancel could leave the turn running. The console now resolves the
148
+ worker thread through its `#thread` reader and interrupts it.
149
+
150
+ * **console: protect the state write from cancellation** <br>
151
+ The console now defers `LLM::Interrupt` while it saves the agent's
152
+ state after a turn, so a cancel that arrives during the write cannot
153
+ interrupt `agent.save` mid-flight and risk a lost or corrupted session
154
+ file.
155
+
156
+ ### Tools
157
+
158
+ * **tools: the command runner is now `exec`** <br>
159
+ The command tool that spawns a process without a shell is now
160
+ [`LLM::Tool::Exec`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Exec.html),
161
+ with the tool name `exec` instead of the previous `shell`. This is an
162
+ internal refactor of the shell-out tools: `git`, `rg`, `mkdir`,
163
+ `ruby`, and `bundle` all route through it and inherit
164
+ its bounded output.
165
+
166
+ * **tools: report when a command cannot be found** <br>
167
+ `LLM::Tool::Exec` now returns `{ok: false, error: "command 'NAME' was
168
+ not found on this system"}` when the requested command is missing,
169
+ instead of a bare `{ok: false}` result that did not tell the model why
170
+ the tool failed.
171
+
172
+ * **tools: drop the `name:` parameter from `LLM::Tool::Exec#call`** <br>
173
+ `LLM::Tool::Exec#call` now takes a single `arguments:` array instead of
174
+ separate `name:` and `arguments:` parameters, with the command name as
175
+ the first element (for example `arguments: ["rg", "-m", "10", "lib"]`).
176
+ The `Git`, `Mkdir`, `Rg`, `Ruby`, and `Bundle` tools build their calls
177
+ the same way. The change was made after models were observed confusing
178
+ the two parameters, so a single list is simpler and more reliable.
179
+
180
+ * **tools: rename `repl` as `console`** <br>
181
+ The interactive loop is renamed to
182
+ [`LLM::Console`](https://r.uby.dev/api-docs/llm.rb/LLM/Console.html),
183
+ which better reflects what it does. `agent.console` is the primary
184
+ entry point, and the require path moves from `llm/repl` to
185
+ `llm/console`. Backwards-compatible aliases remain: `LLM::Repl`,
186
+ `LLM::Agent#repl`, the ORM wrappers' `#repl`, and `LLM::Command =`
187
+ `LLM::Console::Command`.
188
+
189
+ * **tools: `LLM::Tool::Git#call` takes an `arguments:` array** <br>
190
+ [`LLM::Tool::Git#call`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Git.html)
191
+ now takes a single `arguments:` array in place of the previous
192
+ `subcommand:` parameter. The first element must be one of `log`,
193
+ `diff`, `commit`, `checkout`, `branch`, or `show`, validated before the
194
+ command is spawned; the remaining elements are forwarded to git.
195
+
196
+ * **tools: `LLM::Tool::Utils` now owns command spawning** <br>
197
+ The shared [`LLM::Tool::Utils`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Utils.html)
198
+ module now requires the `test-cmd.rb` gem (at `~> 2.7.1`) itself and
199
+ exposes the `spawn` and `wait` helpers, so any tool that includes
200
+ `Utils` gets command spawning without requiring `exec` directly. The
201
+ `Git`, `Mkdir`, `Rg`, `Ruby`, `Exec`, and `Bundle` tools all
202
+ inherit their bounded-output protections from this shared runner.
203
+
204
+ * **tools: route `git`, `rg`, `mkdir`, and `ruby` through `exec`** <br>
205
+ `LLM::Tool::Git`, `LLM::Tool::Rg`, `LLM::Tool::Mkdir`, and
206
+ `LLM::Tool::Ruby` now implement their calls through the `exec` tool,
207
+ completing the refactor so every tool that shells out flows through
208
+ the shared command runner with its bounded output.
209
+
210
+ * **tools: read-file returns structured lines** <br>
211
+ `LLM::Tool::ReadFile#call` now returns its content as structured
212
+ `{lineno:, content:}` lines under a `lines:` key instead of a single
213
+ `content:` string, and adds a `truncated:` flag. A reversed range
214
+ (`start: 20, stop: 2`) is swapped to read lines 2 through 20. The
215
+ truncation marker is kept out of the returned lines, so the model
216
+ does not mistake it for a real file line.
217
+
218
+ * **tools: write-file appends a trailing newline by default** <br>
219
+ `LLM::Tool::WriteFile` now ensures written content ends with a newline,
220
+ adding one when the content does not already end with `\n`. It previously
221
+ wrote the content exactly as given. A new `newline:` parameter (default
222
+ `true`) controls this, so `newline: false` writes the content exactly as
223
+ given.
224
+
225
+ * **tools: fix `edit-file` treating `before` as a regex** <br>
226
+ `LLM::Tool::EditFile` now escapes the `before` snippet with
227
+ `Regexp.escape`, so regex metacharacters are matched literally, and
228
+ switches to the block form of `sub` so the `after` replacement keeps
229
+ backslash sequences like `\1` and `\&` literal.
230
+
231
+ * **tools: bound tool output with a per-tool `max_bytes`** <br>
232
+ Each of the `Exec`, `ReadFile`, `Rg`, `Mkdir`, `Ruby`, and
233
+ `Bundle` tools gains a `max_bytes` limit (default 75,000) for the
234
+ maximum number of bytes a tool returns to the model. `Exec` and
235
+ `ReadFile` add the class-level `max_bytes` accessor, which the other
236
+ tools inherit through `Exec`, so each tool's cap can be configured
237
+ independently, for example `LLM::Tool::ReadFile.max_bytes(175_000)`.
238
+ It does not enforce the limit by itself;
239
+ [`LLM::Tool::Utils#truncate`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Utils.html#truncate-instance_method)
240
+ trims a string within the limit and marks the trailing content as
241
+ truncated, and `truncate!` returns a `[content, truncated]` tuple for
242
+ callers that structure truncated output themselves. `rg` also gains a
243
+ `max_count:` parameter that caps the number of results per file.
244
+
245
+ * **tools: add a `bundle` tool** <br>
246
+ A new [`LLM::Tool::Bundle`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Bundle.html)
247
+ tool runs a command through `bundle`. It uses the `BUNDLE_GEMFILE`
248
+ environment variable when set, or a `Gemfile` in the current working
249
+ directory otherwise. The tool takes an `arguments:` array, so the
250
+ model passes the bundle command and its arguments as a single list,
251
+ for example `arguments: ["exec", "rspec"]`.
252
+
253
+ * **tools: resolve defaults through `LLM::Utils.resolve_option`** <br>
254
+ A tool parameter default can now be an immediate value, a Symbol resolved
255
+ as a method on the tool, or a Proc evaluated lazily at runtime, matching
256
+ how `LLM::Agent` resolves its attributes. This lets a default track a
257
+ value that can change between boot and runtime, such as a tool's
258
+ `max_bytes`.
259
+
260
+ ### Registry
261
+
262
+ * **refresh model metadata** <br>
263
+ Update `data/` with current pricing, limits, and capabilities for the
264
+ OpenRouter, OpenAI, Bedrock, DeepInfra, DeepSeek, Google, Mistral,
265
+ Moonshot, Z.ai, and Alibaba registries.
266
+
267
+ ### Provider
268
+
269
+ * **provider: retry `Net::WriteTimeout`, too** <br>
270
+ Requests that raise `Net::WriteTimeout` are now retried alongside the
271
+ other timed-out and rate-limited requests, up to the `retry_budget`,
272
+ matching how `Net::OpenTimeout` and `Net::ReadTimeout` are handled. The
273
+ console status bar also reports a write timeout as `Timed out`.
274
+
275
+ * **alibaba: default to a retry budget of 8** <br>
276
+ An agent that runs on the Alibaba provider now defaults to a retry
277
+ budget of 8 instead of 5, because Alibaba (token plan) frequently rate
278
+ limits and times out requests that it later recovers from. An explicit
279
+ `retry_budget:` still overrides the default.
280
+
281
+ * **deepseek: default to the `deepseek-flash` model** <br>
282
+ The default DeepSeek chat model is now `deepseek-flash` instead of
283
+ `deepseek-v4-flash`. DeepSeek resolves `deepseek-flash` to
284
+ `deepseek-v4.1-flash` and recommends the name in its documentation and
285
+ API error messages, so the default follows the current model alias
286
+ instead of a pinned version.
287
+
288
+ ### Tracer
289
+
290
+ * **tracer: add `LLM::Tracer` factory methods** <br>
291
+ Add
292
+ [`LLM::Tracer.logger`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#logger-class_method),
293
+ [`LLM::Tracer.pretty_logger`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#pretty_logger-class_method),
294
+ and
295
+ [`LLM::Tracer.telemetry`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#telemetry-class_method)
296
+ as the preferred way to build a tracer for a provider, so switching
297
+ between tracers means changing a factory method instead of a class name.
298
+ The old `LLM.logger(llm, ...)` convenience method is removed in favor
299
+ of `LLM::Tracer.logger(llm, ...)`.
300
+
301
+ * **tracer: add `path:` support to `LLM::Tracer::PrettyLogger`** <br>
302
+ [`LLM::Tracer::PrettyLogger`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer/PrettyLogger.html)
303
+ now accepts a `path:` option to write its human-readable entries to a
304
+ file, matching `LLM::Tracer::Logger`. It previously only accepted `io:`.
305
+
306
+ ### Fix
307
+
308
+ * **json: scrub invalid UTF-8 on dump** <br>
309
+ Fix a bug where [`LLM::JSONAdapter`](https://r.uby.dev/api-docs/llm.rb/LLM/JSONAdapter.html)
310
+ raised a JSON generator error when dumping a string tagged as UTF-8 that
311
+ carried invalid bytes. The normalize step now transcodes every string to
312
+ valid UTF-8, replacing invalid sequences with the replacement character,
313
+ so dumping works on `json ~> 3.0`. The `oj` and `yajl` adapters now run
314
+ the same normalization, so every backend scrubs invalid bytes before
315
+ serializing.
316
+
317
+ * **fork: require xchan.rb `~> 0.23`** <br>
318
+ The `:fork` concurrency strategy now requires the `xchan.rb` gem at
319
+ `~> 0.23` instead of `~> 0.22`. xchan.rb 0.23.0 replaces the external
320
+ `lockf.rb` gem with a built-in, Fiddle-based `Chan::Lockf`, so fork
321
+ channels no longer carry that extra dependency. (The socket
322
+ length-header deadlock fix shipped earlier, in xchan.rb 0.22.0.)
323
+
324
+ * **async: fix a shutdown exception on the reactor thread** <br>
325
+ Fix a bug where the `:async` strategy's
326
+ [`LLM::Function::Async::Reactor`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Async/Reactor.html)
327
+ raised a `TypeError` on shutdown with recent `async` and `io-event`
328
+ versions, because their internals tried to raise an integer as an
329
+ exception. The scheduler is now detached from the reactor thread
330
+ before it exits, which avoids that code path entirely, and teardown
331
+ is managed by `reactor.stop`, so the thread exits promptly instead of
332
+ abruptly or hanging.
333
+
334
+ * **openai: prevent the loss of user messages in the completions path** <br>
335
+ Fix a bug where the request body was built from `params[:messages]`
336
+ alone when that key was present, discarding the messages built from the
337
+ prompt. The DeepSeek and Alibaba schema support injects the schema
338
+ system message into `params[:messages]`, so a request with a `schema:`
339
+ could be sent with the schema message only, dropping the user's
340
+ messages. The built messages now lead with `params[:messages]`, and the
341
+ key is removed before the body is assembled.
342
+
18
343
  ## v15.1.0
19
344
 
20
345
  Changes since `v15.0.3`.
@@ -23,7 +348,7 @@ This release adds the OpenRouter provider, splits provider timeouts
23
348
  into `connect_timeout` and `read_timeout`, retries timed-out requests,
24
349
  and renames `on_rate_limit` to `on_retry`. Skills now gain a
25
350
  frontmatter `model:` parameter and inherit the parent agent's model,
26
- while the CLI gains `-m` and `-x` switches, and the REPL shows retry
351
+ while the CLI gains `-m` and `-x` switches, and the console shows retry
27
352
  progress and measures text by display width.
28
353
 
29
354
  ### Provider
@@ -132,21 +457,21 @@ progress and measures text by display width.
132
457
  up to three stack lines from the backtrace, so the failure is easier to
133
458
  locate and report than a bare diagnostic.
134
459
 
135
- ### Repl
460
+ ### Console
136
461
 
137
- * **repl: show retry progress in the status bar** <br>
138
- When a request is rate limited or times out, the curses-based REPL status
462
+ * **console: show retry progress in the status bar** <br>
463
+ When a request is rate limited or times out, the curses-based console status
139
464
  bar shows a retry indicator with the error and the remaining attempts, for
140
465
  example `🔁 Rate limited • attempt 2 of 5`.
141
466
 
142
- * **repl: measure text width with `unicode-display_width`** <br>
143
- The curses-based REPL now counts and slices text by display column width
467
+ * **console: measure text width with `unicode-display_width`** <br>
468
+ The curses-based console now counts and slices text by display column width
144
469
  instead of character count, so wrapping, table columns, and clipping stay
145
470
  aligned for wide characters such as emoji. It requires the optional
146
471
  `unicode-display_width` gem.
147
472
 
148
- * **repl: treat `LLM::InsufficientQuotaError` as a rate limit in the status bar** <br>
149
- The curses-based REPL status bar now shows `Rate limited` when a request
473
+ * **console: treat `LLM::InsufficientQuotaError` as a rate limit in the status bar** <br>
474
+ The curses-based console status bar now shows `Rate limited` when a request
150
475
  raises
151
476
  [`LLM::InsufficientQuotaError`](https://r.uby.dev/api-docs/llm.rb/LLM/InsufficientQuotaError.html),
152
477
  matching how ordinary `LLM::RateLimitError`s are shown, instead of falling
@@ -1222,7 +1547,7 @@ reliable across all six concurrency backends. The `functions` and
1222
1547
  `LLM.require` now accepts a second `version` parameter that is passed
1223
1548
  to `Kernel#gem` before loading, enabling version constraints for
1224
1549
  optional runtime dependencies. For example,
1225
- `LLM.require "test-cmd.rb", "~> 2.1"` ensures a minimum gem version
1550
+ `LLM.require "test-cmd.rb", "~> 2.2"` ensures a minimum gem version
1226
1551
  is available. This is used internally by the `Git`, `Rg`, `Mkdir`,
1227
1552
  and `Shell` tools to enforce compatibility with the `test-cmd.rb` gem.
1228
1553
 
@@ -2286,7 +2611,7 @@ As always, see the changelog details for a thorough overview.
2286
2611
  * **Add `LLM::Agent#repl`** <br>
2287
2612
  Add a curses-based read-eval-print loop for `LLM::Agent` that lets
2288
2613
  developers interact with an agent after it has been set up or has
2289
- performed a task. It is similar to `binding.pry`: once you exit,
2614
+ performed a task. It is similar to `binding.irb`: once you exit,
2290
2615
  you can continue with the rest of your program. It requires the
2291
2616
  `curses` gem.
2292
2617