ruby_llm 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (522) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +162 -120
  3. data/app/assets/javascripts/ruby_llm/chat_message_controller.js +163 -0
  4. data/app/assets/javascripts/ruby_llm/dompurify.js +9 -0
  5. data/app/assets/javascripts/ruby_llm/marked.js +71 -0
  6. data/config/importmap.rb +5 -0
  7. data/lib/generators/ruby_llm/agent/agent_generator.rb +11 -3
  8. data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +6 -3
  9. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +201 -55
  10. data/lib/generators/ruby_llm/chat_ui/templates/agents/chat_agent.rb.tt +15 -0
  11. data/lib/generators/ruby_llm/chat_ui/templates/controllers/answers_controller.rb.tt +31 -0
  12. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +16 -5
  13. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +27 -9
  14. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +1 -1
  15. data/lib/generators/ruby_llm/chat_ui/templates/controllers/queued_messages_controller.rb.tt +14 -0
  16. data/lib/generators/ruby_llm/chat_ui/templates/javascript/chat_message_controller.js +1 -0
  17. data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +5 -8
  18. data/lib/generators/ruby_llm/chat_ui/templates/jobs/resume_interrupted_chats_job.rb.tt +7 -0
  19. data/lib/generators/ruby_llm/chat_ui/templates/migrations/add_reply_error_migration.rb.tt +5 -0
  20. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_files.html.erb.tt +15 -0
  21. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +5 -7
  22. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_model_select.html.erb.tt +7 -0
  23. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +32 -3
  24. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +17 -2
  25. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_attachments.html.erb.tt +14 -0
  26. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +1 -1
  27. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +28 -18
  28. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_queued_message.html.erb.tt +18 -0
  29. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +1 -1
  30. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +4 -1
  31. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +4 -2
  32. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +1 -1
  33. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +4 -2
  34. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/questions/_question.html.erb.tt +101 -0
  35. data/lib/generators/ruby_llm/chat_ui/templates/test/agents/chat_agent_test.rb.tt +28 -0
  36. data/lib/generators/ruby_llm/chat_ui/templates/test/application_system_test_case.rb.tt +5 -0
  37. data/lib/generators/ruby_llm/chat_ui/templates/test/system/chats_test.rb.tt +240 -0
  38. data/lib/generators/ruby_llm/chat_ui/templates/views/answers/create.turbo_stream.erb.tt +6 -0
  39. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_files.html.erb.tt +15 -0
  40. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +5 -7
  41. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_model_select.html.erb.tt +7 -0
  42. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_questions.html.erb.tt +13 -0
  43. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +29 -3
  44. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +16 -2
  45. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_attachments.html.erb.tt +14 -0
  46. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_error.html.erb.tt +1 -1
  47. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +26 -16
  48. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_queued_message.html.erb.tt +8 -0
  49. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt +1 -1
  50. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +4 -1
  51. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +3 -2
  52. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +1 -0
  53. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +1 -1
  54. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +3 -2
  55. data/lib/generators/ruby_llm/chat_ui/templates/views/questions/_question.html.erb.tt +78 -0
  56. data/lib/generators/ruby_llm/evaluation/evaluation_generator.rb +43 -0
  57. data/lib/generators/ruby_llm/evaluation/templates/evaluation.rb.tt +9 -0
  58. data/lib/generators/ruby_llm/evaluation/templates/evaluation.yml.tt +4 -0
  59. data/lib/generators/ruby_llm/generator_helpers.rb +116 -3
  60. data/lib/generators/ruby_llm/install/install_generator.rb +5 -47
  61. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +4 -0
  62. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +5 -3
  63. data/lib/generators/ruby_llm/install/templates/create_ruby_llm_records_migration.rb.tt +41 -1
  64. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +2 -1
  65. data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +1 -2
  66. data/lib/generators/ruby_llm/judge/judge_generator.rb +35 -0
  67. data/lib/generators/ruby_llm/judge/templates/judge.rb.tt +2 -0
  68. data/lib/generators/ruby_llm/mcp/mcp_generator.rb +58 -0
  69. data/lib/generators/ruby_llm/mcp/templates/mcp.rb.tt +7 -0
  70. data/lib/generators/ruby_llm/provider/scaffold.rb +5 -12
  71. data/lib/generators/ruby_llm/provider/templates/core/provider.rb.erb +6 -0
  72. data/lib/generators/ruby_llm/provider/templates/gem/chat_spec.rb.erb +0 -1
  73. data/lib/generators/ruby_llm/provider/templates/gem/chat_streaming_spec.rb.erb +0 -1
  74. data/lib/generators/ruby_llm/provider/templates/gem/moderation_spec.rb.erb +1 -1
  75. data/lib/generators/ruby_llm/sandbox/sandbox_generator.rb +68 -0
  76. data/lib/generators/ruby_llm/sandbox/templates/sandbox.rb.tt +3 -0
  77. data/lib/generators/ruby_llm/skill/skill_generator.rb +48 -0
  78. data/lib/generators/ruby_llm/tool/templates/tailwind/tool_call.html.erb.tt +1 -1
  79. data/lib/generators/ruby_llm/tool/templates/tailwind/tool_result.html.erb.tt +1 -1
  80. data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +0 -3
  81. data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +1 -1
  82. data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +1 -1
  83. data/lib/generators/ruby_llm/tool/tool_generator.rb +1 -5
  84. data/lib/generators/ruby_llm/upgrade/templates/upgrade_ruby_llm_to_2_2.rb.tt +164 -0
  85. data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +41 -3
  86. data/lib/ruby_llm/accounting/operation.rb +73 -0
  87. data/lib/ruby_llm/accounting/usage.rb +74 -24
  88. data/lib/ruby_llm/active_record/acts_as.rb +78 -11
  89. data/lib/ruby_llm/active_record/attachment_helpers.rb +10 -4
  90. data/lib/ruby_llm/active_record/batch.rb +67 -9
  91. data/lib/ruby_llm/active_record/chat_agent_hooks.rb +17 -0
  92. data/lib/ruby_llm/active_record/chat_approvals.rb +71 -0
  93. data/lib/ruby_llm/active_record/chat_cancellation.rb +106 -0
  94. data/lib/ruby_llm/active_record/chat_column_guard.rb +51 -0
  95. data/lib/ruby_llm/active_record/chat_file.rb +37 -0
  96. data/lib/ruby_llm/active_record/chat_files.rb +30 -0
  97. data/lib/ruby_llm/active_record/chat_helper.rb +73 -0
  98. data/lib/ruby_llm/active_record/chat_methods.rb +522 -277
  99. data/lib/ruby_llm/active_record/chat_placeholders.rb +67 -0
  100. data/lib/ruby_llm/active_record/chat_questions.rb +177 -0
  101. data/lib/ruby_llm/active_record/chat_queue.rb +120 -0
  102. data/lib/ruby_llm/active_record/chat_reply_error.rb +35 -0
  103. data/lib/ruby_llm/active_record/chat_run.rb +51 -0
  104. data/lib/ruby_llm/active_record/chat_tool_call_state.rb +77 -0
  105. data/lib/ruby_llm/active_record/chunk_methods.rb +49 -0
  106. data/lib/ruby_llm/active_record/claim_heartbeat.rb +149 -0
  107. data/lib/ruby_llm/active_record/file_store.rb +269 -0
  108. data/lib/ruby_llm/active_record/mcp_credential.rb +24 -9
  109. data/lib/ruby_llm/active_record/message_broadcasting.rb +85 -0
  110. data/lib/ruby_llm/active_record/message_methods.rb +83 -11
  111. data/lib/ruby_llm/active_record/model.rb +37 -9
  112. data/lib/ruby_llm/active_record/provider_file.rb +21 -4
  113. data/lib/ruby_llm/active_record/queued_message.rb +50 -0
  114. data/lib/ruby_llm/active_record/record.rb +3 -1
  115. data/lib/ruby_llm/active_record/run_claims.rb +608 -0
  116. data/lib/ruby_llm/active_record/solid_queue_shutdown.rb +33 -0
  117. data/lib/ruby_llm/active_record/system_instructions.rb +30 -0
  118. data/lib/ruby_llm/active_record/tool_call.rb +55 -4
  119. data/lib/ruby_llm/active_record/usage.rb +79 -9
  120. data/lib/ruby_llm/agent/callbacks.rb +98 -0
  121. data/lib/ruby_llm/agent/context_budget.rb +26 -0
  122. data/lib/ruby_llm/agent/join_callbacks.rb +28 -0
  123. data/lib/ruby_llm/agent/model_options.rb +56 -0
  124. data/lib/ruby_llm/agent/ownership.rb +22 -0
  125. data/lib/ruby_llm/agent/tool_conversion.rb +19 -0
  126. data/lib/ruby_llm/agent.rb +620 -236
  127. data/lib/ruby_llm/agent_tool.rb +91 -0
  128. data/lib/ruby_llm/aliases.json +84 -91
  129. data/lib/ruby_llm/attachment.rb +101 -20
  130. data/lib/ruby_llm/batch.rb +132 -46
  131. data/lib/ruby_llm/cached_content.rb +4 -4
  132. data/lib/ruby_llm/chat/agent_run.rb +22 -0
  133. data/lib/ruby_llm/chat/context_budget.rb +119 -0
  134. data/lib/ruby_llm/chat/file_references.rb +259 -0
  135. data/lib/ruby_llm/chat/message_queue.rb +56 -0
  136. data/lib/ruby_llm/chat/ownership.rb +18 -0
  137. data/lib/ruby_llm/chat/rescuable.rb +48 -0
  138. data/lib/ruby_llm/chat/run.rb +32 -0
  139. data/lib/ruby_llm/chat/tool_call_ids.rb +46 -0
  140. data/lib/ruby_llm/chat/tool_call_state.rb +115 -0
  141. data/lib/ruby_llm/chat/tool_concurrency.rb +90 -49
  142. data/lib/ruby_llm/chat/tool_search.rb +36 -1
  143. data/lib/ruby_llm/chat/tool_sequences.rb +84 -0
  144. data/lib/ruby_llm/chat.rb +1598 -332
  145. data/lib/ruby_llm/chunk.rb +27 -1
  146. data/lib/ruby_llm/citation.rb +2 -1
  147. data/lib/ruby_llm/code_execution/language.rb +28 -0
  148. data/lib/ruby_llm/code_execution/node_installation.rb +56 -0
  149. data/lib/ruby_llm/code_execution/packages.rb +241 -0
  150. data/lib/ruby_llm/code_execution/ruby_installation.rb +170 -0
  151. data/lib/ruby_llm/code_execution/workspace.rb +314 -0
  152. data/lib/ruby_llm/code_execution.rb +781 -0
  153. data/lib/ruby_llm/configuration.rb +151 -4
  154. data/lib/ruby_llm/content_filter.rb +68 -0
  155. data/lib/ruby_llm/cost.rb +68 -41
  156. data/lib/ruby_llm/downloaded_file.rb +10 -9
  157. data/lib/ruby_llm/embedding.rb +30 -45
  158. data/lib/ruby_llm/embedding_request.rb +1 -1
  159. data/lib/ruby_llm/error.rb +167 -14
  160. data/lib/ruby_llm/evaluation/dataset.rb +5 -4
  161. data/lib/ruby_llm/evaluation/evidence.rb +5 -3
  162. data/lib/ruby_llm/evaluation/report.rb +7 -3
  163. data/lib/ruby_llm/evaluation/result.rb +1 -1
  164. data/lib/ruby_llm/evaluation.rb +2 -1
  165. data/lib/ruby_llm/fallback.rb +32 -4
  166. data/lib/ruby_llm/file_store/converters/ocr.rb +28 -0
  167. data/lib/ruby_llm/file_store/converters/office/markup.rb +148 -0
  168. data/lib/ruby_llm/file_store/converters/office/outline.rb +193 -0
  169. data/lib/ruby_llm/file_store/converters/office/prose.rb +161 -0
  170. data/lib/ruby_llm/file_store/converters/office/workbook.rb +187 -0
  171. data/lib/ruby_llm/file_store/converters/office/zip.rb +149 -0
  172. data/lib/ruby_llm/file_store/converters/office.rb +106 -0
  173. data/lib/ruby_llm/file_store/converters/text.rb +27 -0
  174. data/lib/ruby_llm/file_store/converters.rb +84 -0
  175. data/lib/ruby_llm/file_store/directory.rb +190 -0
  176. data/lib/ruby_llm/file_store/memory.rb +75 -0
  177. data/lib/ruby_llm/file_store/tools/base.rb +99 -0
  178. data/lib/ruby_llm/file_store/tools/delete.rb +48 -0
  179. data/lib/ruby_llm/file_store/tools/edit.rb +155 -0
  180. data/lib/ruby_llm/file_store/tools/list.rb +57 -0
  181. data/lib/ruby_llm/file_store/tools/read.rb +120 -0
  182. data/lib/ruby_llm/file_store/tools/search.rb +208 -0
  183. data/lib/ruby_llm/file_store/tools/write.rb +40 -0
  184. data/lib/ruby_llm/file_store/tools.rb +39 -0
  185. data/lib/ruby_llm/file_store.rb +338 -0
  186. data/lib/ruby_llm/files/saving.rb +14 -0
  187. data/lib/ruby_llm/image.rb +67 -45
  188. data/lib/ruby_llm/input_required_error.rb +28 -0
  189. data/lib/ruby_llm/judge/builder.rb +6 -1
  190. data/lib/ruby_llm/judge.rb +1 -1
  191. data/lib/ruby_llm/judgment.rb +6 -24
  192. data/lib/ruby_llm/mcp/client.rb +67 -28
  193. data/lib/ruby_llm/mcp/collection.rb +39 -2
  194. data/lib/ruby_llm/mcp/content.rb +7 -2
  195. data/lib/ruby_llm/mcp/error.rb +8 -2
  196. data/lib/ruby_llm/mcp/http.rb +117 -37
  197. data/lib/ruby_llm/mcp/input_request.rb +3 -112
  198. data/lib/ruby_llm/mcp/input_required_error.rb +3 -21
  199. data/lib/ruby_llm/mcp/insufficient_scope_error.rb +27 -0
  200. data/lib/ruby_llm/mcp/listener.rb +7 -1
  201. data/lib/ruby_llm/mcp/oauth.rb +102 -39
  202. data/lib/ruby_llm/mcp/outages.rb +172 -0
  203. data/lib/ruby_llm/mcp/resource.rb +9 -6
  204. data/lib/ruby_llm/mcp/resource_template.rb +3 -2
  205. data/lib/ruby_llm/mcp/session_expired.rb +10 -2
  206. data/lib/ruby_llm/mcp/stdio.rb +66 -18
  207. data/lib/ruby_llm/mcp/task.rb +13 -40
  208. data/lib/ruby_llm/mcp/tool.rb +34 -11
  209. data/lib/ruby_llm/mcp.rb +123 -59
  210. data/lib/ruby_llm/message.rb +217 -23
  211. data/lib/ruby_llm/model/modalities.rb +1 -1
  212. data/lib/ruby_llm/model/pricing.rb +26 -4
  213. data/lib/ruby_llm/model/pricing_category.rb +38 -15
  214. data/lib/ruby_llm/model/pricing_tier.rb +3 -2
  215. data/lib/ruby_llm/model.rb +24 -6
  216. data/lib/ruby_llm/models/lookup.rb +37 -4
  217. data/lib/ruby_llm/models/registry.rb +4 -19
  218. data/lib/ruby_llm/models.json +36186 -27832
  219. data/lib/ruby_llm/models.rb +44 -39
  220. data/lib/ruby_llm/moderation.rb +10 -30
  221. data/lib/ruby_llm/ocr.rb +5 -22
  222. data/lib/ruby_llm/open_telemetry/attributes.rb +68 -19
  223. data/lib/ruby_llm/open_telemetry/content.rb +105 -0
  224. data/lib/ruby_llm/open_telemetry.rb +18 -4
  225. data/lib/ruby_llm/prompt/context.rb +2 -2
  226. data/lib/ruby_llm/prompt.rb +7 -5
  227. data/lib/ruby_llm/protocol/binary_streaming.rb +2 -8
  228. data/lib/ruby_llm/protocol/cache_lifetime.rb +22 -0
  229. data/lib/ruby_llm/protocol/native_content.rb +69 -0
  230. data/lib/ruby_llm/protocol/replayed_arguments.rb +28 -0
  231. data/lib/ruby_llm/protocol/stream_accumulator.rb +73 -33
  232. data/lib/ruby_llm/protocol/streaming.rb +105 -22
  233. data/lib/ruby_llm/protocol/tool_namespaces.rb +100 -0
  234. data/lib/ruby_llm/protocol/unsupported_operations.rb +42 -0
  235. data/lib/ruby_llm/protocol.rb +134 -109
  236. data/lib/ruby_llm/protocols/anthropic/batches.rb +41 -4
  237. data/lib/ruby_llm/protocols/anthropic/block_binding.rb +30 -0
  238. data/lib/ruby_llm/protocols/anthropic/chat.rb +135 -60
  239. data/lib/ruby_llm/protocols/anthropic/files.rb +9 -0
  240. data/lib/ruby_llm/protocols/anthropic/media.rb +14 -14
  241. data/lib/ruby_llm/protocols/anthropic/models.rb +16 -10
  242. data/lib/ruby_llm/protocols/anthropic/streaming.rb +43 -22
  243. data/lib/ruby_llm/protocols/anthropic/tools.rb +36 -24
  244. data/lib/ruby_llm/protocols/anthropic.rb +61 -6
  245. data/lib/ruby_llm/protocols/azure/speech/transcription.rb +125 -0
  246. data/lib/ruby_llm/protocols/azure/speech.rb +14 -0
  247. data/lib/ruby_llm/protocols/bedrock/batches.rb +26 -8
  248. data/lib/ruby_llm/protocols/bedrock/event_stream.rb +205 -0
  249. data/lib/ruby_llm/protocols/bedrock/files.rb +15 -6
  250. data/lib/ruby_llm/protocols/bedrock/guardrails.rb +1 -1
  251. data/lib/ruby_llm/protocols/bedrock/rerank.rb +2 -0
  252. data/lib/ruby_llm/protocols/chat_completions/chat.rb +158 -58
  253. data/lib/ruby_llm/protocols/chat_completions/content_filters.rb +45 -0
  254. data/lib/ruby_llm/protocols/chat_completions/embedding_batches.rb +23 -2
  255. data/lib/ruby_llm/protocols/chat_completions/embeddings.rb +26 -3
  256. data/lib/ruby_llm/protocols/chat_completions/images.rb +7 -9
  257. data/lib/ruby_llm/protocols/chat_completions/media.rb +20 -20
  258. data/lib/ruby_llm/protocols/chat_completions/moderation.rb +8 -5
  259. data/lib/ruby_llm/protocols/chat_completions/speech.rb +8 -2
  260. data/lib/ruby_llm/protocols/chat_completions/streaming.rb +38 -17
  261. data/lib/ruby_llm/protocols/chat_completions/think_tags.rb +148 -0
  262. data/lib/ruby_llm/protocols/chat_completions/tools.rb +15 -28
  263. data/lib/ruby_llm/protocols/chat_completions/transcription.rb +17 -2
  264. data/lib/ruby_llm/protocols/chat_completions.rb +4 -0
  265. data/lib/ruby_llm/protocols/cohere/batches.rb +15 -8
  266. data/lib/ruby_llm/protocols/cohere/chat.rb +47 -25
  267. data/lib/ruby_llm/protocols/cohere/datasets.rb +6 -2
  268. data/lib/ruby_llm/protocols/cohere/embeddings.rb +7 -5
  269. data/lib/ruby_llm/protocols/cohere/media.rb +7 -7
  270. data/lib/ruby_llm/protocols/cohere/rerank.rb +2 -1
  271. data/lib/ruby_llm/protocols/cohere/streaming.rb +26 -11
  272. data/lib/ruby_llm/protocols/cohere/tools.rb +17 -28
  273. data/lib/ruby_llm/protocols/cohere/transcription.rb +5 -4
  274. data/lib/ruby_llm/protocols/converse/batches.rb +2 -24
  275. data/lib/ruby_llm/protocols/converse/chat.rb +210 -75
  276. data/lib/ruby_llm/protocols/converse/media.rb +24 -22
  277. data/lib/ruby_llm/protocols/converse/reasoning.rb +23 -9
  278. data/lib/ruby_llm/protocols/converse/streaming.rb +68 -220
  279. data/lib/ruby_llm/protocols/converse.rb +17 -1
  280. data/lib/ruby_llm/protocols/deepgram/speech.rb +1 -2
  281. data/lib/ruby_llm/protocols/deepgram/streaming_transcription.rb +1 -3
  282. data/lib/ruby_llm/protocols/deepgram/transcription.rb +28 -19
  283. data/lib/ruby_llm/protocols/deepgram.rb +3 -2
  284. data/lib/ruby_llm/protocols/deepseek/files.rb +1 -1
  285. data/lib/ruby_llm/protocols/elevenlabs/assets.rb +6 -4
  286. data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +4 -0
  287. data/lib/ruby_llm/protocols/elevenlabs/speech.rb +3 -3
  288. data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +5 -2
  289. data/lib/ruby_llm/protocols/elevenlabs/transcription.rb +6 -3
  290. data/lib/ruby_llm/protocols/files.rb +27 -12
  291. data/lib/ruby_llm/protocols/gemini/caches.rb +4 -4
  292. data/lib/ruby_llm/protocols/gemini/chat.rb +118 -51
  293. data/lib/ruby_llm/protocols/gemini/embedding_batches.rb +9 -5
  294. data/lib/ruby_llm/protocols/gemini/embeddings.rb +3 -3
  295. data/lib/ruby_llm/protocols/gemini/file_transcription.rb +4 -0
  296. data/lib/ruby_llm/protocols/gemini/files.rb +5 -4
  297. data/lib/ruby_llm/protocols/gemini/images.rb +11 -9
  298. data/lib/ruby_llm/protocols/gemini/live_transcription.rb +1 -1
  299. data/lib/ruby_llm/protocols/gemini/long_decode.rb +104 -0
  300. data/lib/ruby_llm/protocols/gemini/media.rb +31 -28
  301. data/lib/ruby_llm/protocols/gemini/retry_info.rb +29 -0
  302. data/lib/ruby_llm/protocols/gemini/schemas.rb +126 -0
  303. data/lib/ruby_llm/protocols/gemini/speech.rb +9 -3
  304. data/lib/ruby_llm/protocols/gemini/streaming.rb +37 -13
  305. data/lib/ruby_llm/protocols/gemini/tools.rb +18 -10
  306. data/lib/ruby_llm/protocols/gemini/transcription.rb +13 -9
  307. data/lib/ruby_llm/protocols/gemini/videos.rb +14 -3
  308. data/lib/ruby_llm/protocols/gemini.rb +16 -0
  309. data/lib/ruby_llm/protocols/gpustack/responses.rb +3 -3
  310. data/lib/ruby_llm/protocols/gpustack/tokenization.rb +14 -0
  311. data/lib/ruby_llm/protocols/interactions/chat.rb +29 -14
  312. data/lib/ruby_llm/protocols/interactions/content.rb +19 -14
  313. data/lib/ruby_llm/protocols/interactions/streaming.rb +1 -1
  314. data/lib/ruby_llm/protocols/interactions/tools.rb +10 -13
  315. data/lib/ruby_llm/protocols/interactions/videos.rb +99 -0
  316. data/lib/ruby_llm/protocols/interactions.rb +1 -0
  317. data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +28 -10
  318. data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +1 -1
  319. data/lib/ruby_llm/protocols/invoke_model/nova_embeddings.rb +45 -11
  320. data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +5 -2
  321. data/lib/ruby_llm/protocols/invoke_model/titan_multimodal_embeddings.rb +11 -3
  322. data/lib/ruby_llm/protocols/invoke_model.rb +21 -2
  323. data/lib/ruby_llm/protocols/meta/transcription/stream.rb +108 -0
  324. data/lib/ruby_llm/protocols/meta/transcription.rb +76 -0
  325. data/lib/ruby_llm/protocols/minimax/files.rb +58 -0
  326. data/lib/ruby_llm/protocols/minimax/images.rb +54 -0
  327. data/lib/ruby_llm/protocols/minimax/speech.rb +53 -0
  328. data/lib/ruby_llm/protocols/minimax/transcription.rb +82 -0
  329. data/lib/ruby_llm/protocols/minimax/videos.rb +93 -0
  330. data/lib/ruby_llm/protocols/minimax.rb +21 -0
  331. data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +44 -21
  332. data/lib/ruby_llm/protocols/mistral/files.rb +6 -4
  333. data/lib/ruby_llm/protocols/mistral/multi_completion.rb +15 -4
  334. data/lib/ruby_llm/protocols/moonshot/files.rb +48 -0
  335. data/lib/ruby_llm/protocols/openai/batches.rb +3 -2
  336. data/lib/ruby_llm/protocols/openai/decisions.rb +6 -4
  337. data/lib/ruby_llm/protocols/openai/files.rb +13 -6
  338. data/lib/ruby_llm/protocols/openrouter/batches.rb +13 -17
  339. data/lib/ruby_llm/protocols/openrouter/responses.rb +6 -10
  340. data/lib/ruby_llm/protocols/perplexity/agent.rb +19 -35
  341. data/lib/ruby_llm/protocols/perplexity/batches.rb +37 -0
  342. data/lib/ruby_llm/protocols/perplexity/files.rb +19 -9
  343. data/lib/ruby_llm/protocols/responses/approvals.rb +12 -11
  344. data/lib/ruby_llm/protocols/responses/chat.rb +165 -75
  345. data/lib/ruby_llm/protocols/responses/citation_markers.rb +167 -0
  346. data/lib/ruby_llm/protocols/responses/compaction.rb +2 -2
  347. data/lib/ruby_llm/protocols/responses/media.rb +17 -9
  348. data/lib/ruby_llm/protocols/responses/streaming.rb +71 -12
  349. data/lib/ruby_llm/protocols/responses/token_counting.rb +1 -1
  350. data/lib/ruby_llm/protocols/responses/tools.rb +48 -1
  351. data/lib/ruby_llm/protocols/responses.rb +21 -0
  352. data/lib/ruby_llm/protocols/system_one/responses.rb +9 -2
  353. data/lib/ruby_llm/protocols/vertexai/files.rb +4 -3
  354. data/lib/ruby_llm/protocols/vertexai/text_to_speech.rb +116 -0
  355. data/lib/ruby_llm/protocols/xai/files.rb +13 -6
  356. data/lib/ruby_llm/protocols/zai/files.rb +37 -0
  357. data/lib/ruby_llm/provider.rb +128 -31
  358. data/lib/ruby_llm/providers/anthropic.rb +5 -1
  359. data/lib/ruby_llm/providers/azure/chat.rb +17 -3
  360. data/lib/ruby_llm/providers/azure/chat_completions.rb +1 -0
  361. data/lib/ruby_llm/providers/azure/decisions.rb +17 -0
  362. data/lib/ruby_llm/providers/azure/embeddings.rb +1 -2
  363. data/lib/ruby_llm/providers/azure/media.rb +6 -6
  364. data/lib/ruby_llm/providers/azure/mistral.rb +17 -0
  365. data/lib/ruby_llm/providers/azure/models.rb +16 -2
  366. data/lib/ruby_llm/providers/azure/moderation.rb +16 -0
  367. data/lib/ruby_llm/providers/azure/responses.rb +9 -0
  368. data/lib/ruby_llm/providers/azure/videos.rb +2 -2
  369. data/lib/ruby_llm/providers/azure.rb +43 -1
  370. data/lib/ruby_llm/providers/bedrock/anthropic/batches.rb +32 -0
  371. data/lib/ruby_llm/providers/bedrock/anthropic.rb +134 -0
  372. data/lib/ruby_llm/providers/bedrock/auth.rb +12 -3
  373. data/lib/ruby_llm/providers/bedrock/connection.rb +19 -0
  374. data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +5 -1
  375. data/lib/ruby_llm/providers/bedrock/mantle/chat_completions.rb +2 -2
  376. data/lib/ruby_llm/providers/bedrock/mantle/responses.rb +12 -2
  377. data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +1 -1
  378. data/lib/ruby_llm/providers/bedrock/mantle.rb +7 -0
  379. data/lib/ruby_llm/providers/bedrock/models.rb +2 -9
  380. data/lib/ruby_llm/providers/bedrock.rb +51 -8
  381. data/lib/ruby_llm/providers/cohere.rb +5 -1
  382. data/lib/ruby_llm/providers/deepgram/chat.rb +25 -0
  383. data/lib/ruby_llm/providers/deepgram.rb +21 -2
  384. data/lib/ruby_llm/providers/deepseek/chat.rb +7 -16
  385. data/lib/ruby_llm/providers/deepseek/responses.rb +25 -8
  386. data/lib/ruby_llm/providers/deepseek.rb +5 -1
  387. data/lib/ruby_llm/providers/elevenlabs.rb +2 -1
  388. data/lib/ruby_llm/providers/gemini.rb +13 -1
  389. data/lib/ruby_llm/providers/gpustack/chat.rb +23 -3
  390. data/lib/ruby_llm/providers/gpustack/embeddings.rb +2 -2
  391. data/lib/ruby_llm/providers/gpustack/media.rb +8 -8
  392. data/lib/ruby_llm/providers/gpustack/transcription.rb +14 -6
  393. data/lib/ruby_llm/providers/gpustack.rb +1 -1
  394. data/lib/ruby_llm/providers/hetzner/chat.rb +23 -6
  395. data/lib/ruby_llm/providers/hetzner.rb +5 -1
  396. data/lib/ruby_llm/providers/meta/responses.rb +81 -0
  397. data/lib/ruby_llm/providers/meta.rb +37 -0
  398. data/lib/ruby_llm/providers/minimax/anthropic.rb +95 -0
  399. data/lib/ruby_llm/providers/minimax/chat_completions.rb +48 -0
  400. data/lib/ruby_llm/providers/minimax/responses.rb +52 -0
  401. data/lib/ruby_llm/providers/minimax.rb +104 -0
  402. data/lib/ruby_llm/providers/mistral/chat.rb +34 -4
  403. data/lib/ruby_llm/providers/mistral/chat_completions/batches.rb +8 -3
  404. data/lib/ruby_llm/providers/mistral/embeddings.rb +1 -12
  405. data/lib/ruby_llm/providers/mistral/input_tokens.rb +35 -0
  406. data/lib/ruby_llm/providers/mistral/media.rb +8 -8
  407. data/lib/ruby_llm/providers/mistral/transcription.rb +8 -6
  408. data/lib/ruby_llm/providers/mistral.rb +13 -1
  409. data/lib/ruby_llm/providers/moonshot/capabilities.rb +16 -0
  410. data/lib/ruby_llm/providers/moonshot/chat.rb +100 -0
  411. data/lib/ruby_llm/providers/moonshot/documents.rb +79 -0
  412. data/lib/ruby_llm/providers/moonshot/responses.rb +55 -0
  413. data/lib/ruby_llm/providers/moonshot.rb +54 -0
  414. data/lib/ruby_llm/providers/ollama/chat.rb +5 -6
  415. data/lib/ruby_llm/providers/ollama/embeddings.rb +41 -0
  416. data/lib/ruby_llm/providers/ollama/media.rb +6 -6
  417. data/lib/ruby_llm/providers/ollama/speech.rb +24 -0
  418. data/lib/ruby_llm/providers/ollama.rb +13 -1
  419. data/lib/ruby_llm/providers/ollama_cloud.rb +22 -1
  420. data/lib/ruby_llm/providers/openai/responses.rb +8 -0
  421. data/lib/ruby_llm/providers/openai.rb +7 -3
  422. data/lib/ruby_llm/providers/openrouter/chat.rb +15 -9
  423. data/lib/ruby_llm/providers/openrouter/embeddings.rb +12 -8
  424. data/lib/ruby_llm/providers/openrouter/images.rb +16 -10
  425. data/lib/ruby_llm/providers/openrouter/judgments.rb +47 -0
  426. data/lib/ruby_llm/providers/openrouter/media.rb +5 -5
  427. data/lib/ruby_llm/providers/openrouter/models.rb +19 -7
  428. data/lib/ruby_llm/providers/openrouter/streaming.rb +4 -4
  429. data/lib/ruby_llm/providers/openrouter/videos.rb +1 -1
  430. data/lib/ruby_llm/providers/openrouter.rb +32 -5
  431. data/lib/ruby_llm/providers/perplexity/chat.rb +3 -3
  432. data/lib/ruby_llm/providers/perplexity/embeddings.rb +1 -1
  433. data/lib/ruby_llm/providers/perplexity/media.rb +6 -6
  434. data/lib/ruby_llm/providers/perplexity.rb +13 -2
  435. data/lib/ruby_llm/providers/typesafe.rb +1 -1
  436. data/lib/ruby_llm/providers/vertexai/anthropic.rb +5 -1
  437. data/lib/ruby_llm/providers/vertexai/credentials.rb +27 -3
  438. data/lib/ruby_llm/providers/vertexai/embed_content.rb +1 -1
  439. data/lib/ruby_llm/providers/vertexai/embeddings.rb +2 -3
  440. data/lib/ruby_llm/providers/vertexai/gemini.rb +1 -1
  441. data/lib/ruby_llm/providers/vertexai/mistral.rb +5 -0
  442. data/lib/ruby_llm/providers/vertexai/models.rb +24 -2
  443. data/lib/ruby_llm/providers/vertexai/transcription.rb +2 -2
  444. data/lib/ruby_llm/providers/vertexai/videos.rb +1 -1
  445. data/lib/ruby_llm/providers/vertexai.rb +56 -9
  446. data/lib/ruby_llm/providers/xai/chat.rb +3 -3
  447. data/lib/ruby_llm/providers/xai/chat_completions/batches.rb +8 -3
  448. data/lib/ruby_llm/providers/xai/images.rb +9 -4
  449. data/lib/ruby_llm/providers/xai/models.rb +2 -2
  450. data/lib/ruby_llm/providers/xai/responses.rb +10 -0
  451. data/lib/ruby_llm/providers/xai/speech.rb +2 -2
  452. data/lib/ruby_llm/providers/xai/transcription.rb +14 -9
  453. data/lib/ruby_llm/providers/xai/videos.rb +1 -1
  454. data/lib/ruby_llm/providers/xai.rb +9 -1
  455. data/lib/ruby_llm/providers/zai/chat.rb +117 -0
  456. data/lib/ruby_llm/providers/zai/ocr.rb +65 -0
  457. data/lib/ruby_llm/providers/zai/token_counting.rb +25 -0
  458. data/lib/ruby_llm/providers/zai/videos.rb +67 -0
  459. data/lib/ruby_llm/providers/zai.rb +66 -0
  460. data/lib/ruby_llm/question.rb +390 -0
  461. data/lib/ruby_llm/queued_message.rb +74 -0
  462. data/lib/ruby_llm/railtie.rb +43 -0
  463. data/lib/ruby_llm/request_shape/part.rb +2 -2
  464. data/lib/ruby_llm/request_shape/problem.rb +2 -2
  465. data/lib/ruby_llm/request_shape/tool_round.rb +2 -2
  466. data/lib/ruby_llm/request_shape/turn.rb +2 -2
  467. data/lib/ruby_llm/request_shape.rb +1 -1
  468. data/lib/ruby_llm/rerank.rb +10 -37
  469. data/lib/ruby_llm/research_job.rb +26 -13
  470. data/lib/ruby_llm/sandbox/landlock.rb +78 -0
  471. data/lib/ruby_llm/sandbox/mxc.rb +137 -0
  472. data/lib/ruby_llm/sandbox.rb +141 -0
  473. data/lib/ruby_llm/search_results.rb +9 -4
  474. data/lib/ruby_llm/server_tool_call.rb +1 -1
  475. data/lib/ruby_llm/skill/loader.rb +131 -0
  476. data/lib/ruby_llm/skill.rb +263 -0
  477. data/lib/ruby_llm/speech.rb +25 -53
  478. data/lib/ruby_llm/stored_file.rb +94 -0
  479. data/lib/ruby_llm/support/cancellation.rb +4 -4
  480. data/lib/ruby_llm/support/deprecated_enumerable.rb +13 -0
  481. data/lib/ruby_llm/support/deprecated_job_predicates.rb +24 -0
  482. data/lib/ruby_llm/support/inspectable.rb +1 -1
  483. data/lib/ruby_llm/support/instrumentation.rb +6 -5
  484. data/lib/ruby_llm/support/rescuable.rb +84 -0
  485. data/lib/ruby_llm/support/utils.rb +26 -3
  486. data/lib/ruby_llm/task.rb +126 -0
  487. data/lib/ruby_llm/test_helper.rb +129 -0
  488. data/lib/ruby_llm/thinking.rb +3 -3
  489. data/lib/ruby_llm/tokens.rb +52 -12
  490. data/lib/ruby_llm/tool.rb +339 -5
  491. data/lib/ruby_llm/tool_call/collection.rb +132 -0
  492. data/lib/ruby_llm/tool_call.rb +68 -6
  493. data/lib/ruby_llm/tools/provider_tools.rb +23 -0
  494. data/lib/ruby_llm/tools/search.rb +118 -0
  495. data/lib/ruby_llm/transcription/segment.rb +67 -0
  496. data/lib/ruby_llm/transcription/wav_audio.rb +2 -1
  497. data/lib/ruby_llm/transcription.rb +25 -49
  498. data/lib/ruby_llm/transcription_chunk.rb +9 -9
  499. data/lib/ruby_llm/transport/connection.rb +146 -33
  500. data/lib/ruby_llm/transport/error_middleware.rb +94 -49
  501. data/lib/ruby_llm/transport/event_stream_parser.rb +18 -4
  502. data/lib/ruby_llm/transport/json_response.rb +9 -3
  503. data/lib/ruby_llm/transport/log_formatter.rb +20 -0
  504. data/lib/ruby_llm/transport/release_request.rb +35 -0
  505. data/lib/ruby_llm/transport/websocket_connection.rb +26 -30
  506. data/lib/ruby_llm/uploaded_file.rb +15 -6
  507. data/lib/ruby_llm/version.rb +1 -1
  508. data/lib/ruby_llm/video.rb +34 -10
  509. data/lib/ruby_llm/video_job.rb +55 -42
  510. data/lib/ruby_llm/web_source.rb +38 -0
  511. data/lib/ruby_llm/workflow.rb +2 -2
  512. data/lib/ruby_llm.rb +63 -25
  513. data/lib/tasks/ruby_llm/evaluations.rb +1 -1
  514. data/skills/rubyllm/SKILL.md +128 -54
  515. data/skills/rubyllm/references/cost-and-tracing.md +8 -0
  516. data/skills/rubyllm/references/files-code-and-context.md +6 -0
  517. data/skills/rubyllm/references/operations.md +8 -0
  518. data/skills/rubyllm/references/rails-chats.md +22 -0
  519. data/skills/rubyllm/references/tools-and-agents.md +11 -0
  520. metadata +219 -14
  521. data/lib/generators/ruby_llm/chat_ui/templates/helpers/messages_helper.rb.tt +0 -25
  522. data/lib/generators/ruby_llm/upgrade/templates/upgrade_ruby_llm_to_2_1.rb.tt +0 -78
@@ -9,14 +9,14 @@ module RubyLLM
9
9
  # configured chats wherever they are needed.
10
10
  #
11
11
  # class SupportAgent < RubyLLM::Agent
12
- # model "gpt-5.6-luna"
12
+ # model "gpt-6-luna"
13
13
  # instructions "You are a concise support assistant."
14
14
  # tools SearchDocs, LookupAccount
15
15
  # end
16
16
  #
17
17
  # SupportAgent.new.ask "How do I reset my API key?"
18
18
  #
19
- # ::chat returns a configured Chat. When ::chat_model names an ActiveRecord
19
+ # ::chat returns a configured Chat. When ::chat_class names an ActiveRecord
20
20
  # chat class, ::create, ::create!, and ::find return configured records of
21
21
  # that class instead.
22
22
  #
@@ -37,11 +37,14 @@ module RubyLLM
37
37
  # Agent instances delegate Chat's conversation API (#ask, #complete,
38
38
  # #with_tools, and so on) to the wrapped chat, which is available via
39
39
  # #chat. Direct transcript replacement stays on the wrapped chat because
40
- # Rails-backed chat models own their message association. Agents are
41
- # enumerable over their messages.
40
+ # Rails-backed chat models own their message association. Read the
41
+ # conversation through #messages.
42
42
  class Agent
43
43
  extend Forwardable
44
- include Enumerable
44
+ extend ModelOptions
45
+ singleton_class.prepend JoinCallbacks, Ownership, Callbacks, ToolConversion, ContextBudget
46
+ include Support::DeprecatedEnumerable
47
+ include Support::Rescuable
45
48
 
46
49
  DUPED_INHERITED_CONFIG = {
47
50
  :@chat_kwargs => {},
@@ -55,8 +58,7 @@ module RubyLLM
55
58
  :@headers => {},
56
59
  :@input_names => [],
57
60
  :@fallbacks => [],
58
- :@fallback_options => {},
59
- :@rescue_handlers => []
61
+ :@fallback_options => {}
60
62
  }.freeze
61
63
  # Simple value options: a class-level getter/setter macro whose value the
62
64
  # agent forwards to the matching Chat#with_* when it builds its chat.
@@ -65,42 +67,43 @@ module RubyLLM
65
67
  UNRESOLVED_CONTEXT = Object.new.freeze
66
68
  private_constant :UNRESOLVED_CONTEXT
67
69
 
68
- # The chat operations an agent instance runs through its ::rescue_from
69
- # handlers. Remaining delegated methods pass through untouched.
70
- GUARDED_OPERATIONS = %i[ask say ask_later complete generate run_tools step count_tokens compact].freeze
71
-
72
70
  # Chat methods that return the wrapped chat so calls can be chained there.
73
71
  CHAINABLE_CHAT_DELEGATES = %i[
74
72
  with_instructions with_tools with_mcp with_provider_tools with_tool_options with_model
75
- with_temperature with_max_output_tokens with_thinking with_citations
76
- with_end_user with_compaction with_caching with_context with_provider_options
77
- with_headers with_schema with_fallbacks
78
- before_request before_message after_message before_tool_call after_tool_result after_tool_progress
79
- before_fallback after_fallback
73
+ with_temperature with_max_output_tokens with_thinking with_citations with_context_budget
74
+ with_end_user with_owner with_compaction with_caching with_code_execution with_skills with_file_store with_context
75
+ with_provider_options with_headers with_schema with_fallbacks
76
+ before_request before_message after_message before_tool_call around_tool_call after_tool_result
77
+ after_tool_progress before_fallback after_fallback around_join after_file_write
80
78
  convert_unsupported_attachments
81
- cancel approve deny answer decline cache_until_here
79
+ cancel approve answer decline cache_until_here
82
80
  ].freeze
83
81
 
84
82
  # Chat values and operations whose return values pass through unchanged.
85
83
  PASSTHROUGH_CHAT_DELEGATES = %i[
86
- model provider messages tools deferred_tools mcp provider_tools tool_options provider_options headers schema
87
- concurrency caching citations compaction context end_user fallbacks thinking temperature max_output_tokens
88
- each complete? cancelled? waiting? awaiting_approval? pending_approvals awaiting_input? pending_inputs
89
- awaiting_tasks? pending_tasks add_message add_completion tokens cost render
84
+ model model_id provider messages tools deferred_tools mcp provider_tools tool_options provider_options headers
85
+ schema concurrency caching citations code_execution compaction context_budget context end_user fallbacks thinking
86
+ temperature max_output_tokens skills file_store
87
+ each complete? cancelled? waiting? pending_questions pending_tasks add_message add_completion tokens cost render
88
+ queue queued_messages withdraw join_queued_messages
90
89
  ].freeze
91
90
 
92
91
  COPIED_INHERITED_CONFIG = (%i[
93
92
  @thinking
94
93
  @citations
94
+ @code_execution
95
+ @skills
96
+ @file_store
95
97
  @end_user
96
98
  @schema
97
99
  @context
98
- @chat_model
100
+ @context_budget
101
+ @chat_class
99
102
  @tools_defer
100
103
  @mcp_defer
101
104
  ] + PASSTHROUGH_OPTIONS.map { |option| :"@#{option}" }).freeze
102
105
  private_constant :DUPED_INHERITED_CONFIG, :COPIED_INHERITED_CONFIG,
103
- :PASSTHROUGH_OPTIONS, :THINKING_OPTIONS, :GUARDED_OPERATIONS,
106
+ :PASSTHROUGH_OPTIONS, :THINKING_OPTIONS,
104
107
  :CHAINABLE_CHAT_DELEGATES, :PASSTHROUGH_CHAT_DELEGATES
105
108
 
106
109
  class << self
@@ -116,9 +119,17 @@ module RubyLLM
116
119
  # available as methods. Called with no arguments, returns the
117
120
  # configured chat keywords.
118
121
  #
119
- # model "gpt-5.6-luna"
120
- # model "gpt-5.6", provider: :openai, protocol: :responses
121
- # model { quality == :high ? "gpt-5.6" : "gpt-5.6-luna" }
122
+ # model "gpt-6-luna"
123
+ # model "gpt-6.1-sol", provider: :openai, protocol: :responses
124
+ # model { quality == :high ? "gpt-6.1-sol" : "gpt-6-luna" }
125
+ #
126
+ # A +model:+ passed to ::chat, ::new, ::create, or ::create! replaces
127
+ # the declared model and its options, so the caller's model resolves
128
+ # on its own provider unless the caller also passes +provider:+. Options
129
+ # declared without a model stay. Later, ::find, ::new with +chat:+, and
130
+ # ::sync_instructions apply the options only to a record on the declared
131
+ # model, and always when a block declares it, since the block's choice
132
+ # is unknown until it runs.
122
133
  #
123
134
  # The block runs before the chat exists, so it can read inputs but not
124
135
  # +chat+.
@@ -138,12 +149,19 @@ module RubyLLM
138
149
  # tools SearchDocs, LookupAccount
139
150
  # tools { [TodoTool.new(chat: chat)] }
140
151
  #
141
- # Pass +defer:+ as Chat#with_tools takes it:
152
+ # Pass +defer:+ as Chat#with_tools takes it, or a lambda that decides
153
+ # when the chat is built:
142
154
  #
143
155
  # tools SearchDocs, LookupAccount, defer: true
156
+ # tools(defer: -> { many_tools? }) { connected_tools }
157
+ #
158
+ # With +defer:+ alone it applies to the tools a parent agent declared.
144
159
  #
145
160
  def tools(*tools, defer: nil, &block)
146
- return @tools || [] if tools.empty? && !block_given?
161
+ if tools.empty? && !block_given?
162
+ @tools_defer = defer unless defer.nil?
163
+ return @tools || []
164
+ end
147
165
 
148
166
  @tools_defer = defer
149
167
  @tools = block_given? ? block : tools.flatten
@@ -157,20 +175,26 @@ module RubyLLM
157
175
  # mcp Files
158
176
  # mcp { [Linear.new(user: user), Files] }
159
177
  #
160
- # Pass +defer:+ as Chat#with_mcp takes it:
178
+ # Pass +defer:+ as Chat#with_mcp takes it, or a lambda that decides
179
+ # when the chat is built:
161
180
  #
162
181
  # mcp GitHub, defer: true
163
182
  #
183
+ # With +defer:+ alone it applies to the servers a parent agent declared.
184
+ #
164
185
  def mcp(*servers, defer: nil, &block)
165
- return @mcp || [] if servers.empty? && !block_given?
186
+ if servers.empty? && !block_given?
187
+ @mcp_defer = defer unless defer.nil?
188
+ return @mcp || []
189
+ end
166
190
 
167
191
  @mcp_defer = defer
168
192
  @mcp = block_given? ? block : servers.flatten
169
193
  end
170
194
 
171
195
  # Sets how chats this agent builds use their tools, applied via
172
- # Chat#with_tool_options. Accepts +choice:+, +calls:+, and
173
- # +concurrency:+. A block defers evaluation until the chat is built.
196
+ # Chat#with_tool_options. Accepts +choice:+, +calls:+, +concurrency:+,
197
+ # and +rounds:+. A block defers evaluation until the chat is built.
174
198
  # Called with no arguments, returns the configured options.
175
199
  #
176
200
  # tool_options choice: :required, calls: :one
@@ -183,11 +207,13 @@ module RubyLLM
183
207
 
184
208
  # Enables provider-executed tools for chats this agent builds, applied
185
209
  # via Chat#with_provider_tools. Accepts the same aliases, options, and
186
- # raw Hashes; a block defers evaluation until the chat is built.
210
+ # raw Hashes; a block defers evaluation until the chat is built and
211
+ # returns a list, or a Hash of aliases and options like the keywords.
187
212
  # Called with no arguments, returns the declared entries.
188
213
  #
189
214
  # provider_tools :web_search
190
215
  # provider_tools web_search: { allowed_domains: ["ruby-lang.org"] }
216
+ # provider_tools { { web_search: { max_uses: max_searches } } }
191
217
  #
192
218
  def provider_tools(*tools, **tools_with_options, &block)
193
219
  return @provider_tools || [] if tools.empty? && tools_with_options.empty? && !block_given?
@@ -286,6 +312,89 @@ module RubyLLM
286
312
  @compaction = options
287
313
  end
288
314
 
315
+ # Keeps tool results within shares of the model's context window in
316
+ # chats this agent builds, applied via Chat#with_context_budget, in
317
+ # place of Configuration#context_budget. Pass +nil+ for no budget.
318
+ #
319
+ # context_budget per_result: 0.25, per_request: 0.7
320
+ # context_budget window: 128_000, per_request: 0.7
321
+ # context_budget nil
322
+ #
323
+ # A block is evaluated for each chat, with the agent's inputs and
324
+ # +chat+ in scope, and returns the options, or +nil+ for no budget.
325
+ #
326
+ # context_budget { { window: model_window, per_request: 0.7 } if model_window }
327
+ #
328
+ def context_budget(budget = {}, **, &) = (@context_budget = context_budget_option(budget, **, &))
329
+
330
+ # Lets chats this agent builds run code in the configured sandbox,
331
+ # applied via Chat#with_code_execution. Accepts the same options as
332
+ # keywords or a Hash. Pass +false+ to disable it. Passing +nil+ or a
333
+ # block raises ArgumentError.
334
+ #
335
+ # code_execution
336
+ # code_execution false
337
+ # code_execution languages: [:ruby, :python], gems: %w[prawn]
338
+ # code_execution sandbox: ReadOnlySandbox, escalate_to: [WorkspaceSandbox]
339
+ # code_execution mounts: -> { { "templates" => chat.templates_dir } }
340
+ #
341
+ # A +mounts+ lambda runs before each run, with +chat+ and the
342
+ # agent's inputs in scope, like the agent's other lambdas.
343
+ def code_execution(enabled = true, **options) # rubocop:disable Style/OptionalBooleanParameter
344
+ raise ArgumentError, 'code_execution takes no block. Pass a lambda as mounts: for each run' if block_given?
345
+
346
+ @code_execution = CodeExecution.options(enabled, options)
347
+ end
348
+
349
+ # Attaches a file store to chats this agent builds, with the tools to
350
+ # work with it, applied via Chat#with_file_store, which takes the
351
+ # same arguments. A +store+ lambda runs once for each chat, with
352
+ # +chat+ and the agent's inputs in scope, like the agent's other
353
+ # lambdas. Pass +nil+ to remove the store a parent agent declared. A
354
+ # block raises ArgumentError: pass the lambda as the store instead.
355
+ #
356
+ # file_store
357
+ # file_store inline_limit: 20_000, tools: :read
358
+ # file_store :memory
359
+ # file_store -> { "tmp/chats/#{chat.id}" }
360
+ # file_store nil
361
+ #
362
+ # Each chat gets a store of its own: without +store+, a new
363
+ # FileStore::Memory, or for a persisted chat, its files in Active
364
+ # Storage, kept for the chat's owner (see ::owner). A folder path on
365
+ # its own is shared by every chat, so use a lambda to give each chat
366
+ # its own folder.
367
+ #
368
+ # :call-seq: file_store(store = default, inline_limit: 10_000, tools: %i[read write edit])
369
+ def file_store(store = Chat::FileReferences::DEFAULT_STORE, inline_limit: Chat::FileReferences::INLINE_LIMIT,
370
+ tools: FileStore::Tools::DEFAULT)
371
+ raise ArgumentError, 'file_store takes no block. Pass the store as a lambda instead' if block_given?
372
+ return @file_store = false if store.nil?
373
+
374
+ Chat::FileReferences.validate(store, inline_limit:, tools:, store_lambda: true)
375
+ @file_store = { store:, inline_limit:, tools: }.freeze
376
+ end
377
+
378
+ # Gives chats this agent builds the skills named, applied via
379
+ # Chat#with_skills when the chat is built. Symbols name folders under
380
+ # Skill.roots, Strings and Pathnames are paths to skill folders,
381
+ # +true+ is every skill under the roots, and +false+ removes the
382
+ # skills a parent agent declared. An agent without a +skills+
383
+ # declaration has no skills. Called with no arguments, returns the
384
+ # declaration: the names, +true+, +false+, or +nil+. A block raises
385
+ # ArgumentError.
386
+ #
387
+ # skills :invoices, :refunds
388
+ # skills true
389
+ # skills false
390
+ #
391
+ def skills(*skills)
392
+ raise ArgumentError, 'skills takes no block. Name the skills, or pass true for every skill' if block_given?
393
+ return @skills if skills.empty?
394
+
395
+ @skills = Skill.selection(skills)
396
+ end
397
+
289
398
  # Sets the safety identifier for chats this agent builds, applied via
290
399
  # Chat#with_end_user. A block defers evaluation until the
291
400
  # chat is built, so the id can come from the agent's inputs. Called
@@ -294,11 +403,21 @@ module RubyLLM
294
403
  # end_user "tenant-42"
295
404
  # end_user { workspace.public_id }
296
405
  #
297
- def end_user(value = nil, &block)
298
- return @end_user if value.nil? && !block_given?
299
-
300
- @end_user = block || value
301
- end
406
+ def end_user(value = nil, &block) = value.nil? && !block ? @end_user : (@end_user = block || value)
407
+
408
+ # Sets whoever the usage of chats this agent builds or finds is
409
+ # attributed to, applied via Chat#with_owner. A block defers
410
+ # evaluation until the chat is built, so the owner can come from the
411
+ # agent's inputs or, for a persisted chat, from +chat+. A persisted
412
+ # chat keeps the files of its Active Storage store for the owner
413
+ # too, and the owner wins over the association its
414
+ # <tt>acts_as_chat owner:</tt> names. Subclasses inherit it. Called
415
+ # with no arguments, returns the configured value.
416
+ #
417
+ # owner { account }
418
+ # owner { chat.user }
419
+ #
420
+ def owner(value = nil, &block) = value.nil? && !block ? @owner : (@owner = block || value)
302
421
 
303
422
  # Enables citations for chats this agent builds, applied via
304
423
  # Chat#with_citations. Pass +false+ to disable them.
@@ -320,13 +439,18 @@ module RubyLLM
320
439
  #
321
440
  # caching
322
441
  # caching false
442
+ # caching cache
323
443
  # caching ttl: "1h"
324
444
  # caching { { ttl: workspace.cache_ttl } }
325
445
  #
326
446
  def caching(enabled = true, **options, &block) # rubocop:disable Metrics/PerceivedComplexity, Style/OptionalBooleanParameter
327
447
  return caching(**enabled.transform_keys(&:to_sym), **options, &block) if enabled.is_a?(Hash)
448
+ return caching(id: enabled, **options, &block) if enabled.is_a?(CachedContent)
449
+
450
+ unless [true, false].include?(enabled)
451
+ raise ArgumentError, 'caching accepts false, a CachedContent, or caching options'
452
+ end
328
453
 
329
- raise ArgumentError, 'caching accepts false or caching options' unless [true, false].include?(enabled)
330
454
  raise ArgumentError, 'caching accepts options or a block, not both' if options.any? && block
331
455
  raise ArgumentError, 'caching false does not accept options or a block' if !enabled && (options.any? || block)
332
456
 
@@ -375,17 +499,19 @@ module RubyLLM
375
499
 
376
500
  # Sets fallback models for chats this agent builds, applied via
377
501
  # Chat#with_fallbacks. Called with no arguments, returns the
378
- # configured models.
502
+ # configured models. Keep the default +on:+: providers signal
503
+ # overload as HTTP 529 or 503, and the default covers both.
379
504
  #
380
505
  # fallbacks "gpt-4.1-mini", "claude-haiku-4-5"
381
- # fallbacks "gpt-4.1-mini", on: [RubyLLM::RateLimitError]
506
+ # fallbacks "gpt-4.1-mini", on: [*RubyLLM::Fallback::DEFAULT_ERRORS, RubyLLM::ContextLengthExceededError]
507
+ # fallbacks drop_unsupported: true
382
508
  #
383
- def fallbacks(*models, **options)
384
- return @fallbacks || [] if models.empty? && options.empty?
385
- raise ArgumentError, 'To set fallback options, provide at least one fallback model' if models.empty?
509
+ def fallbacks(*models, on: Fallback::DEFAULT_ERRORS, drop_unsupported: false)
510
+ return @fallbacks || [] if (bare = models.empty? && !drop_unsupported) && on.equal?(Fallback::DEFAULT_ERRORS)
511
+ raise ArgumentError, 'To set fallback options, provide at least one fallback model' if bare
386
512
 
387
513
  @fallbacks = models.flatten.compact
388
- @fallback_options = options
514
+ @fallback_options = { on:, drop_unsupported: }
389
515
  end
390
516
 
391
517
  def fallback_options
@@ -425,13 +551,18 @@ module RubyLLM
425
551
  # ::sync_instructions). Accepts the class or its name as a string.
426
552
  # Called with no argument, returns the configured value.
427
553
  #
428
- # chat_model Chat
554
+ # chat_class Chat
429
555
  #
430
- def chat_model(value = nil)
431
- return @chat_model if value.nil?
556
+ def chat_class(value = nil)
557
+ return @chat_class if value.nil?
558
+
559
+ @chat_class = value
560
+ remove_instance_variable(:@resolved_chat_class) if instance_variable_defined?(:@resolved_chat_class)
561
+ end
432
562
 
433
- @chat_model = value
434
- remove_instance_variable(:@resolved_chat_model) if instance_variable_defined?(:@resolved_chat_model)
563
+ def chat_model(value = nil) # :nodoc:
564
+ RubyLLM.deprecator.deprecate('Agent.chat_model', replacement: 'Agent.chat_class', removal: '3.0')
565
+ chat_class(value)
435
566
  end
436
567
 
437
568
  # Declares named runtime inputs. Matching keyword arguments passed to
@@ -447,57 +578,6 @@ module RubyLLM
447
578
  @input_names = names.flatten.map(&:to_sym)
448
579
  end
449
580
 
450
- # Registers a handler for exceptions raised by the chat operations of
451
- # this agent's instances: #ask, #say, #ask_later, #complete,
452
- # #generate, #run_tools, #step, #count_tokens, and #compact. Name the
453
- # handler with +with:+ or pass a block; either runs on the agent instance,
454
- # so the agent's #chat, inputs, and class name are available for instrumentation.
455
- #
456
- # class ApplicationAgent < RubyLLM::Agent
457
- # rescue_from RubyLLM::RateLimitError, Faraday::TimeoutError, with: :handle_transient
458
- # rescue_from RubyLLM::BadRequestError do |error|
459
- # error_tracker.notify(error)
460
- # raise
461
- # end
462
- #
463
- # private
464
- #
465
- # def handle_transient(error)
466
- # metrics.increment("llm.api_error", type: "transient")
467
- # raise
468
- # end
469
- # end
470
- #
471
- # Handlers are searched in reverse declaration order, so the last
472
- # matching one wins. Re-raise inside a handler to let the caller see
473
- # the exception; otherwise the handler's return value becomes the
474
- # operation's return value. Exceptions no handler matches are
475
- # re-raised. Subclasses inherit the handlers declared when they are
476
- # defined.
477
- #
478
- # Exception classes may be named as Strings, which defers constant
479
- # lookup until an exception is raised.
480
- def rescue_from(*exception_classes, with: nil, &block)
481
- raise ArgumentError, 'rescue_from needs a handler: pass with: or a block' unless with || block
482
- raise ArgumentError, 'rescue_from takes with: or a block, not both' if with && block
483
-
484
- exception_classes.flatten.each do |exception_class|
485
- rescue_handlers << [rescue_handler_key(exception_class), with || block]
486
- end
487
- end
488
-
489
- def rescue_handlers # :nodoc:
490
- @rescue_handlers ||= []
491
- end
492
-
493
- def rescue_handler_for(exception) # :nodoc:
494
- rescue_handlers.reverse_each do |class_name, handler|
495
- exception_class = rescue_handler_class(class_name)
496
- return handler if exception_class && exception.is_a?(exception_class)
497
- end
498
- nil
499
- end
500
-
501
581
  def chat_kwargs # :nodoc:
502
582
  @chat_kwargs || {}
503
583
  end
@@ -511,16 +591,9 @@ module RubyLLM
511
591
  evaluate(value, runtime_context(chat: nil, inputs:))
512
592
  end
513
593
 
514
- def resolved_chat_kwargs(inputs: {}) # :nodoc:
515
- kwargs = chat_kwargs
516
- return kwargs unless kwargs[:model].is_a?(Proc)
517
-
518
- kwargs.merge(model: evaluate(kwargs[:model], runtime_context(chat: nil, inputs: inputs)))
519
- end
520
-
521
594
  def build_chat(inputs:, options:, resolved_context: UNRESOLVED_CONTEXT) # :nodoc:
522
595
  context_value = resolved_context.equal?(UNRESOLVED_CONTEXT) ? self.resolved_context(inputs:) : resolved_context
523
- (context_value || RubyLLM).chat(**resolved_chat_kwargs(inputs:), **options)
596
+ (context_value || RubyLLM).chat(**resolved_chat_kwargs(inputs:, options:))
524
597
  end
525
598
 
526
599
  # Builds a Chat configured with this agent's declarations and returns
@@ -538,13 +611,13 @@ module RubyLLM
538
611
  chat
539
612
  end
540
613
 
541
- # Creates a ::chat_model record, applies this agent's configuration to
614
+ # Creates a ::chat_class record, applies this agent's configuration to
542
615
  # it, and returns it. Keywords matching declared ::inputs become
543
616
  # runtime inputs; the rest are forwarded to the model's +create+.
544
617
  #
545
618
  # chat = WorkAssistant.create(user: current_user)
546
619
  #
547
- # Raises ArgumentError if ::chat_model is not configured.
620
+ # Raises ArgumentError if ::chat_class is not configured.
548
621
  def create(**)
549
622
  with_rails_chat_record(:create, **)
550
623
  end
@@ -558,38 +631,37 @@ module RubyLLM
558
631
  with_rails_chat_record(:create!, **)
559
632
  end
560
633
 
561
- # Finds the ::chat_model record with +id+ and applies this agent's
634
+ # Finds the ::chat_class record with +id+ and applies this agent's
562
635
  # configuration at runtime, without persisting instructions. Returns
563
636
  # the record.
564
637
  #
565
638
  # chat = WorkAssistant.find(params[:id])
566
639
  #
567
- # Raises ArgumentError if ::chat_model is not configured.
640
+ # Raises ArgumentError if ::chat_class is not configured.
568
641
  def find(id, **kwargs)
569
- raise ArgumentError, 'chat_model must be configured to use find' unless resolved_chat_model
642
+ raise ArgumentError, 'chat_class must be configured to use find' unless resolved_chat_class
570
643
 
571
644
  input_values, = partition_inputs(kwargs)
572
- record = resolved_chat_model.find(id)
645
+ record = resolved_chat_class.find(id)
573
646
  apply_configuration(record, input_values:, persist_instructions: false)
574
647
 
575
648
  record
576
649
  end
577
650
 
578
651
  # Re-renders this agent's instructions and persists them on the given
579
- # ::chat_model record (or the record found by that id). Keywords
652
+ # ::chat_class record (or the record found by that id). Keywords
580
653
  # matching declared ::inputs become runtime inputs. Returns the
581
654
  # record.
582
655
  #
583
656
  # WorkAssistant.sync_instructions(chat)
584
657
  #
585
- # Raises ArgumentError if ::chat_model is not configured.
658
+ # Raises ArgumentError if ::chat_class is not configured.
586
659
  def sync_instructions(chat_or_id, **kwargs)
587
- raise ArgumentError, 'chat_model must be configured to use sync_instructions' unless resolved_chat_model
660
+ raise ArgumentError, 'chat_class must be configured to use sync_instructions' unless resolved_chat_class
588
661
 
589
662
  input_values, = partition_inputs(kwargs)
590
- record = chat_or_id.is_a?(resolved_chat_model) ? chat_or_id : resolved_chat_model.find(chat_or_id)
591
- apply_assume_model_exists(record)
592
- apply_protocol(record)
663
+ record = chat_or_id.is_a?(resolved_chat_class) ? chat_or_id : resolved_chat_class.find(chat_or_id)
664
+ apply_chat_options(record)
593
665
  runtime = runtime_context(chat: record, inputs: input_values)
594
666
  apply_context(record, runtime)
595
667
  apply_instructions(
@@ -627,10 +699,12 @@ module RubyLLM
627
699
  chat,
628
700
  input_values:,
629
701
  persist_instructions:,
630
- resolved_context: UNRESOLVED_CONTEXT
702
+ resolved_context: UNRESOLVED_CONTEXT,
703
+ agent: nil,
704
+ chat_options: {}
631
705
  )
632
706
  runtime = runtime_context(chat:, inputs: input_values)
633
- apply_chat_options(chat)
707
+ apply_chat_options(chat, chat_options)
634
708
  apply_context(chat, runtime, resolved_context:)
635
709
  apply_instructions(chat, runtime, inputs: input_values, persist: persist_instructions)
636
710
  apply_tools(chat, runtime)
@@ -640,10 +714,17 @@ module RubyLLM
640
714
  apply_end_user(chat, runtime)
641
715
  apply_caching(chat, runtime)
642
716
  apply_compaction(chat)
717
+ apply_code_execution(chat, runtime)
718
+ apply_skills(chat)
719
+ apply_file_store(chat, runtime)
720
+ apply_tool_options(chat, runtime)
643
721
  apply_provider_options(chat, runtime)
644
722
  apply_headers(chat, runtime)
645
723
  apply_schema(chat, runtime)
646
724
  apply_fallbacks(chat)
725
+ apply_rescue_handlers(chat, agent, input_values)
726
+ target = chat.respond_to?(:to_llm) ? chat.to_llm : chat
727
+ target.agent_class = self if target.respond_to?(:agent_class=)
647
728
  end
648
729
 
649
730
  private
@@ -666,28 +747,12 @@ module RubyLLM
666
747
  raise ArgumentError, "thinking accepts #{THINKING_OPTIONS.join(', ')}, got #{unsupported.join(', ')}"
667
748
  end
668
749
 
669
- def rescue_handler_key(exception_class)
670
- case exception_class
671
- when Module then exception_class.name || exception_class
672
- when String then exception_class
673
- else raise ArgumentError, "#{exception_class.inspect} is not an exception class or its name"
674
- end
675
- end
676
-
677
- def rescue_handler_class(class_name)
678
- return class_name if class_name.is_a?(Module)
679
-
680
- Object.const_get(class_name)
681
- rescue NameError
682
- nil
683
- end
684
-
685
750
  def copy_inherited_config_to(subclass)
686
751
  subclass.instance_variable_set(:@inherited_instruction_declarations, instruction_declarations.dup)
687
752
 
688
753
  DUPED_INHERITED_CONFIG.each do |ivar, default|
689
754
  value = instance_variable_defined?(ivar) ? instance_variable_get(ivar) : default
690
- subclass.instance_variable_set(ivar, value.respond_to?(:dup) ? value.dup : value)
755
+ subclass.instance_variable_set(ivar, value.dup)
691
756
  end
692
757
 
693
758
  COPIED_INHERITED_CONFIG.each do |ivar|
@@ -696,13 +761,13 @@ module RubyLLM
696
761
  end
697
762
 
698
763
  def with_rails_chat_record(method_name, **kwargs)
699
- raise ArgumentError, 'chat_model must be configured to use create/create!' unless resolved_chat_model
764
+ raise ArgumentError, 'chat_class must be configured to use create/create!' unless resolved_chat_class
700
765
 
701
766
  input_values, chat_options = partition_inputs(kwargs)
702
- record = resolved_chat_model.public_send(
703
- method_name, **resolved_chat_kwargs(inputs: input_values), **chat_options
767
+ record = resolved_chat_class.public_send(
768
+ method_name, **resolved_chat_kwargs(inputs: input_values, options: chat_options)
704
769
  )
705
- apply_configuration(record, input_values:, persist_instructions: true) if record
770
+ apply_configuration(record, input_values:, persist_instructions: true, chat_options:) if record&.persisted?
706
771
  record
707
772
  end
708
773
 
@@ -712,7 +777,13 @@ module RubyLLM
712
777
  end
713
778
 
714
779
  def apply_instructions(chat, runtime, inputs:, persist:, persistent_only: false)
715
- instructions_config.each do |declaration|
780
+ instructions = resolved_instructions(chat, runtime, inputs:, persist:, persistent_only:)
781
+ instructions.reject! { |_, options| options[:persist] } if instructions_persisted?(chat, instructions)
782
+ instructions.each { |value, options| chat.with_instructions(value, **options) }
783
+ end
784
+
785
+ def resolved_instructions(chat, runtime, inputs:, persist:, persistent_only:)
786
+ instructions_config.filter_map do |declaration|
716
787
  next if persistent_only && !declaration[:persist]
717
788
 
718
789
  value = resolved_instruction_value(declaration, chat, runtime, inputs:)
@@ -723,10 +794,22 @@ module RubyLLM
723
794
  cache_until_here: declaration[:cache_until_here]
724
795
  }
725
796
  options[:persist] = persist && declaration[:persist] if rails_chat_record?(chat)
726
- chat.with_instructions(value, **options)
797
+ [value, options]
727
798
  end
728
799
  end
729
800
 
801
+ # Applying an agent to a record on every request would otherwise
802
+ # rewrite the same system rows each time, behind the conversation.
803
+ def instructions_persisted?(chat, instructions)
804
+ persistent = instructions.select { |_, options| options[:persist] }
805
+ replacement = persistent.rindex { |_, options| !options[:append] }
806
+ return false unless replacement
807
+
808
+ chat.system_instructions_persisted?(
809
+ persistent.drop(replacement).map { |value, options| [value, options[:cache_until_here]] }
810
+ )
811
+ end
812
+
730
813
  # An empty prompt file, such as the one the agent generator writes,
731
814
  # means no instructions rather than an empty system message.
732
815
  def blank_instruction?(value)
@@ -735,16 +818,18 @@ module RubyLLM
735
818
 
736
819
  def apply_tools(chat, runtime)
737
820
  tools_to_apply = Array(evaluate(tools, runtime)).compact
738
- chat.with_tools(*tools_to_apply, defer: @tools_defer) if tools_to_apply.any?
821
+ chat.with_tools(*tools_to_apply, defer: evaluate(@tools_defer, runtime)) if tools_to_apply.any?
739
822
 
740
823
  servers = Array(evaluate(mcp, runtime)).compact
741
- chat.with_mcp(*servers, defer: @mcp_defer) if servers.any?
824
+ chat.with_mcp(*servers, defer: evaluate(@mcp_defer, runtime)) if servers.any?
825
+
826
+ provider_tools_to_apply = RubyLLM::Tools::ProviderTools.entries(evaluate(provider_tools, runtime))
827
+ chat.with_provider_tools(*provider_tools_to_apply) if provider_tools_to_apply.any?
828
+ end
742
829
 
830
+ def apply_tool_options(chat, runtime)
743
831
  options = evaluate(tool_options, runtime)
744
832
  chat.with_tool_options(**options) if options && !options.empty?
745
-
746
- provider_tools_to_apply = Array(evaluate(provider_tools, runtime)).compact
747
- chat.with_provider_tools(*provider_tools_to_apply) if provider_tools_to_apply.any?
748
833
  end
749
834
 
750
835
  def apply_passthrough_options(chat)
@@ -782,6 +867,33 @@ module RubyLLM
782
867
  @compaction == false ? chat.with_compaction(false) : chat.with_compaction(**@compaction)
783
868
  end
784
869
 
870
+ def apply_code_execution(chat, runtime)
871
+ return if @code_execution.nil?
872
+ return chat.with_code_execution(false) if @code_execution == false
873
+
874
+ options = @code_execution
875
+ mounts = options[:mounts]
876
+ options = options.merge(mounts: -> { evaluate(mounts, runtime) }) if mounts.is_a?(Proc)
877
+ chat.with_code_execution(**options)
878
+ end
879
+
880
+ def apply_file_store(chat, runtime)
881
+ return if @file_store.nil?
882
+ return chat.with_file_store(nil) if @file_store == false
883
+
884
+ store = @file_store[:store]
885
+ options = @file_store.except(:store).transform_values { |value| evaluate(value, runtime) }
886
+ return chat.with_file_store(**options) if store.equal?(Chat::FileReferences::DEFAULT_STORE)
887
+
888
+ chat.with_file_store(evaluate(store, runtime), **options)
889
+ end
890
+
891
+ def apply_skills(chat)
892
+ return if @skills.nil?
893
+
894
+ chat.with_skills(*Array(@skills))
895
+ end
896
+
785
897
  def apply_provider_options(chat, runtime)
786
898
  value = evaluate(provider_options, runtime)
787
899
  chat.with_provider_options(**value) if value && !value.empty?
@@ -798,7 +910,13 @@ module RubyLLM
798
910
  end
799
911
 
800
912
  def apply_fallbacks(chat)
801
- chat.with_fallbacks(*fallbacks, **fallback_options) if fallbacks.any?
913
+ chat.with_fallbacks(*fallbacks, **fallback_options) if fallbacks.any? || fallback_options[:drop_unsupported]
914
+ end
915
+
916
+ def apply_rescue_handlers(chat, agent, input_values)
917
+ return unless chat.is_a?(Chat::Rescuable)
918
+
919
+ chat.rescuer = rescue_handlers? ? agent || allocate.bind(chat, input_values) : nil
802
920
  end
803
921
 
804
922
  def resolved_schema_value(runtime)
@@ -809,27 +927,6 @@ module RubyLLM
809
927
  Schematist::Schema.create(&value)
810
928
  end
811
929
 
812
- def apply_chat_options(chat)
813
- apply_assume_model_exists(chat)
814
- apply_protocol(chat)
815
- end
816
-
817
- def apply_assume_model_exists(chat_object)
818
- return unless chat_kwargs.key?(:assume_model_exists) &&
819
- resolved_chat_model &&
820
- chat_object.is_a?(resolved_chat_model)
821
-
822
- chat_object.assume_model_exists = chat_kwargs[:assume_model_exists]
823
- end
824
-
825
- def apply_protocol(chat_object)
826
- return unless chat_kwargs.key?(:protocol) &&
827
- resolved_chat_model &&
828
- chat_object.is_a?(resolved_chat_model)
829
-
830
- chat_object.protocol = chat_kwargs[:protocol]
831
- end
832
-
833
930
  def evaluate(value, runtime)
834
931
  value.is_a?(Proc) ? runtime.instance_exec(&value) : value
835
932
  end
@@ -860,7 +957,7 @@ module RubyLLM
860
957
  end
861
958
 
862
959
  def rails_chat_record?(chat)
863
- resolved_chat_model && chat.is_a?(resolved_chat_model)
960
+ resolved_chat_class && chat.is_a?(resolved_chat_class)
864
961
  end
865
962
 
866
963
  def default_instructions_prompt_exists?
@@ -898,12 +995,12 @@ module RubyLLM
898
995
  Support::Utils.underscore(class_name.gsub('::', '/')).tr('-', '_')
899
996
  end
900
997
 
901
- def resolved_chat_model
902
- return @resolved_chat_model if defined?(@resolved_chat_model)
998
+ def resolved_chat_class
999
+ return @resolved_chat_class if defined?(@resolved_chat_class)
903
1000
 
904
- @resolved_chat_model = case @chat_model
905
- when String then Object.const_get(@chat_model)
906
- else @chat_model
1001
+ @resolved_chat_class = case @chat_class
1002
+ when String then Object.const_get(@chat_class)
1003
+ else @chat_class
907
1004
  end
908
1005
  end
909
1006
  end
@@ -927,22 +1024,20 @@ module RubyLLM
927
1024
  input_values = input_values.merge(inputs || {})
928
1025
 
929
1026
  if chat
930
- @chat = chat
931
- self.class.apply_configuration(@chat, input_values:, persist_instructions:)
1027
+ bind(chat, input_values)
1028
+ self.class.apply_configuration(@chat, input_values:, persist_instructions:, agent: self)
932
1029
  return
933
1030
  end
934
1031
 
935
1032
  context_value = self.class.resolved_context(inputs: input_values)
936
- @chat = self.class.build_chat(
937
- inputs: input_values,
938
- options: chat_options,
939
- resolved_context: context_value
940
- )
1033
+ bind(self.class.build_chat(inputs: input_values, options: chat_options, resolved_context: context_value),
1034
+ input_values)
941
1035
  self.class.apply_configuration(
942
1036
  @chat,
943
1037
  input_values:,
944
1038
  persist_instructions:,
945
- resolved_context: context_value
1039
+ resolved_context: context_value,
1040
+ agent: self
946
1041
  )
947
1042
  end
948
1043
 
@@ -953,6 +1048,207 @@ module RubyLLM
953
1048
  # #chat without adapting its return values.
954
1049
  def_delegators :chat, *CHAINABLE_CHAT_DELEGATES, *PASSTHROUGH_CHAT_DELEGATES
955
1050
 
1051
+ ##
1052
+ # :singleton-method: rescue_from
1053
+ # :call-seq: rescue_from(*exception_classes, with: nil, &block)
1054
+ #
1055
+ # Registers a handler for exceptions raised by the chat operations of
1056
+ # this agent's instances and of the chats and records ::chat, ::create,
1057
+ # ::create!, and ::find return: #ask, #say, #ask_later, #complete,
1058
+ # #generate, #run_tools, #step, #count_tokens, and #compact. Name the
1059
+ # handler with +with:+ or pass a block; either runs on the agent instance,
1060
+ # so the agent's #chat, inputs, and class name are available for instrumentation.
1061
+ #
1062
+ # class ApplicationAgent < RubyLLM::Agent
1063
+ # rescue_from RubyLLM::RateLimitError, RubyLLM::ConnectionError, with: :handle_transient
1064
+ # rescue_from RubyLLM::BadRequestError do |error|
1065
+ # error_tracker.notify(error)
1066
+ # raise
1067
+ # end
1068
+ #
1069
+ # private
1070
+ #
1071
+ # def handle_transient(error)
1072
+ # metrics.increment("llm.api_error", type: "transient")
1073
+ # raise
1074
+ # end
1075
+ # end
1076
+ #
1077
+ # Handlers are searched in reverse declaration order, so the last
1078
+ # matching one wins. Re-raise inside a handler to let the caller see
1079
+ # the exception; otherwise the handler's return value becomes the
1080
+ # operation's return value. Exceptions no handler matches are
1081
+ # re-raised. Subclasses use their parents' handlers, including ones
1082
+ # declared after the subclass, and their own come first.
1083
+ #
1084
+ # Exception classes may be named as Strings, which defers constant
1085
+ # lookup until an exception is raised. A class that can never match a
1086
+ # StandardError, such as Interrupt, raises ArgumentError.
1087
+
1088
+ ##
1089
+ # :method: rescue_with_handler
1090
+ # :call-seq: rescue_with_handler(exception)
1091
+ #
1092
+ # Runs the ::rescue_from handler matching +exception+ and returns its
1093
+ # value, or re-raises when no handler matches. The chat operations call
1094
+ # this for you.
1095
+
1096
+ ##
1097
+ # :singleton-method: description
1098
+ # :call-seq: description(text = nil)
1099
+ #
1100
+ # Sets what the agent does, which a model reads when the agent is one
1101
+ # of its tools (see ::to_tool), or returns it when called without an
1102
+ # argument.
1103
+ #
1104
+ # class ResearchAgent < RubyLLM::Agent
1105
+ # description "Researches a question across the web and reports back"
1106
+ # end
1107
+
1108
+ ##
1109
+ # :singleton-method: to_tool
1110
+ # :call-seq: to_tool(**inputs)
1111
+ #
1112
+ # Returns an AgentTool that runs this agent as a subagent: a model
1113
+ # hands it a task and gets its answer back. Keywords matching ::inputs
1114
+ # are given to every run; the agent's other inputs become parameters
1115
+ # the model fills. Chat#with_tools and ::tools take an Agent class too,
1116
+ # and convert it with no inputs.
1117
+ #
1118
+ # chat.with_tools(ResearchAgent.to_tool(workspace: current_workspace))
1119
+ # chat.with_tools(ResearchAgent)
1120
+ #
1121
+ # Raises ArgumentError for an agent class without a name.
1122
+
1123
+ ##
1124
+ # :singleton-method: after_message
1125
+ # :call-seq: after_message(method_name = nil, if: nil, unless: nil, &block)
1126
+ #
1127
+ # Registers a Chat#after_message callback on every chat the agent builds
1128
+ # or finds, Agent::find records included. Name a method or pass a block;
1129
+ # either runs on the agent instance, so its inputs, +chat+, and private
1130
+ # methods are available, and receives the callback's arguments when it
1131
+ # takes them. +if:+ and +unless:+ take a method name, a lambda, or an
1132
+ # Array of them, like Rails callbacks. Subclasses inherit the callbacks
1133
+ # declared when they are defined. Returns the agent class.
1134
+ #
1135
+ # For a chat from ::chat or ::find, the agent is bound to the chat
1136
+ # without running +initialize+, as for ::rescue_from handlers, so read
1137
+ # inputs and +chat+ rather than instance variables +initialize+ sets.
1138
+ # Wrapping a configured chat in a new agent, as
1139
+ # <tt>SupportAgent.new(chat:)</tt> does, moves its callbacks to that
1140
+ # agent instead of registering them twice.
1141
+ #
1142
+ # class SupportAgent < RubyLLM::Agent
1143
+ # inputs :ticket
1144
+ #
1145
+ # after_message :log_reply, if: -> { ticket.urgent? }
1146
+ # before_tool_call { |tool_call| Rails.logger.info "Calling #{tool_call.name}" }
1147
+ #
1148
+ # private
1149
+ #
1150
+ # def log_reply(message) = ticket.notes.create!(body: message.content)
1151
+ # end
1152
+ #
1153
+ # The other chat callbacks have macros of the same name and shape:
1154
+ # ::before_request, ::before_message, ::before_tool_call,
1155
+ # ::around_tool_call, ::after_tool_result, ::after_tool_progress, ::before_fallback,
1156
+ # ::after_fallback, and ::after_file_write. Raises ArgumentError for
1157
+ # both a method name and a block, neither, or another option.
1158
+
1159
+ ##
1160
+ # :singleton-method: before_request
1161
+ # :call-seq: before_request(method_name = nil, if: nil, unless: nil, &block)
1162
+ #
1163
+ # Registers a Chat#before_request callback on every chat the agent builds or
1164
+ # finds. See ::after_message.
1165
+
1166
+ ##
1167
+ # :singleton-method: before_message
1168
+ # :call-seq: before_message(method_name = nil, if: nil, unless: nil, &block)
1169
+ #
1170
+ # Registers a Chat#before_message callback on every chat the agent builds or
1171
+ # finds. See ::after_message.
1172
+
1173
+ ##
1174
+ # :singleton-method: before_tool_call
1175
+ # :call-seq: before_tool_call(method_name = nil, if: nil, unless: nil, &block)
1176
+ #
1177
+ # Registers a Chat#before_tool_call callback on every chat the agent builds or
1178
+ # finds. See ::after_message.
1179
+
1180
+ ##
1181
+ # :singleton-method: around_tool_call
1182
+ # :call-seq: around_tool_call(method_name = nil, if: nil, unless: nil, &block)
1183
+ #
1184
+ # Registers a Chat#around_tool_call callback on every chat the agent builds
1185
+ # or finds. The method or block receives the ToolCall and a block that
1186
+ # runs the tool. A method yields to it; a block takes it as +&run+:
1187
+ #
1188
+ # class SupportAgent < RubyLLM::Agent
1189
+ # inputs :account
1190
+ #
1191
+ # around_tool_call :in_tenant
1192
+ #
1193
+ # private
1194
+ #
1195
+ # def in_tenant(tool_call, &) = ActsAsTenant.with_tenant(account, &)
1196
+ # end
1197
+ #
1198
+ # When +if:+ or +unless:+ skips the callback, the tool runs without it.
1199
+ # See ::after_message.
1200
+
1201
+ ##
1202
+ # :singleton-method: after_tool_result
1203
+ # :call-seq: after_tool_result(method_name = nil, if: nil, unless: nil, &block)
1204
+ #
1205
+ # Registers a Chat#after_tool_result callback on every chat the agent builds or
1206
+ # finds. See ::after_message.
1207
+
1208
+ ##
1209
+ # :singleton-method: after_tool_progress
1210
+ # :call-seq: after_tool_progress(method_name = nil, if: nil, unless: nil, &block)
1211
+ #
1212
+ # Registers a Chat#after_tool_progress callback on every chat the agent builds or
1213
+ # finds. See ::after_message.
1214
+
1215
+ ##
1216
+ # :singleton-method: before_fallback
1217
+ # :call-seq: before_fallback(method_name = nil, if: nil, unless: nil, &block)
1218
+ #
1219
+ # Registers a Chat#before_fallback callback on every chat the agent builds or
1220
+ # finds. See ::after_message.
1221
+
1222
+ ##
1223
+ # :singleton-method: after_fallback
1224
+ # :call-seq: after_fallback(method_name = nil, if: nil, unless: nil, &block)
1225
+ #
1226
+ # Registers a Chat#after_fallback callback on every chat the agent builds or
1227
+ # finds. See ::after_message.
1228
+
1229
+ ##
1230
+ # :singleton-method: after_file_write
1231
+ # :call-seq: after_file_write(method_name = nil, if: nil, unless: nil, &block)
1232
+ #
1233
+ # Registers a Chat#after_file_write callback on every chat the agent builds or
1234
+ # finds. See ::after_message.
1235
+
1236
+ ##
1237
+ # :singleton-method: around_join
1238
+ # :call-seq: around_join(&block)
1239
+ #
1240
+ # Registers +block+ as a Chat#around_join callback on every chat the
1241
+ # agent builds or finds. Subclasses inherit the callbacks declared
1242
+ # when they are defined. Returns the list of callbacks.
1243
+ #
1244
+ # class SupportAgent < RubyLLM::Agent
1245
+ # around_join do |queued, join|
1246
+ # join.call(sender: queued.sender)
1247
+ # end
1248
+ # end
1249
+ #
1250
+ # Raises ArgumentError without a block.
1251
+
956
1252
  ##
957
1253
  # :method: with_instructions
958
1254
  # :call-seq: with_instructions(instructions, append: false, cache_until_here: false)
@@ -1019,6 +1315,18 @@ module RubyLLM
1019
1315
  #
1020
1316
  # Delegates to Chat#with_end_user. See that method for arguments and return values.
1021
1317
 
1318
+ ##
1319
+ # :method: with_owner
1320
+ # :call-seq: with_owner(owner)
1321
+ #
1322
+ # Delegates to Chat#with_owner. See that method for arguments and return values.
1323
+
1324
+ ##
1325
+ # :method: with_context_budget
1326
+ # :call-seq: with_context_budget(budget = {}, per_result: nil, per_request: nil, window: nil)
1327
+ #
1328
+ # Delegates to Chat#with_context_budget. See that method for arguments and return values.
1329
+
1022
1330
  ##
1023
1331
  # :method: with_compaction
1024
1332
  # :call-seq: with_compaction(options = {})
@@ -1031,6 +1339,24 @@ module RubyLLM
1031
1339
  #
1032
1340
  # Delegates to Chat#with_caching. See that method for arguments and return values.
1033
1341
 
1342
+ ##
1343
+ # :method: with_code_execution
1344
+ # :call-seq: with_code_execution(enabled = true, **options)
1345
+ #
1346
+ # Delegates to Chat#with_code_execution. See that method for arguments and return values.
1347
+
1348
+ ##
1349
+ # :method: with_skills
1350
+ # :call-seq: with_skills(*skills)
1351
+ #
1352
+ # Delegates to Chat#with_skills. See that method for arguments and return values.
1353
+
1354
+ ##
1355
+ # :method: with_file_store
1356
+ # :call-seq: with_file_store(store = :memory, inline_limit: 10_000, tools: %i[read write edit])
1357
+ #
1358
+ # Delegates to Chat#with_file_store. See that method for arguments and return values.
1359
+
1034
1360
  ##
1035
1361
  # :method: with_context
1036
1362
  # :call-seq: with_context(context)
@@ -1057,7 +1383,7 @@ module RubyLLM
1057
1383
 
1058
1384
  ##
1059
1385
  # :method: with_fallbacks
1060
- # :call-seq: with_fallbacks(*models, on: Fallback::DEFAULT_ERRORS)
1386
+ # :call-seq: with_fallbacks(*models, on: Fallback::DEFAULT_ERRORS, drop_unsupported: false)
1061
1387
  #
1062
1388
  # Delegates to Chat#with_fallbacks. See that method for arguments and return values.
1063
1389
 
@@ -1097,6 +1423,12 @@ module RubyLLM
1097
1423
  #
1098
1424
  # Delegates to Chat#after_tool_result. See that method for arguments and return values.
1099
1425
 
1426
+ ##
1427
+ # :method: around_tool_call
1428
+ # :call-seq: around_tool_call(&block)
1429
+ #
1430
+ # Delegates to Chat#around_tool_call. See that method for arguments and return values.
1431
+
1100
1432
  ##
1101
1433
  # :method: after_tool_progress
1102
1434
  # :call-seq: after_tool_progress(&block)
@@ -1115,6 +1447,24 @@ module RubyLLM
1115
1447
  #
1116
1448
  # Delegates to Chat#after_fallback. See that method for arguments and return values.
1117
1449
 
1450
+ ##
1451
+ # :method: around_join
1452
+ # :call-seq: around_join(&block)
1453
+ #
1454
+ # Delegates to Chat#around_join. See that method for arguments and return values.
1455
+
1456
+ ##
1457
+ # :method: join_queued_messages
1458
+ # :call-seq: join_queued_messages
1459
+ #
1460
+ # Delegates to Chat#join_queued_messages. See that method for arguments and return values.
1461
+
1462
+ ##
1463
+ # :method: after_file_write
1464
+ # :call-seq: after_file_write(&block)
1465
+ #
1466
+ # Delegates to Chat#after_file_write. See that method for arguments and return values.
1467
+
1118
1468
  ##
1119
1469
  # :method: cancel
1120
1470
  # :call-seq: cancel()
@@ -1123,25 +1473,19 @@ module RubyLLM
1123
1473
 
1124
1474
  ##
1125
1475
  # :method: approve
1126
- # :call-seq: approve(tool_call)
1476
+ # :call-seq: approve(tool_call, remember: false)
1127
1477
  #
1128
1478
  # Delegates to Chat#approve. See that method for arguments and return values.
1129
1479
 
1130
- ##
1131
- # :method: deny
1132
- # :call-seq: deny(tool_call)
1133
- #
1134
- # Delegates to Chat#deny. See that method for arguments and return values.
1135
-
1136
1480
  ##
1137
1481
  # :method: answer
1138
- # :call-seq: answer(request, **values)
1482
+ # :call-seq: answer(question, value = nil, **values)
1139
1483
  #
1140
1484
  # Delegates to Chat#answer. See that method for arguments and return values.
1141
1485
 
1142
1486
  ##
1143
1487
  # :method: decline
1144
- # :call-seq: decline(request)
1488
+ # :call-seq: decline(question, note = nil)
1145
1489
  #
1146
1490
  # Delegates to Chat#decline. See that method for arguments and return values.
1147
1491
 
@@ -1149,7 +1493,9 @@ module RubyLLM
1149
1493
  # :method: cache_until_here
1150
1494
  # :call-seq: cache_until_here(ttl: nil)
1151
1495
  #
1152
- # Delegates to Chat#cache_until_here. See that method for arguments and return values.
1496
+ # Deprecated: use Message#cache_until_here or
1497
+ # <tt>with_instructions(cache_until_here:)</tt>. Delegates to
1498
+ # Chat#cache_until_here, which warns. RubyLLM 3.0 removes it.
1153
1499
 
1154
1500
  ##
1155
1501
  # :method: model
@@ -1235,12 +1581,36 @@ module RubyLLM
1235
1581
  #
1236
1582
  # Delegates to Chat#citations. See that method for arguments and return values.
1237
1583
 
1584
+ ##
1585
+ # :method: code_execution
1586
+ # :call-seq: code_execution
1587
+ #
1588
+ # Delegates to Chat#code_execution. See that method for arguments and return values.
1589
+
1590
+ ##
1591
+ # :method: skills
1592
+ # :call-seq: skills
1593
+ #
1594
+ # Delegates to Chat#skills. See that method for arguments and return values.
1595
+
1596
+ ##
1597
+ # :method: file_store
1598
+ # :call-seq: file_store
1599
+ #
1600
+ # Delegates to Chat#file_store. See that method for arguments and return values.
1601
+
1238
1602
  ##
1239
1603
  # :method: compaction
1240
1604
  # :call-seq: compaction
1241
1605
  #
1242
1606
  # Delegates to Chat#compaction. See that method for arguments and return values.
1243
1607
 
1608
+ ##
1609
+ # :method: context_budget
1610
+ # :call-seq: context_budget
1611
+ #
1612
+ # Delegates to Chat#context_budget. See that method for arguments and return values.
1613
+
1244
1614
  ##
1245
1615
  # :method: context
1246
1616
  # :call-seq: context
@@ -1281,7 +1651,15 @@ module RubyLLM
1281
1651
  # :method: each
1282
1652
  # :call-seq: each(&block)
1283
1653
  #
1284
- # Delegates to Chat#each. See that method for arguments and return values.
1654
+ # Deprecated: iterate #messages instead. Delegates to Chat#each, which
1655
+ # warns, as do the Enumerable methods built on it. RubyLLM 3.0 removes
1656
+ # them.
1657
+
1658
+ ##
1659
+ # :method: model_id
1660
+ # :call-seq: model_id()
1661
+ #
1662
+ # Delegates to Chat#model_id. See that method for arguments and return values.
1285
1663
 
1286
1664
  ##
1287
1665
  # :method: complete?
@@ -1302,46 +1680,40 @@ module RubyLLM
1302
1680
  # Delegates to Chat#waiting?. See that method for arguments and return values.
1303
1681
 
1304
1682
  ##
1305
- # :method: awaiting_approval?
1306
- # :call-seq: awaiting_approval?()
1683
+ # :method: pending_questions
1684
+ # :call-seq: pending_questions()
1307
1685
  #
1308
- # Delegates to Chat#awaiting_approval?. See that method for arguments and return values.
1686
+ # Delegates to Chat#pending_questions. See that method for arguments and return values.
1309
1687
 
1310
1688
  ##
1311
- # :method: pending_approvals
1312
- # :call-seq: pending_approvals()
1313
- #
1314
- # Delegates to Chat#pending_approvals. See that method for arguments and return values.
1315
-
1316
- ##
1317
- # :method: awaiting_input?
1318
- # :call-seq: awaiting_input?()
1689
+ # :method: pending_tasks
1690
+ # :call-seq: pending_tasks()
1319
1691
  #
1320
- # Delegates to Chat#awaiting_input?. See that method for arguments and return values.
1692
+ # Delegates to Chat#pending_tasks. See that method for arguments and return values.
1321
1693
 
1322
1694
  ##
1323
- # :method: pending_inputs
1324
- # :call-seq: pending_inputs()
1695
+ # :method: add_message
1696
+ # :call-seq: add_message(message_or_attributes)
1325
1697
  #
1326
- # Delegates to Chat#pending_inputs. See that method for arguments and return values.
1698
+ # Delegates to Chat#add_message. See that method for arguments and return values.
1327
1699
 
1328
1700
  ##
1329
- # :method: awaiting_tasks?
1330
- # :call-seq: awaiting_tasks?()
1701
+ # :method: queue
1702
+ # :call-seq: queue(message = nil, with: nil, steer: true, sender: nil)
1331
1703
  #
1332
- # Delegates to Chat#awaiting_tasks?. See that method for arguments and return values.
1704
+ # Delegates to Chat#queue. See that method for arguments and return values.
1333
1705
 
1334
1706
  ##
1335
- # :method: pending_tasks
1336
- # :call-seq: pending_tasks()
1707
+ # :method: queued_messages
1708
+ # :call-seq: queued_messages()
1337
1709
  #
1338
- # Delegates to Chat#pending_tasks. See that method for arguments and return values.
1710
+ # Delegates to Chat#queued_messages. See that method for arguments and return values.
1339
1711
 
1340
1712
  ##
1341
- # :method: add_message
1342
- # :call-seq: add_message(message_or_attributes)
1713
+ # :method: withdraw
1714
+ # :call-seq: withdraw(queued)
1343
1715
  #
1344
- # Delegates to Chat#add_message. See that method for arguments and return values.
1716
+ # Delegates to Chat#withdraw. See that method for arguments and return values.
1345
1717
 
1346
1718
  ##
1347
1719
  # :method: tokens
@@ -1423,25 +1795,37 @@ module RubyLLM
1423
1795
  # Compacts the model context through Chat#compact and returns its Message.
1424
1796
  # Exceptions use ::rescue_from handlers.
1425
1797
 
1426
- GUARDED_OPERATIONS.each do |operation|
1798
+ Chat::Rescuable::OPERATIONS.each do |operation|
1427
1799
  define_method(operation) do |*args, **kwargs, &block|
1428
- chat.public_send(operation, *args, **kwargs, &block)
1429
- rescue StandardError => e
1430
- rescue_with_handler(e)
1800
+ rescuing { chat.public_send(operation, *args, **kwargs, &block) }
1431
1801
  end
1432
1802
  end
1433
1803
 
1434
- # Runs the ::rescue_from handler matching +exception+ and returns its
1435
- # value, or re-raises when no handler matches. The chat operations call
1436
- # this for you.
1437
- def rescue_with_handler(exception)
1438
- handler = self.class.rescue_handler_for(exception)
1439
- raise exception unless handler
1804
+ # Returns whoever the usage of the agent's chat is attributed to, as
1805
+ # Chat#owner describes. For a persisted chat, reads it from the
1806
+ # record's chat, since the record leaves +owner+ to the application.
1807
+ def owner
1808
+ (chat.respond_to?(:to_llm) ? chat.to_llm : chat).owner
1809
+ end
1440
1810
 
1441
- return instance_exec(exception, &handler) unless handler.is_a?(Symbol)
1811
+ def bind(chat, input_values) # :nodoc:
1812
+ @chat = chat
1813
+ input_values.each do |name, value|
1814
+ define_singleton_method(name) { value } unless respond_to?(name)
1815
+ end
1816
+ self
1817
+ end
1818
+
1819
+ private
1820
+
1821
+ def rescuing(&)
1822
+ return chat.rescuing_with(self, &) if chat.is_a?(Chat::Rescuable)
1442
1823
 
1443
- handler_method = method(handler)
1444
- handler_method.arity.zero? ? handler_method.call : handler_method.call(exception)
1824
+ begin
1825
+ yield
1826
+ rescue StandardError => e
1827
+ rescue_with_handler(e)
1828
+ end
1445
1829
  end
1446
1830
  end
1447
1831
  end