llm.rb 13.0.0 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +505 -14
  3. data/README.md +484 -50
  4. data/bin/llm.rb +148 -0
  5. data/data/anthropic.json +206 -263
  6. data/data/bedrock.json +2138 -1860
  7. data/data/deepinfra.json +1003 -624
  8. data/data/deepseek.json +38 -34
  9. data/data/google.json +1079 -371
  10. data/data/mistral.json +448 -368
  11. data/data/moonshot.json +384 -0
  12. data/data/openai.json +974 -1343
  13. data/data/xai.json +154 -126
  14. data/data/zai.json +191 -191
  15. data/lib/llm/agent.rb +123 -20
  16. data/lib/llm/context.rb +71 -88
  17. data/lib/llm/cost.rb +23 -17
  18. data/lib/llm/error.rb +0 -8
  19. data/lib/llm/function/array.rb +3 -3
  20. data/lib/llm/function/async/task.rb +2 -0
  21. data/lib/llm/function/fiber/task.rb +2 -0
  22. data/lib/llm/function/fork/task.rb +2 -0
  23. data/lib/llm/function/ractor/task.rb +2 -0
  24. data/lib/llm/function/sequential/group.rb +4 -1
  25. data/lib/llm/function/sequential/task.rb +1 -1
  26. data/lib/llm/function/task.rb +4 -0
  27. data/lib/llm/function/thread/task.rb +2 -0
  28. data/lib/llm/function.rb +33 -6
  29. data/lib/llm/guard/loop.rb +89 -0
  30. data/lib/llm/guard/null.rb +19 -0
  31. data/lib/llm/guard.rb +61 -0
  32. data/lib/llm/provider.rb +36 -0
  33. data/lib/llm/providers/anthropic/stream_parser.rb +1 -0
  34. data/lib/llm/providers/anthropic.rb +2 -9
  35. data/lib/llm/providers/bedrock/request_adapter.rb +1 -1
  36. data/lib/llm/providers/bedrock/stream_parser.rb +1 -0
  37. data/lib/llm/providers/bedrock.rb +1 -8
  38. data/lib/llm/providers/google/stream_parser.rb +1 -0
  39. data/lib/llm/providers/google.rb +1 -8
  40. data/lib/llm/providers/mistral.rb +1 -1
  41. data/lib/llm/providers/moonshot.rb +76 -0
  42. data/lib/llm/providers/ollama.rb +2 -9
  43. data/lib/llm/providers/openai/responses/stream_parser.rb +1 -0
  44. data/lib/llm/providers/openai/responses.rb +7 -9
  45. data/lib/llm/providers/openai/stream_parser.rb +1 -0
  46. data/lib/llm/providers/openai.rb +4 -11
  47. data/lib/llm/repl/bar.rb +4 -3
  48. data/lib/llm/repl/{transcript.rb → buffer.rb} +69 -29
  49. data/lib/llm/repl/color.rb +78 -0
  50. data/lib/llm/repl/command.rb +12 -5
  51. data/lib/llm/repl/commands/compact.rb +2 -2
  52. data/lib/llm/repl/commands/help.rb +3 -5
  53. data/lib/llm/repl/input/char.rb +46 -0
  54. data/lib/llm/repl/input/row.rb +39 -0
  55. data/lib/llm/repl/input.rb +251 -66
  56. data/lib/llm/repl/markdown/table.rb +11 -3
  57. data/lib/llm/repl/markdown.rb +34 -8
  58. data/lib/llm/repl/node.rb +37 -0
  59. data/lib/llm/repl/status.rb +42 -7
  60. data/lib/llm/repl/stream.rb +18 -6
  61. data/lib/llm/repl/walker.rb +3 -2
  62. data/lib/llm/repl/window.rb +54 -35
  63. data/lib/llm/repl.rb +74 -32
  64. data/lib/llm/skill.rb +20 -4
  65. data/lib/llm/stream.rb +8 -7
  66. data/lib/llm/tool.rb +29 -0
  67. data/lib/llm/tools/{swap_text.rb → edit-file.rb} +3 -3
  68. data/lib/llm/tools/git.rb +3 -0
  69. data/lib/llm/tools/mkdir.rb +3 -0
  70. data/lib/llm/tools/rg.rb +3 -0
  71. data/lib/llm/tools/ruby.rb +46 -0
  72. data/lib/llm/tools/shell.rb +3 -0
  73. data/lib/llm/tracer/pretty_logger.rb +127 -0
  74. data/lib/llm/tracer.rb +1 -0
  75. data/lib/llm/transformer/null.rb +21 -0
  76. data/lib/llm/transformer.rb +55 -0
  77. data/lib/llm/version.rb +1 -1
  78. data/lib/llm.rb +12 -2
  79. data/llm.gemspec +9 -2
  80. data/resources/deepdive/advanced/cancellation.md +74 -0
  81. data/resources/deepdive/advanced/compaction.md +83 -0
  82. data/resources/deepdive/advanced/context.md +267 -0
  83. data/resources/deepdive/advanced/guard.md +371 -0
  84. data/resources/deepdive/advanced/tracer.md +180 -0
  85. data/resources/deepdive/advanced/transformer.md +67 -0
  86. data/resources/deepdive/advanced/transports.md +45 -0
  87. data/resources/deepdive/everything_else/audio.md +122 -0
  88. data/resources/deepdive/everything_else/cost.md +99 -0
  89. data/resources/deepdive/everything_else/images.md +89 -0
  90. data/resources/deepdive/everything_else/object.md +108 -0
  91. data/resources/deepdive/everything_else/ocr.md +48 -0
  92. data/resources/deepdive/fundamentals/agents.md +202 -0
  93. data/resources/deepdive/fundamentals/builtin_tools.md +191 -0
  94. data/resources/deepdive/fundamentals/concurrency.md +104 -0
  95. data/resources/deepdive/fundamentals/database.md +449 -0
  96. data/resources/deepdive/fundamentals/embeddings.md +157 -0
  97. data/resources/deepdive/fundamentals/repl.md +87 -0
  98. data/resources/deepdive/fundamentals/schema.md +61 -0
  99. data/resources/deepdive/fundamentals/skills.md +106 -0
  100. data/resources/deepdive/fundamentals/stream.md +110 -0
  101. data/resources/deepdive/fundamentals/tools.md +265 -0
  102. data/resources/deepdive/protocols/a2a.md +106 -0
  103. data/resources/deepdive/protocols/mcp.md +111 -0
  104. data/resources/deepdive.md +58 -1792
  105. metadata +51 -7
  106. data/lib/llm/loop_guard.rb +0 -107
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0304f2a840c94209256729edf114209ffab0bdd309d1a25172818458e4c3f4f
4
- data.tar.gz: 46c695d1722ef6e618f27eb9b433f77774bf9140bfd31983589946be3ce53c1e
3
+ metadata.gz: 80d9ecf944df7561e9a0ac70bb37225dac11f18b13fba94d689b672da3b27e6f
4
+ data.tar.gz: d3986e29989bcd415c40cd26fd4361ac8b3f92461cf81f2f5abf5a9d291cd068
5
5
  SHA512:
6
- metadata.gz: 0a9da53406a6e612f68412005d33b0e85bf6064c9c38ff70e5cd2290f8b381bbba9ae9becd6ca71b15fed399e1d84bdd55972301d0cdb51c10d528d00f5a5d8f
7
- data.tar.gz: 882a041334ad21b83397792e3921510c0298b0c623b6c05441117f5655ac1fbe35b560cb015b29bcbd0aad526cc772d9e58596567c72d456d3ee77adc372f397
6
+ metadata.gz: 702cd38c23fa817b068dd9b7c8942f3641915c874e1ed9bef6526127767092f138fd09c320cd54fafc265e2b33131d0cca8b7376f2c7b5e9e3203e66b1f4a513
7
+ data.tar.gz: f5eebb31abe9349e451bb1410b01a35353dd466fc66b6df230c5cb6be469856be234dea3351f885eb9c616375c0b84bf18e72dc268b88454997919f475a89774
data/CHANGELOG.md CHANGED
@@ -15,7 +15,498 @@
15
15
 
16
16
  ## What's next
17
17
 
18
- _No unreleased changes yet._
18
+ *No unreleased changes yet. Check back after the next release.*
19
+
20
+ ## v14.0.0
21
+
22
+ Changes since `v13.1.0`.
23
+
24
+ This release replaces the `transformer=` setter with the new
25
+ `LLM::Transformer` class hierarchy, refactors guards into the
26
+ `LLM::Guard` superclass with per-tool-call interception, and replaces
27
+ the agent `tool_attempts` parameter with the `tool_budget` class DSL.
28
+ It also adds the Moonshot (Kimi) provider, the `LLM::Tool.set`
29
+ bulk-assignment DSL, a `LLM::Function#return` shorthand, and a wide
30
+ range of REPL improvements.
31
+
32
+ ### Breaking
33
+
34
+ #### Migration
35
+
36
+ | Old | New |
37
+ |-----|-----|
38
+ | `ctx.transformer = MyTransformer` | `LLM::Context.new(transformer: MyTransformer)` |
39
+ | `transformer.call(ctx, prompt, params)` | `transformer.call(message:, **opts)` |
40
+ | `~/.llm.rb/session.json` (shared across providers) | `~/.llm.rb/<provider>/<uuid>.json` (scoped per provider and directory) |
41
+ | `agent.talk(tool_attempts: 25)` | `set :tool_budget => 50` (disabled by default) |
42
+ | `LLM::LoopGuard` | `LLM::Guard::Loop` |
43
+ | `guard: true` / `ctx.guard = MyGuard` | `guard: MyGuard, guard_options: {}` |
44
+ | `guard.call(ctx)` (warning string) | `guard.call(function:)` (`LLM::Function::Return` or nil) |
45
+ | `LLM::GuardError` | `"guard_error"` |
46
+
47
+ * **replace the transformer setter with `LLM::Transformer`** <br>
48
+ The previous `transformer=` setter and 3-argument
49
+ `call(ctx, prompt, params)` interface on `LLM::Context` have been
50
+ replaced by the new
51
+ [`LLM::Transformer`](https://r.uby.dev/api-docs/llm.rb/LLM/Transformer.html)
52
+ class interface. Configure a transformer class through `transformer:`
53
+ and options through `transformer_options:` instead.
54
+
55
+ * **cli: scope session persistence per provider and directory** <br>
56
+ `bin/llm.rb` no longer shares a single session file between providers.
57
+ Each provider now has a `~/.llm.rb/<provider>.json` file that maps the
58
+ current working directory to a UUID-scoped session file under
59
+ `~/.llm.rb/<provider>/<uuid>.json`, so sessions are scoped to both the
60
+ provider and the directory they were started in.
61
+
62
+ * **cli: harden the executable against bad inputs** <br>
63
+ `bin/llm.rb` now prints an error message followed by the help menu and
64
+ exits with status 1 when the `-p` switch is given without an argument or
65
+ when an unknown option is passed. Previously unknown options produced a
66
+ warning but the run continued. The session-file lookup also no longer
67
+ rewrites `~/.llm.rb/<provider>.json` when it already exists.
68
+
69
+ * **agent: replace `tool_attempts` with the `tool_budget` class DSL** <br>
70
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#tool_budget-class_method)
71
+ replaces the `tool_attempts` parameter with a `tool_budget` class DSL
72
+ (`tool_budget { 50 }`) that caps the number of tool calls allowed in a
73
+ single turn. Once the budget is spent, the agent sends an in-band
74
+ advisory message back through the model telling it to solve the problem
75
+ with fewer tool calls.
76
+ <br><br>
77
+ The feature is now disabled by default; the old `tool_attempts`
78
+ parameter defaulted to 25, which long-horizon agents could easily
79
+ exhaust in a single turn.
80
+
81
+ * **guard: replace `LLM::LoopGuard` with the `LLM::Guard` class hierarchy** <br>
82
+ [`LLM::Guard`](https://r.uby.dev/api-docs/llm.rb/LLM/Guard.html)
83
+ is a new superclass for context-level supervisors, with
84
+ [`LLM::Guard::Loop`](https://r.uby.dev/api-docs/llm.rb/LLM/Guard/Loop.html)
85
+ (replacing `LLM::LoopGuard`) and
86
+ [`LLM::Guard::Null`](https://r.uby.dev/api-docs/llm.rb/LLM/Guard/Null.html)
87
+ as the built-in implementations.
88
+ [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html)
89
+ now accepts `guard:` (a guard class defaulting to `LLM::Guard::Null`)
90
+ and `guard_options:` (a hash forwarded to the guard's `call` method),
91
+ matching the transformer and compactor interfaces. The old boolean and
92
+ hash forms of `guard` and the `guard=` setter are removed. `LLM::Agent`
93
+ enables `LLM::Guard::Loop` by default.
94
+
95
+ * **guard: block individual tool calls instead of the whole batch** <br>
96
+ [`LLM::Guard#call`](https://r.uby.dev/api-docs/llm.rb/LLM/Guard.html#call-instance_method)
97
+ now receives the pending `function:` and returns an
98
+ [`LLM::Function::Return`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Return.html)
99
+ (or nil) instead of a warning string for the entire batch, so a guard
100
+ can block a single tool call while the rest of the batch still
101
+ executes. Custom guards that implemented the old `call(ctx)`
102
+ warning-string interface must be updated to return a
103
+ `LLM::Function::Return` instead.
104
+
105
+ * **errors: drop `LLM::GuardError`** <br>
106
+ Remove `LLM::GuardError`. The constant was never raised as an
107
+ exception; it only named the in-band error type for guarded tool
108
+ returns. Guarded tool returns now use the string `"guard_error"` as
109
+ their error type.
110
+
111
+ ### Core
112
+
113
+ * **add `LLM::Provider#build_messages` for assembling outgoing messages** <br>
114
+ [`LLM::Provider#build_messages`](https://r.uby.dev/api-docs/llm.rb/LLM/Provider.html#build_messages-instance_method)
115
+ normalizes a prompt into `LLM::Message` objects and prepends the existing
116
+ history, replacing the per-provider `build_complete_messages`
117
+ implementation. The method is idempotent: prompts that are already
118
+ [`LLM::Message`](https://r.uby.dev/api-docs/llm.rb/LLM/Message.html)
119
+ instances or arrays of messages are returned as-is.
120
+
121
+ * **copy the `params` hash in `LLM::Context` and `LLM::Agent`** <br>
122
+ [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html) and
123
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) now copy
124
+ the `params` hash in their constructors before mutating it, leaving the
125
+ caller's hash untouched. Previously the constructors deleted keys from
126
+ the caller's hash in place.
127
+
128
+ * **gemspec: ship the deepdive sub-files in the gem** <br>
129
+ The gemspec now includes `resources/deepdive/*/*.md` in the gem
130
+ package, so the full deepdive guide (fundamentals, advanced,
131
+ protocols, and everything-else chapters) is available after
132
+ installation.
133
+
134
+ * **add short aliases to `LLM::Cost`** <br>
135
+ [`LLM::Cost`](https://r.uby.dev/api-docs/llm.rb/LLM/Cost.html) now
136
+ offers short aliases for its cost accessors: `input`, `output`,
137
+ `input_audio`, `output_audio`, `input_image`, `cache_read`,
138
+ `cache_write`, and `reasoning`. Each alias matches the key used by
139
+ `#to_h`, so `cost.input` reads the same value as `cost.input_costs`.
140
+
141
+ ### Provider
142
+
143
+ * **add `LLM::Moonshot` for the Moonshot AI provider** <br>
144
+ [`LLM::Moonshot`](https://r.uby.dev/api-docs/llm.rb/LLM/Moonshot.html)
145
+ is a new provider that talks to
146
+ [Moonshot AI](https://platform.moonshot.ai) through its
147
+ OpenAI-compatible Kimi API. Create an instance with
148
+ [`LLM.moonshot`](https://r.uby.dev/api-docs/llm.rb/LLM.html#moonshot-class_method),
149
+ which accepts the same `key:`, `host:`, and `base_path:` options as the
150
+ OpenAI provider. The provider defaults to the `kimi-k3` model and
151
+ supports chat completions, streaming, tool calls, and structured output
152
+ through the shared OpenAI-compatible path; image, audio, moderation,
153
+ responses, and vector store endpoints raise `NotImplementedError`.
154
+ Model metadata ships in `data/moonshot.json` for the registry.
155
+
156
+ ### Transformer
157
+
158
+ * **add `LLM::Transformer` for rewriting messages before they reach the provider** <br>
159
+ [`LLM::Transformer`](https://r.uby.dev/api-docs/llm.rb/LLM/Transformer.html)
160
+ is a new superclass for message transformers. A transformer is bound to a
161
+ context and rewrites a single message before it is sent to the provider,
162
+ which makes it possible to redact personal information or rewrite any
163
+ message before it goes out over the wire. Each subclass implements
164
+ `call(message:, **opts)` and returns the message to send, either by
165
+ mutating it in place or returning a new one.
166
+ [`LLM::Transformer::Null`](https://r.uby.dev/api-docs/llm.rb/LLM/Transformer/Null.html)
167
+ is a no-op transformer used as the default.
168
+
169
+ * **hook the transformer API into `LLM::Context`** <br>
170
+ [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html) now
171
+ accepts `transformer:` (a transformer class defaulting to
172
+ `LLM::Transformer::Null`) and `transformer_options:` (a hash forwarded to
173
+ the transformer's `call` method). The transformer runs on the most recent
174
+ message in both chat and responses turns.
175
+ [`LLM::Stream#on_transform`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_transform-instance_method)
176
+ and
177
+ [`LLM::Stream#on_transform_finish`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_transform_finish-instance_method)
178
+ now receive the transformer instance as their single argument.
179
+
180
+ ### Tool
181
+
182
+ * **add `LLM::Tool.set` for bulk-assigning tool properties** <br>
183
+ [`LLM::Tool.set`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html#set-class_method)
184
+ accepts a hash of `name`, `description`, `parameters`, `required`, and
185
+ `defaults` to configure a tool subclass in a single call. Parameters are
186
+ defined as tuples of `[name, type, description, options]`, matching the
187
+ same interface as the existing `parameter` DSL. Unknown keys raise
188
+ `KeyError`.
189
+
190
+ ### Function
191
+
192
+ * **add `LLM::Function#return` for building tool returns** <br>
193
+ [`LLM::Function#return`](https://r.uby.dev/api-docs/llm.rb/LLM/Function.html#return-instance_method)
194
+ returns an
195
+ [`LLM::Function::Return`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Return.html)
196
+ built from the function's own id and name, using the given hash as its
197
+ value. It is a shorthand mainly useful inside a
198
+ [`LLM::Guard`](https://r.uby.dev/api-docs/llm.rb/LLM/Guard.html)
199
+ subclass and is defined via `define_method` because `return` is a Ruby
200
+ keyword.
201
+
202
+ ### Guard
203
+
204
+ * **run the guard for streamed tool calls** <br>
205
+ Fix a gap where the guard was not consulted when a tool call was queued
206
+ while a response was still streaming. The guard is now stamped onto the
207
+ functions a context binds, so it runs wherever a task is spawned,
208
+ including tool calls queued from a stream. A blocked call yields its
209
+ `guard_error` return without executing.
210
+
211
+ ### Agent
212
+
213
+ * **add `LLM::Agent#compacted?`** <br>
214
+ [`LLM::Agent#compacted?`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#compacted%3F-instance_method)
215
+ delegates to the wrapped
216
+ [`LLM::Context#compacted?`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#compacted%3F-instance_method)
217
+ and reports whether the conversation has been compacted, so callers
218
+ can detect when history was trimmed.
219
+
220
+ ### Change
221
+
222
+ * **openai: default to `gpt-5.6-luna`** <br>
223
+ The default OpenAI chat model has changed from `gpt-5.4-mini` to
224
+ `gpt-5.6-luna`. The new model is OpenAI's fastest and most affordable
225
+ option, matching the kind of default llm.rb aims for.
226
+
227
+ ### Repl
228
+
229
+ * **show an unknown context state after `/compact`** <br>
230
+ After running `/compact`, the REPL status line now renders `Context
231
+ compacted` and the context-usage bar shows `???` instead of a percentage,
232
+ because the used context is unknown until the next response.
233
+
234
+ * **land on a blank line after Ctrl+N at the end of history** <br>
235
+ When recalling history with Ctrl+P and Ctrl+N, Ctrl+N at the last item
236
+ now advances to a blank line so you can start typing new input, instead
237
+ of staying stuck on the last item in history (the previous behavior).
238
+ Recalling with Ctrl+P or Ctrl+N also no longer overwrites the input when
239
+ there is no history to show.
240
+
241
+ * **restore history wrap for Ctrl+P and Ctrl+N** <br>
242
+ Fix a regression where Ctrl+P and Ctrl+N recalled history text without
243
+ reflowing it into rows, so recalled lines wider than the terminal were
244
+ clipped. Recalled text now flows through the same word-wrap path as
245
+ typed input and wraps at the terminal width.
246
+
247
+ * **restore Ctrl+D deletion across rows** <br>
248
+ Fix a bug where Ctrl+D at the end of an input row was a no-op, so
249
+ multiline input could not be joined by deleting a row break. Deleting
250
+ at the end of a row now consumes the break and pulls the next row up,
251
+ restoring the split space so merged words do not run together.
252
+
253
+ * **center the buffer with 20% gutters** <br>
254
+ The curses-based REPL now centers
255
+ [`LLM::Repl::Buffer`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Buffer.html)
256
+ in a content area that is 60% of the terminal width, with an unused 20%
257
+ gutter on each side. The drawing area is based on the available rows and
258
+ columns instead of a fixed 80-column width, and `Buffer#wrap` now
259
+ hard-breaks words that overflow the width onto the next row, fixing a
260
+ bug where a word could be cut off between rows.
261
+
262
+ * **apply markdown to previous messages** <br>
263
+ The curses-based REPL now renders every message in the buffer with
264
+ markdown styling, including messages that were already present when
265
+ the session started or restored from disk. Previously only newly
266
+ streamed responses were styled; older messages fell back to plain
267
+ text.
268
+
269
+ * **add `LLM::Repl#sender` for the user label** <br>
270
+ [`LLM::Repl#sender`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl.html#sender-instance_method)
271
+ returns the label used for user messages in the curses-based REPL. It
272
+ defaults to `"You"` (previously `"user"`), and the buffer layout now
273
+ places each label on its own line followed by the message content and a
274
+ blank line.
275
+
276
+ * **add `LLM::Repl::Color` for coloring the curses UI** <br>
277
+ Add
278
+ [`LLM::Repl::Color`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Color.html)
279
+ as a new module that returns Curses color bitmasks. `Color.enable`
280
+ initializes 8 color pairs, and methods like `Color.blue` return the
281
+ corresponding `Curses.color_pair(X)` bitmask, which can be bitwise
282
+ OR'ed with other attributes such as `Curses::A_BOLD`. User labels in
283
+ the REPL are now rendered in blue instead of plain bold text.
284
+
285
+ * **split on words rather than characters** <br>
286
+ `LLM::Repl::Buffer#wrap` now breaks text on word boundaries instead of
287
+ wrapping one character at a time. A word that does not fit on the
288
+ current row moves to the next, and only a single word longer than the
289
+ whole width is hard-broken, so text is never clipped by the window.
290
+
291
+ * **render kramdown typographic symbols and smart quotes** <br>
292
+ Fix a bug where certain character sequences such as `...` were not
293
+ rendered at all in the curses-based REPL. Kramdown parses them into
294
+ `:typographic_sym` and `:smart_quote` nodes, which previously fell
295
+ through to the children clause and were dropped. The markdown renderer
296
+ now maps them to their unicode equivalents: ellipsis, en and em
297
+ dashes, guillemets, and single and double quotation marks.
298
+
299
+ * **apply colors to the markdown renderer** <br>
300
+ The curses-based REPL now renders markdown with the `LLM::Repl::Color`
301
+ palette: headers and strong text in white, code spans and code blocks
302
+ in green, and links in underlined green, on the black background.
303
+ Previously markdown styling used bold, underline, and reverse video
304
+ attributes only.
305
+
306
+ * **wrap the input line at word boundaries** <br>
307
+ The curses-based REPL input line now wraps words whole onto the next
308
+ row at the terminal width instead of cutting them in half. A word that
309
+ does not fit on the current row moves to the next row, and only a
310
+ single word longer than the whole width is hard-broken, so typed text
311
+ is never clipped by the window.
312
+
313
+ * **distinguish the connecting and thinking status bar phases** <br>
314
+ The curses-based REPL status bar now shows `Connecting • Esc to
315
+ cancel` while the model is establishing a connection, then switches
316
+ to `Thinking • Esc to cancel` once a tool call or text fragment
317
+ arrives on the stream. Active tool calls appear in the status bar
318
+ with a lambda indicator.
319
+
320
+ * **add emoji to the status bar phases** <br>
321
+ The curses-based REPL status bar now uses emoji to identify each
322
+ phase at a glance: a globe (`🌐`) while the model is connecting, and
323
+ a brain (`🧠`) while it is thinking. The text after the emoji still
324
+ reads `Connecting • Esc to cancel` and `Thinking • Esc to cancel`
325
+ respectively.
326
+
327
+ * **render the status bar with color and attributes** <br>
328
+ The curses-based REPL status bar now supports colored and attributed
329
+ status text, so the lambda indicator for active tool calls is drawn
330
+ in bold red.
331
+
332
+ ### Registry
333
+
334
+ * **refresh model metadata across providers** <br>
335
+ Update `data/*.json` files with current provider model listings and
336
+ pricing. Remove the deprecated Claude Opus 4.1 entries from the
337
+ Anthropic registry, add `Qwen/Qwen3.8-Max` to DeepInfra, and add a
338
+ `low` reasoning-effort option to DeepSeek.
339
+
340
+ ## v13.1.0
341
+
342
+ Changes since `v13.0.0`.
343
+
344
+ This release adds `LLM::Agent` class DSL attributes (`path`, `description`),
345
+ extends skills with file-path loading and the `tools: all` directive, adds new
346
+ built-in tools (`LLM::Tool::Ruby`, `LLM::Tool::EditFile`), introduces the
347
+ `LLM::Tracer::PrettyLogger` for human-readable tracing, renames `Transcript`
348
+ to `Buffer` across the REPL, ships a `bin/llm.rb` CLI entry point, and fixes
349
+ several agent and tool bugs around persistence, interruption, and naming.
350
+
351
+ ### Core
352
+
353
+ * **add post install message with deepdive link** <br>
354
+ The gemspec now includes a `post_install_message` that points users to
355
+ the deepdive guide at `https://r.uby.dev/llm/deepdive` after installation,
356
+ making it easier for new users to discover the project documentation.
357
+
358
+ ### Agent
359
+
360
+ * **add `description` class DSL and instance method** <br>
361
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) now
362
+ has a `description` class DSL (`description "release engineer"`) and a
363
+ corresponding `#description` instance method. The description is an
364
+ optional self-documenting string that serves as a brief summary of the
365
+ agent's purpose. It can be set via the class DSL,
366
+ `LLM::Agent.set(description: ...)`, or `LLM::Agent.new(description: ...)`.
367
+
368
+ * **add `path` class DSL and instance method** <br>
369
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) now
370
+ has a `path` class DSL (`path "contexts/admin.json"`) and a
371
+ corresponding `#path` instance method. When a path is set, the agent
372
+ automatically restores its conversation history from that file on
373
+ initialization and saves it back after each `talk` or `ask` turn,
374
+ making session persistence across process restarts transparent.
375
+
376
+ ### Skills
377
+
378
+ * **accept a path to a markdown file** <br>
379
+ [`LLM::Skill.load`](https://r.uby.dev/api-docs/llm.rb/LLM/Skill.html#load-class_method)
380
+ now accepts a path to a markdown file in addition to a directory path.
381
+ When given a file path, the file is read directly instead of looking for
382
+ a `SKILL.md` inside a directory. This makes it possible to load a single
383
+ markdown file as a skill without placing it in a dedicated directory.
384
+
385
+ * **extend with `all` keyword for loading the full tool registry** <br>
386
+ `LLM::Skill` now supports `tools: all` (or `tools: "*"`) in the frontmatter
387
+ to load all tools from the global
388
+ [`LLM::Tool.registry`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html#registry-class_method).
389
+ Previously, the `tools:` frontmatter only accepted `inherit`, an array of tool
390
+ names, or nothing. The new `all` keyword makes it possible to give a skill
391
+ access to every registered tool without listing them individually.
392
+
393
+ ### Tools
394
+
395
+ * **add `LLM::Tool::Ruby` for executing Ruby code in a subprocess** <br>
396
+ [`LLM::Tool::Ruby`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Ruby.html)
397
+ is a new built-in tool that runs a string of Ruby code in a separate
398
+ Ruby process with a configurable timeout (default 15s). The code runs
399
+ in an isolated address space unaware of its parent, making it useful
400
+ for safe(ish) dynamic code execution. It must be required explicitly
401
+ with `require "llm/tools/ruby"` and requires the `test-cmd.rb` gem.
402
+
403
+ * **rename `LLM::Tool::SwapText` to `LLM::Tool::EditFile`** <br>
404
+ The `SwapText` tool has been renamed to
405
+ [`LLM::Tool::EditFile`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/EditFile.html)
406
+ to better match the naming of sibling tools (`ReadFile`, `WriteFile`).
407
+ The old `require "llm/tools/swap_text"` path no longer exists; use
408
+ `require "llm/tools/edit-file"` instead.
409
+
410
+ ### Tracer
411
+
412
+ * **add `LLM::Tracer::PrettyLogger` for human-readable tracing** <br>
413
+ [`LLM::Tracer::PrettyLogger`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer/PrettyLogger.html)
414
+ is a new tracer that writes human-readable request and tool-call logs to a
415
+ console or file. Unlike the structured JSON output of
416
+ `LLM::Tracer::Logger`, the pretty logger emits single-line entries with
417
+ inline context, making it easier to follow agent activity at a glance.
418
+ It writes to `$stderr` by default and accepts an `io:` option for file
419
+ output.
420
+
421
+ ### Repl
422
+
423
+ * **rename `LLM::Repl::Transcript` to `LLM::Repl::Buffer`** <br>
424
+ `LLM::Repl::Transcript` has been renamed to
425
+ [`LLM::Repl::Buffer`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Buffer.html)
426
+ to better reflect its role as a conversation state manager. The old
427
+ `start` and `finish` methods have been renamed to `open` and `close`
428
+ respectively. The public accessor on
429
+ [`LLM::Repl`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl.html) has been
430
+ renamed from `transcript` to `buffer`.
431
+
432
+ * **add `write_message` for formatted message writing** <br>
433
+ [`LLM::Repl::Buffer#write_message`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Buffer.html#write_message-instance_method)
434
+ and
435
+ [`LLM::Repl#write_message`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl.html#write_message-instance_method)
436
+ provide a convenience method that takes a username and content string,
437
+ formatting the output with a bold `user:` label and a trailing newline.
438
+ This is simpler than the equivalent sequence of `write` calls.
439
+
440
+ * **add `Command#write_message` and refactor `Command#write`** <br>
441
+ [`LLM::Command#write_message`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Command.html#write_message-instance_method)
442
+ provides a convenience method that takes a username and content string,
443
+ matching the same interface on `LLM::Repl` and `LLM::Buffer`. The
444
+ `Command#write` method is now implemented on top of `write_message`,
445
+ always prefixing output with `command(<name>): `. The `who:` keyword
446
+ argument previously accepted by `write` has been removed; use
447
+ `write_message` instead.
448
+
449
+ * **display pre-existing agent messages when the repl starts** <br>
450
+ When
451
+ [`LLM::Agent#repl`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#repl-instance_method)
452
+ starts, any messages already in the agent's buffer are now rendered
453
+ in the REPL window. Previously the REPL started with an empty
454
+ transcript even when the agent carried prior conversation history,
455
+ making it harder to resume a session. Tool-call and tool-return
456
+ messages are skipped to avoid visual noise.
457
+
458
+ ### CLI
459
+
460
+ * **add `bin/llm.rb` for launching the REPL from the command line** <br>
461
+ A new executable script (`bin/llm.rb`) provides a convenient way to start
462
+ an interactive REPL session directly from the terminal. It auto-detects
463
+ the provider from environment variables like `OPENAI_API_KEY`, supports
464
+ a `-p PROVIDER` flag for explicit provider selection, a `-t` flag for
465
+ temporary (non-persistent) sessions, and `-h` for help. Sessions are
466
+ automatically saved to `~/.llm.rb/` by default.
467
+
468
+ ### Fix
469
+
470
+ * **agent: fix `path` restore on first run** <br>
471
+ Fix a bug where [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
472
+ called `@ctx.restore(path:)` even when the path's file did not exist.
473
+ The fix checks `File.readable?(@path)` before attempting to restore,
474
+ so the agent starts with a blank conversation on first use instead of
475
+ failing with a file-not-found error.
476
+
477
+ * **tools: re-raise `LLM::Interrupt` to abort the turn** <br>
478
+ [`LLM::Tool::Git`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Git.html),
479
+ [`LLM::Tool::Mkdir`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Mkdir.html),
480
+ [`LLM::Tool::Rg`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Rg.html),
481
+ [`LLM::Tool::Ruby`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Ruby.html),
482
+ and
483
+ [`LLM::Tool::Shell`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Shell.html)
484
+ now re-raise `LLM::Interrupt` after killing their running command. The
485
+ previous behavior rescued the interrupt and killed the child process but
486
+ let the turn continue, which meant a cancelled tool call did not abort
487
+ the conversation turn. Re-raising ensures the entire turn is interrupted.
488
+
489
+ * **tools: rescue `LLM::Interrupt` in shell-based tools** <br>
490
+ [`LLM::Tool::Shell`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Shell.html),
491
+ [`LLM::Tool::Git`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Git.html),
492
+ [`LLM::Tool::Mkdir`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Mkdir.html),
493
+ and
494
+ [`LLM::Tool::Rg`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Rg.html)
495
+ now rescue `LLM::Interrupt` and kill their running command, preventing
496
+ orphaned child processes when a tool is interrupted during execution.
497
+
498
+ * **agent: fix default name derivation** <br>
499
+ Fix a bug where `LLM::Agent` used without a subclass derived its default
500
+ name as `"l-lm-agent"` instead of `"agent"`. The fix replaces the
501
+ regex-based parameterization with a pattern that correctly handles
502
+ single-word class names and multi-word namespaced names.
503
+
504
+ * **function: `#params` always returns an `LLM::Object`** <br>
505
+ [`LLM::Function#params`](https://r.uby.dev/api-docs/llm.rb/LLM/Function.html#params-instance_method)
506
+ now always returns an `LLM::Object` representing the function's parameter
507
+ schema. Previously it returned `nil` when a function defined no parameters,
508
+ forcing every caller to guard against `nil`. All provider adapters now use
509
+ `fn.params.to_h` instead of `fn.params || {type: "object", properties: {}}`.
19
510
 
20
511
  ## v13.0.0
21
512
 
@@ -406,7 +897,7 @@ reliable across all six concurrency backends. The `functions` and
406
897
 
407
898
  Changes since `v12.5.1`.
408
899
 
409
- This release adds bulk defaults for tools and agents `LLM::Tool.defaults`
900
+ This release adds bulk defaults for tools and agents: `LLM::Tool.defaults`
410
901
  for setting parameter defaults and `LLM::Agent.set` for mass-assigning
411
902
  class-level defaults, both mirrored on ActiveRecord and Sequel agent models.
412
903
 
@@ -442,7 +933,7 @@ prior call.
442
933
  On cancel, `LLM::Interrupt` is now raised on the thread that is
443
934
  running a tool. The tool can rescue `LLM::Interrupt` and gracefully
444
935
  terminate (e.g., clean up resources). The previous approach used
445
- `Thread#interrupt` which was less reliable it did not interrupt a
936
+ `Thread#interrupt` which was less reliable. It did not interrupt a
446
937
  sleeping thread.
447
938
 
448
939
  * **function: suppress thread exception reporting in `:thread` concurrency** <br>
@@ -468,15 +959,15 @@ prior call.
468
959
  `LLM::Interrupt` is now raised on the active fiber via `Fiber#raise`
469
960
  when interrupting `:fiber`-concurrency tools.
470
961
  <br><br>
471
- `Task#interrupt!` now dispatches by task type `Thread#raise` for
472
- threads, `Fiber#raise` for fibers making interruption reliable
962
+ `Task#interrupt!` now dispatches by task type: `Thread#raise` for
963
+ threads, `Fiber#raise` for fibers. Making interruption reliable
473
964
  across all concurrency strategies.
474
965
 
475
966
  * **function: raise `LLM::Interrupt` on fork-backed tool tasks** <br>
476
967
  `LLM::Interrupt` is now raised on the main thread of a fork child
477
968
  process via `Thread.main.raise(LLM::Interrupt)` when interrupting
478
969
  `:fork`-concurrency tools, and the fork `Task#wait` re-raises the
479
- interrupt on the parent side making interruption reliable across
970
+ interrupt on the parent side. Making interruption reliable across
480
971
  all concurrency strategies including `:fork`.
481
972
 
482
973
  * **function: raise `LLM::Interrupt` on `Async::Task`-backed tool tasks** <br>
@@ -494,7 +985,7 @@ prior call.
494
985
  raises `LLM::Interrupt` on the ractor's main thread.
495
986
  <br><br>
496
987
  `Task#interrupt!` delegates to the mailbox to send the interrupt
497
- message extending reliable interruption to the `:ractor`
988
+ message. Extending reliable interruption to the `:ractor`
498
989
  concurrency strategy.
499
990
 
500
991
  ## v12.5.1
@@ -641,7 +1132,7 @@ OpenAI, Google, DeepInfra, DeepSeek, and xAI model entries.
641
1132
 
642
1133
  * **repl: display command errors in the curses UI** <br>
643
1134
  Commands invoked with too few arguments now display an error
644
- message `command(<name>): too few arguments` directly in
1135
+ message: `command(<name>): too few arguments`. Displayed directly in
645
1136
  the curses transcript area, giving immediate feedback instead
646
1137
  of silently failing.
647
1138
 
@@ -765,7 +1256,7 @@ a command system foundation with the `/exit` command, and several new
765
1256
  keybindings (Ctrl+F, Ctrl+K, Ctrl+Y). Tool calls are rendered with a
766
1257
  compact function-call syntax in the status bar.
767
1258
 
768
- Two new built-in tools `LLM::Tool::Ls` and `LLM::Tool::Which` are
1259
+ Two new built-in tools: `LLM::Tool::Ls` and `LLM::Tool::Which` are
769
1260
  available as opt-in additions for file listing and executable lookup.
770
1261
 
771
1262
  Model metadata has been refreshed across providers, the REPL loop
@@ -832,7 +1323,7 @@ area.
832
1323
  The curses-based REPL input now detects paste operations by tracking
833
1324
  the rate at which characters arrive. A paste rate of ≤50ms is
834
1325
  assumed to be a burst of characters that could only be explained by
835
- a paste no human types that fast. Multiline pastes are supported
1326
+ a paste. No human types that fast. Multiline pastes are supported
836
1327
  through internal refactoring of the input handling logic.
837
1328
 
838
1329
  * **repl: optimize paste mode rendering** <br>
@@ -855,7 +1346,7 @@ area.
855
1346
 
856
1347
  * **repl: render tool calls in a function-call syntax** <br>
857
1348
  The curses-based REPL status bar now renders tool calls with a
858
- compact function-call syntax `tool(key: value)` instead of
1349
+ compact function-call syntax: `tool(key: value)` instead of
859
1350
  `tool: name`. Strings are quoted and truncated, arrays show their
860
1351
  first two elements, and hashes collapse to `{…}`, making it easier
861
1352
  to see what arguments the model is passing. The `tool done` status
@@ -983,8 +1474,8 @@ tracer logger instances with less verbosity.
983
1474
  streamed content via `#<<`. `LLM::Stream::Disabled` represents an explicitly
984
1475
  disabled stream with no-op callbacks.
985
1476
 
986
- This is part of an internal refactoring that lets all stream values IO
987
- objects, `true`, `false`, `nil`, and `LLM::Stream` instances themselves
1477
+ This is part of an internal refactoring that lets all stream values: IO
1478
+ objects, `true`, `false`, `nil`, and `LLM::Stream` instances themselves
988
1479
  be represented by the same `LLM::Stream` interface via the new
989
1480
  `LLM::Stream.try` factory method.
990
1481
 
@@ -1053,7 +1544,7 @@ tracer logger instances with less verbosity.
1053
1544
 
1054
1545
  * **repl: add extra padding between markdown nodes** <br>
1055
1546
  The curses-based REPL markdown renderer now adds extra vertical spacing
1056
- between certain markdown elements paragraphs, headers, and codeblocks
1547
+ between certain markdown elements: paragraphs, headers, and codeblocks
1057
1548
  for improved readability of model responses.
1058
1549
 
1059
1550
  * **repl: add a visual divider between transcript and the rows below it** <br>