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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f0478372c3c7c58cd1452546f21068161f553949b064377738c28e74b67da9d
4
- data.tar.gz: 2f8f63b90a885326904724a95074934877251b589b41ba00d9366dd72d5b3d69
3
+ metadata.gz: e784b5ab43c18ac17675ffa3e46c802934a9f8eaa4ef733bd1e7c450c462fff5
4
+ data.tar.gz: 69d10ad8492781735ce7b50d56476bd39d3cf405e9218adb819e735712144761
5
5
  SHA512:
6
- metadata.gz: 4028eea4e3515ffa779ada1d1464ab5ed4fa4c21418c1ebaa8fe09bb0da11fd6998f6b0132dde1cd59f876b54a43c03f89507be73e7d3e39b3b3794fbb33f3c5
7
- data.tar.gz: e57218438a1479c2d4974926d4afdf509fa18f2c57ffe0524e116feea919d1fb337d80f883dca30205a442ed754faafafb076bcc780eceb2f86b4c6372a47321
6
+ metadata.gz: 38f20b77e3e1553f8b3962fe2da6a11c39ea339363447b652387a832637ccf0b8316d848a895296e157d869d22cc661ce231a891369aa1aee3f33cc06fb863f0
7
+ data.tar.gz: 1403357da777eacf6fce2d449de634e16fd848f93f8e36b9c06632fe60d1b638afe7856456ccfb1e948b5d443980c8a442f586e6f21af7cf5775dd485cdabe47
data/README.md CHANGED
@@ -5,11 +5,24 @@
5
5
  <img src="/docs/assets/images/logotype.svg" alt="RubyLLM" height="120" width="250">
6
6
  </picture>
7
7
 
8
- <strong>Build AI features the Ruby way</strong>
8
+ <strong>The Ruby AI framework</strong>
9
9
 
10
- <p>RubyLLM is the Ruby-native AI framework. Work with models, tools, and agents through one consistent API, in plain Ruby or Rails.</p>
10
+ <p>Build AI features the Ruby way: models, tools, agents, evaluations, and Rails conventions, designed together, with one consistent API.</p>
11
11
 
12
- [Website](https://rubyllm.com/) · [Getting Started](https://rubyllm.com/getting-started/) · [What's New in 2.1](https://rubyllm.com/whats-new-in-2-1/)
12
+ </div>
13
+
14
+ ```ruby
15
+ chat = RubyLLM.chat
16
+ chat.ask "What's the best way to learn Ruby?"
17
+ chat.ask "What's in this image?", with: "ruby_conf.jpg"
18
+ chat.with_model("claude-sonnet-5").ask "Now say it as a haiku"
19
+
20
+ RubyLLM.paint("a sunset over mountains in watercolor").save("sunset.png")
21
+ ```
22
+
23
+ <div align="center">
24
+
25
+ [Website](https://rubyllm.com/) · [Getting Started](https://rubyllm.com/getting-started/) · [What's New in 2.2](https://rubyllm.com/whats-new-in-2-2/)
13
26
 
14
27
  Battle tested at [<picture><source media="(prefers-color-scheme: dark)" srcset="https://chatwithwork.com/logotype-dark.svg"><img src="https://chatwithwork.com/logotype.svg" alt="Chat with Work" height="30" align="absmiddle"></picture>](https://chatwithwork.com) - *Fully private work AI*
15
28
 
@@ -21,20 +34,152 @@ Battle tested at [<picture><source media="(prefers-color-scheme: dark)" srcset="
21
34
  <a href="https://trendshift.io/repositories/13640" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13640" alt="crmne%2Fruby_llm | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
22
35
  </div>
23
36
 
37
+ https://github.com/user-attachments/assets/519fcb20-ab8c-4af3-9185-0953b3ba7b81
38
+
24
39
  > [!NOTE]
25
40
  > Using RubyLLM? [Share your story](https://tally.so/r/3Na02p)! Takes 5 minutes.
26
41
 
27
42
  ---
28
43
 
29
- ## 19 providers. One Ruby API.
44
+ ## 23 providers. One API.
30
45
 
31
46
  Build with the models you want. Move between hosted and local providers without rewriting your application, or connect an OpenAI-compatible endpoint.
32
47
 
33
48
  [Browse models and pricing](https://rubyllm.com/available-models/) · [Connect a provider](https://rubyllm.com/configuration-providers/)
34
49
 
50
+ ## A complete AI framework for Ruby
51
+
52
+ More than a client for model APIs. RubyLLM gives your application the conventions, the runtime, and every piece in between, designed to work together. Everything below ships in RubyLLM 2.2.
53
+
54
+ ### Conversations
55
+
56
+ * **Chat:** Conversational AI with `RubyLLM.chat`
57
+ * **Streaming:** Real-time responses with blocks
58
+ * **Attachments:** Ask about images, videos, audio, PDFs, and other documents with `with:`
59
+ * **Structured output:** Define a Ruby schema and read the result with `response.parsed`
60
+ * **Extended thinking:** Control, view, and persist model deliberation
61
+ * **Citations:** Normalized source citations from documents, search, and grounding
62
+ * **Prompt caching:** Turn on the provider's cache with `with_caching` and `cache_until_here`
63
+ * **Compaction:** Let providers condense long conversations with `with_compaction`
64
+ * **Token counting:** Count a request before you send it with `count_tokens`
65
+ * **Fallbacks and cancellation:** Retry on backup models with `with_fallbacks`, stop a run with `cancel`
66
+
67
+ ### Tools and agents
68
+
69
+ * **Tools:** Let AI call your Ruby methods
70
+ * **Tool approval:** Park a run until a human approves with `requires_approval`
71
+ * **Provider tools:** Web search, code execution, and MCP connectors with `with_provider_tools`
72
+ * **MCP:** Connect agents to your systems with server classes in `app/mcp`, per-user inputs, tool filtering, and OAuth with [`RubyLLM::MCP`](https://rubyllm.com/mcp/)
73
+ * **Agents:** Reusable assistants with `RubyLLM::Agent`
74
+ * **The agentic loop:** Drive it yourself with `ask_later`, `step`, and `complete?`
75
+ * **Prompt templates:** ERB prompts in `app/prompts`, rendered with `RubyLLM.render_prompt`
76
+ * **Workflows:** Correlate multi-agent runs in your telemetry with `RubyLLM.workflow`
77
+ * **Evaluations:** Reusable datasets, model criteria, and Ruby assertions with [`RubyLLM::Evaluation`](https://rubyllm.com/evaluations/)
78
+ * **Judgments:** Probabilities, choices, and scores with `RubyLLM::Judge` and `RubyLLM.judge`
79
+
80
+ ### Individual operations
81
+
82
+ * **Image generation:** Create images with `RubyLLM.paint`
83
+ * **Video generation:** Create videos with `RubyLLM.animate`
84
+ * **Audio:** Transcribe speech with `RubyLLM.transcribe` and generate it with `RubyLLM.speak`
85
+ * **OCR:** Turn documents into markdown with `RubyLLM.ocr`
86
+ * **Embeddings:** Generate embeddings with `RubyLLM.embed`
87
+ * **Reranking:** Order retrieval candidates by relevance with `RubyLLM.rerank`
88
+ * **Moderation:** Content flags, categories, and scores with `RubyLLM.moderate`
89
+ * **Files:** Upload once and reuse across chats with `RubyLLM.upload`
90
+ * **Batches:** Provider-side batch processing with provider-specific discounts via `RubyLLM.batch`
91
+
92
+ ### Rails and production
93
+
94
+ * **Rails:** Active Record persistence, Active Storage attachments, Hotwire streaming, and generators
95
+ * **Cost tracking:** A per-attempt usage ledger behind `chat.tokens` and `chat.cost`
96
+ * **Observability:** Instrumentation events and OpenTelemetry tracing
97
+ * **Async:** Fiber-based concurrency
98
+ * **Model registry:** Browse capabilities, limits, and pricing across providers
99
+ * **Providers:** OpenAI, Azure, xAI, Anthropic, Gemini, VertexAI, Bedrock, Cohere, DeepSeek, Hetzner, Meta, MiniMax, Mistral, Moonshot AI, Ollama, Ollama Cloud, OpenRouter, Perplexity, GPUStack, ElevenLabs, Deepgram, TypeSafe, Z.ai, and any OpenAI-compatible API
100
+
101
+ ### New in 2.2
102
+
103
+ * **Code execution:** Let the model run Ruby, Python, JavaScript, and shell in a sandbox on your servers with [`with_code_execution`](https://rubyllm.com/code-execution/)
104
+ * **Skills:** Teach the model your workflows with folders of instructions and scripts in the Agent Skills format with [`with_skills`](https://rubyllm.com/skills/)
105
+ * **File stores:** Keep attachments and long tool results out of the conversation with [`with_file_store`](https://rubyllm.com/file-stores/)
106
+ * **Message queueing:** Add messages while a chat runs with `queue`
107
+ * **More providers:** Meta, MiniMax, Moonshot AI, and Z.ai
108
+
109
+ ## Evaluate your agents
110
+
111
+ Keep questions and expected answers in a dataset. RubyLLM checks correctness by default. Run the same evaluation from Ruby, RSpec, Minitest, or Rake.
112
+
113
+ `app/agents/support_agent.rb`
114
+
115
+ ```ruby
116
+ class SupportAgent < RubyLLM::Agent
117
+ instructions "Accept returns of unopened items within 30 days."
118
+ end
119
+ ```
120
+
121
+ `app/evals/support_evaluation.rb`
122
+
123
+ ```ruby
124
+ class SupportEvaluation < RubyLLM::Evaluation
125
+ def perform(input)
126
+ SupportAgent.new.ask(input)
127
+ end
128
+ end
129
+ ```
130
+
131
+ `app/evals/support_evaluation.yml`
132
+
133
+ ```yaml
134
+ cases:
135
+ - name: within_return_window
136
+ inputs: Can I return an unopened item after 14 days?
137
+ expected_output: Yes, unopened items can be returned within 30 days.
138
+ - name: past_return_window
139
+ inputs: Can I return an unopened item after 45 days?
140
+ expected_output: No, the 30-day return window has passed.
141
+ ```
142
+
143
+ Run the evaluation:
144
+
145
+ ```ruby
146
+ report = SupportEvaluation.run
147
+ report.passed?
148
+ report.cost.total
149
+ ```
150
+
151
+ The model evaluator checks meaning, so answers can use different wording. Declare your own criteria, add Ruby assertions, or choose a configured evaluator agent or judgment model. [Read the evaluation guides](https://rubyllm.com/evaluations/).
152
+
153
+ ## Feels at home in Rails
154
+
155
+ Save conversations with Active Record and stream replies with Hotwire. The generators give you a working chat UI. Watch the two-minute demo.
156
+
157
+ https://github.com/user-attachments/assets/65422091-9338-47da-a303-92b918bd1345
158
+
159
+ ```bash
160
+ # Install Rails Integration
161
+ bin/rails generate ruby_llm:install
162
+ bin/rails db:migrate
163
+ bin/rails ruby_llm:load_models
164
+
165
+ # Add Chat UI (optional)
166
+ bin/rails generate ruby_llm:chat_ui
167
+ ```
168
+
169
+ ```ruby
170
+ class Chat < ApplicationRecord
171
+ acts_as_chat
172
+ end
173
+
174
+ chat = Chat.create! model: "gpt-6-luna"
175
+ chat.ask "What's in this file?", with: "report.pdf"
176
+ ```
177
+
178
+ Start your Rails server and visit `http://localhost:3000/chats` to try the chat interface. See the [Rails guide](https://rubyllm.com/rails/) for persistence, streaming, and background jobs.
179
+
35
180
  ## Start with one line. Add files, tools, and agents
36
181
 
37
- These examples use **2.1.0**. Follow [Getting Started](https://rubyllm.com/getting-started/) to install it and configure the providers you want to try. For 1.x, use the [1.x docs](https://rubyllm.com/v1/).
182
+ These examples use **2.2.0**. Follow [Getting Started](https://rubyllm.com/getting-started/) to install it and configure the providers you want to try. For 1.x, use the [1.x docs](https://rubyllm.com/v1/).
38
183
 
39
184
  ```ruby
40
185
  # Just ask
@@ -78,7 +223,7 @@ video.save "paper_boat.mp4"
78
223
  ```ruby
79
224
  # Create embeddings
80
225
  embedding = RubyLLM.embed "Ruby is elegant and expressive"
81
- embedding.vectors
226
+ embedding.vector
82
227
  ```
83
228
 
84
229
  ```ruby
@@ -137,7 +282,7 @@ chat.with_tools(Weather).ask "What's the weather in Berlin?"
137
282
  ```ruby
138
283
  # Define an agent with instructions + tools
139
284
  class WeatherAssistant < RubyLLM::Agent
140
- model "gpt-5.6-luna"
285
+ model "gpt-6-luna"
141
286
  instructions "Be concise and always use tools for weather."
142
287
  tools Weather
143
288
  end
@@ -159,97 +304,12 @@ response = chat.with_schema(ProductSchema).ask "Analyze this product", with: "pr
159
304
  response.parsed
160
305
  ```
161
306
 
162
- ## Evaluate your agents
163
-
164
- Keep questions and expected answers in a dataset. RubyLLM checks correctness by default. Run the same evaluation from Ruby, RSpec, Minitest, or Rake.
165
-
166
- Evaluations are available on `main`. Follow the [development installation guide](https://rubyllm.com/next/getting-started/) to try them.
167
-
168
- `app/agents/support_agent.rb`
169
-
170
- ```ruby
171
- class SupportAgent < RubyLLM::Agent
172
- instructions "Accept returns of unopened items within 30 days."
173
- end
174
- ```
175
-
176
- `app/evals/support_evaluation.rb`
177
-
178
- ```ruby
179
- class SupportEvaluation < RubyLLM::Evaluation
180
- def perform(input)
181
- SupportAgent.new.ask(input)
182
- end
183
- end
184
- ```
185
-
186
- `app/evals/support_evaluation.yml`
187
-
188
- ```yaml
189
- cases:
190
- - name: within_return_window
191
- inputs: Can I return an unopened item after 14 days?
192
- expected_output: Yes, unopened items can be returned within 30 days.
193
- - name: past_return_window
194
- inputs: Can I return an unopened item after 45 days?
195
- expected_output: No, the 30-day return window has passed.
196
- ```
197
-
198
- Run the evaluation:
199
-
200
- ```ruby
201
- report = SupportEvaluation.run
202
- report.passed?
203
- report.cost.total
204
- ```
205
-
206
- The model evaluator checks meaning, so answers can use different wording. Declare your own criteria, add Ruby assertions, or choose a configured evaluator agent or judgment model. [Read the evaluation guides](https://rubyllm.com/next/evaluations/).
207
-
208
- ## A complete AI framework for Ruby
209
-
210
- Agents, evaluations, workflows, RAG, images, audio, and video. Built in, with usage tracking and Rails integration to bring them into your app.
211
-
212
- * **Chat:** Conversational AI with `RubyLLM.chat`
213
- * **Vision:** Analyze images and videos
214
- * **Audio:** Transcribe speech with `RubyLLM.transcribe` and generate it with `RubyLLM.speak`
215
- * **Documents:** Ask questions about PDFs, text files, and other supported formats
216
- * **OCR:** Turn documents into markdown with `RubyLLM.ocr`
217
- * **Image generation:** Create images with `RubyLLM.paint`
218
- * **Video generation:** Create videos with `RubyLLM.animate`
219
- * **Embeddings:** Generate embeddings with `RubyLLM.embed`
220
- * **Reranking:** Order retrieval candidates by relevance with `RubyLLM.rerank`
221
- * **Moderation:** Content flags, categories, and scores with `RubyLLM.moderate`
222
- * **Judgments:** Probabilities, choices, and scores with `RubyLLM::Judge` and `RubyLLM.judge`
223
- * **Tools:** Let AI call your Ruby methods
224
- * **Tool approval:** Park a run until a human approves with `requires_approval`
225
- * **The agentic loop:** Drive it yourself with `ask_later`, `step`, and `complete?`
226
- * **Provider tools:** Web search, code execution, and MCP connectors with `with_provider_tools`
227
- * **Agents:** Reusable assistants with `RubyLLM::Agent`
228
- * **Evaluations (main):** Reusable datasets, model criteria, and Ruby assertions with [`RubyLLM::Evaluation`](https://rubyllm.com/next/evaluations/)
229
- * **Prompt templates:** ERB prompts in `app/prompts`, rendered with `RubyLLM.render_prompt`
230
- * **Workflows:** Correlate multi-agent runs in your telemetry with `RubyLLM.workflow`
231
- * **Structured output:** Define a Ruby schema and read the result with `response.parsed`
232
- * **Streaming:** Real-time responses with blocks
233
- * **Rails:** Active Record persistence, Active Storage attachments, Hotwire streaming, and generators
234
- * **Files:** Upload once and reuse across chats with `RubyLLM.upload`
235
- * **Prompt caching:** Turn on the provider's cache with `with_caching` and `cache_until_here`
236
- * **Fallbacks and cancellation:** Retry on backup models with `with_fallbacks`, stop a run with `cancel`
237
- * **Cost tracking:** A per-attempt usage ledger behind `chat.tokens` and `chat.cost`
238
- * **Async:** Fiber-based concurrency
239
- * **Model registry:** Browse capabilities, limits, and pricing across providers
240
- * **Extended thinking:** Control, view, and persist model deliberation
241
- * **Citations:** Normalized source citations from documents, search, and grounding
242
- * **Batches:** Provider-side batch processing with provider-specific discounts via `RubyLLM.batch`
243
- * **Compaction:** Let providers condense long conversations with `with_compaction`
244
- * **Token counting:** Count a request before you send it with `count_tokens`
245
- * **Providers:** OpenAI, Azure, xAI, Anthropic, Gemini, VertexAI, Bedrock, Cohere, DeepSeek, Hetzner, Mistral, Ollama, Ollama Cloud, OpenRouter, Perplexity, GPUStack, ElevenLabs, Deepgram, TypeSafe, and any OpenAI-compatible API
246
-
247
307
  ## Installation
248
308
 
249
- Install RubyLLM 2.1:
309
+ Install RubyLLM 2.2:
250
310
 
251
311
  ```bash
252
- bundle add ruby_llm --version 2.1.0
312
+ bundle add ruby_llm --version 2.2.0
253
313
  ```
254
314
 
255
315
  Configure a provider in your script, or in `config/initializers/ruby_llm.rb` in Rails:
@@ -264,42 +324,24 @@ end
264
324
 
265
325
  Configure the other providers used by the examples as needed: Gemini for files, xAI for video, Mistral for OCR, and Cohere for reranking. [Getting Started](https://rubyllm.com/getting-started/) shows each setup beside its example. Upgrading an existing app? Follow the [upgrade guide](https://rubyllm.com/upgrading/).
266
326
 
267
- ## Feels at home in Rails
327
+ Coming from ruby-openai or langchainrb? Follow the [ruby-openai migration guide](https://rubyllm.com/migrate/ruby-openai/), or see [every migration and comparison guide](https://rubyllm.com/migrating-from/).
268
328
 
269
- Save conversations with Active Record and stream replies with Hotwire. The generators give you a working chat UI. Watch the two-minute demo.
329
+ ## AI coding assistants
270
330
 
271
- https://github.com/user-attachments/assets/65422091-9338-47da-a303-92b918bd1345
331
+ Give your coding assistant the RubyLLM API and documentation that match your application. From your application directory, install the skill packaged with your gem:
272
332
 
273
333
  ```bash
274
- # Install Rails Integration
275
- bin/rails generate ruby_llm:install
276
- bin/rails db:migrate
277
- bin/rails ruby_llm:load_models
278
-
279
- # Add Chat UI (optional)
280
- bin/rails generate ruby_llm:chat_ui
334
+ gem exec skill add "$(bundle show ruby_llm)" --skill rubyllm --agents coding
281
335
  ```
282
336
 
283
- ```ruby
284
- class Chat < ApplicationRecord
285
- acts_as_chat
286
- end
337
+ `--agents coding` installs it for the coding assistants your project uses, not into your app's `app/skills`. In Claude Code, you can also install it as a plugin:
287
338
 
288
- chat = Chat.create! model: "gpt-5.6-luna"
289
- chat.ask "What's in this file?", with: "report.pdf"
290
339
  ```
291
-
292
- Start your Rails server and visit `http://localhost:3000/chats` to try the chat interface. See the [Rails guide](https://rubyllm.com/rails/) for persistence, streaming, and background jobs.
293
-
294
- ## AI coding assistants
295
-
296
- Give your coding assistant the RubyLLM API and documentation that match your application. From your application directory, install the skill packaged with your gem:
297
-
298
- ```bash
299
- npx skills add "$(bundle show ruby_llm)" --skill rubyllm
340
+ /plugin marketplace add crmne/ruby_llm
341
+ /plugin install rubyllm@ruby_llm
300
342
  ```
301
343
 
302
- Choose your coding assistant and installation scope when prompted. See [AI Coding Assistants](https://rubyllm.com/ai-coding-assistants/) for setup and updates.
344
+ See [AI Coding Assistants](https://rubyllm.com/ai-coding-assistants/) for setup and updates.
303
345
 
304
346
  ## Documentation
305
347
 
@@ -0,0 +1,163 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { Marked } from "marked"
3
+ import DOMPurify from "dompurify"
4
+
5
+ const escape = (text) => text.replace(/[&<>"']/g, (character) => `&#${character.charCodeAt(0)};`)
6
+
7
+ const markdown = new Marked({
8
+ renderer: {
9
+ html: ({ text }) => escape(text),
10
+ image: ({ href, text }) => escape(`[image: ${text}](${href})`)
11
+ }
12
+ })
13
+
14
+ const purify = DOMPurify(window)
15
+ const sanitizeOptions = {
16
+ USE_PROFILES: { html: true },
17
+ FORBID_TAGS: ["img", "picture", "source", "video", "audio", "track", "iframe", "input", "form"],
18
+ FORBID_ATTR: ["srcset", "style", "background", "poster", "ping"]
19
+ }
20
+
21
+ const earlyChunks = new Map()
22
+ const removedReplies = new Set()
23
+ const chatStream = "turbo-cable-stream-source[data-chat-stream]"
24
+ let visiting = false
25
+
26
+ addEventListener("turbo:visit", () => { visiting = true })
27
+ addEventListener("turbo:load", () => { visiting = false })
28
+
29
+ addEventListener("turbo:before-stream-render", (event) => {
30
+ const render = event.detail.render
31
+ event.detail.render = (stream) => {
32
+ if (stream.getAttribute("action") === "remove") removedReplies.add(stream.getAttribute("target"))
33
+ if (holdsRefresh(stream, render)) return
34
+ if (!holdsEarlyChunk(stream) && !revivesPlaceholder(stream)) render(stream)
35
+ }
36
+ })
37
+
38
+ // A chat page refreshes whenever its stream connects, so it shows what was
39
+ // broadcast before it subscribed or while it was offline, such as a reply
40
+ // that failed at once. The refresh morphs whatever the page's own refresh
41
+ // method, so the stream stays connected and the refresh happens once.
42
+ const catchUp = '<turbo-stream action="refresh" method="morph" scroll="preserve"></turbo-stream>'
43
+
44
+ new MutationObserver((records) => {
45
+ for (const { target } of records) {
46
+ if (target.matches(`${chatStream}[connected]`)) target.dispatchEvent(new MessageEvent("message", { data: catchUp }))
47
+ }
48
+ }).observe(document.documentElement, { subtree: true, attributeFilter: ["connected"] })
49
+
50
+ addEventListener("turbo:before-morph-element", (event) => {
51
+ const { newElement } = event.detail
52
+ const currentElement = event.detail.currentElement ?? event.target
53
+ const removesStreamingReply = !newElement && currentElement.hasAttribute?.("data-streaming")
54
+ if (removesStreamingReply || newElement?.hasAttribute?.("data-streaming")) event.preventDefault()
55
+ if (changedDraftField(currentElement) && newElement?.id === currentElement.id) event.preventDefault()
56
+ })
57
+
58
+ function changedDraftField(element) {
59
+ if (!element.form?.hasAttribute("data-chat-draft")) return false
60
+ if (element.type === "file") return element.files.length > 0
61
+ if (element.type === "checkbox") return element.checked !== element.defaultChecked
62
+ if (element.type === "radio") return changedRadioGroup(element)
63
+ if (element.options) return changedSelect(element)
64
+ return "defaultValue" in element && element.value !== element.defaultValue
65
+ }
66
+
67
+ function changedRadioGroup(radio) {
68
+ return [...radio.form.elements].some((field) =>
69
+ field.type === "radio" && field.name === radio.name && field.checked !== field.defaultChecked)
70
+ }
71
+
72
+ function changedSelect(select) {
73
+ const options = [...select.options]
74
+ if (select.multiple || select.size > 1) return options.some((option) => option.selected !== option.defaultSelected)
75
+
76
+ const rendered = options.findLast((option) => option.defaultSelected) ?? options.find(selectableOption)
77
+ return select.selectedIndex !== (rendered?.index ?? -1)
78
+ }
79
+
80
+ function selectableOption(option) {
81
+ return !option.disabled && !option.closest("optgroup")?.disabled
82
+ }
83
+
84
+ function holdsEarlyChunk(stream) {
85
+ const target = stream.getAttribute("target")
86
+ if (stream.getAttribute("action") !== "append" || !target?.endsWith("_stream")) return false
87
+ if (document.getElementById(target)) return false
88
+ if (document.getElementById(target.replace(/_stream$/, "_content"))) return true
89
+
90
+ earlyChunks.set(target, [...(earlyChunks.get(target) ?? []), stream.templateContent])
91
+ return true
92
+ }
93
+
94
+ function revivesPlaceholder(stream) {
95
+ const incoming = stream.templateContent?.firstElementChild
96
+ if (!incoming?.hasAttribute("data-streaming")) return false
97
+ return document.getElementById(incoming.id) !== null || removedReplies.has(incoming.id)
98
+ }
99
+
100
+ // Turbo drops a refresh that arrives during a visit, though the page the
101
+ // visit brings may predate it, so a chat page refreshes once the visit ends.
102
+ function holdsRefresh(stream, render) {
103
+ if (stream.getAttribute("action") !== "refresh" || !visiting || !document.querySelector(chatStream)) return false
104
+
105
+ addEventListener("turbo:load", () => render(stream), { once: true })
106
+ return true
107
+ }
108
+
109
+ export default class extends Controller {
110
+ static targets = ["source", "output"]
111
+
112
+ connect() {
113
+ this.observer = new MutationObserver(() => this.update())
114
+ this.observer.observe(this.sourceTarget, { childList: true, characterData: true, subtree: true })
115
+ earlyChunks.get(this.sourceTarget.id)?.forEach((chunk) => this.sourceTarget.append(chunk))
116
+ earlyChunks.delete(this.sourceTarget.id)
117
+ this.restore()
118
+ }
119
+
120
+ disconnect() {
121
+ this.observer.disconnect()
122
+ cancelAnimationFrame(this.frame)
123
+ }
124
+
125
+ restore() {
126
+ this.sourceTarget.hidden = true
127
+ this.render()
128
+ }
129
+
130
+ update() {
131
+ this.frame ||= requestAnimationFrame(() => this.render())
132
+ }
133
+
134
+ order() {
135
+ const seen = new Set()
136
+ for (const chunk of this.sourceTarget.querySelectorAll(":scope > [data-seq]")) {
137
+ if (seen.has(chunk.dataset.seq)) chunk.remove()
138
+ seen.add(chunk.dataset.seq)
139
+ }
140
+
141
+ const chunks = [...this.sourceTarget.querySelectorAll(":scope > [data-seq]")]
142
+ const sorted = chunks.toSorted((a, b) => a.dataset.seq - b.dataset.seq)
143
+ if (sorted.some((chunk, index) => chunk !== chunks[index])) this.sourceTarget.append(...sorted)
144
+ }
145
+
146
+ render() {
147
+ this.frame = null
148
+ if (this.element.hasAttribute("data-streaming")) this.order()
149
+ this.outputTarget.innerHTML = purify.sanitize(markdown.parse(this.text), sanitizeOptions)
150
+ }
151
+
152
+ get text() {
153
+ if (!this.element.hasAttribute("data-streaming")) return this.sourceTarget.textContent
154
+
155
+ const chunks = [...this.sourceTarget.querySelectorAll(":scope > [data-seq]")]
156
+ let text = ""
157
+ for (const [index, chunk] of chunks.entries()) {
158
+ if (index > 0 && chunk.dataset.after !== chunks[index - 1].dataset.seq) break
159
+ text += chunk.textContent
160
+ }
161
+ return text
162
+ }
163
+ }
@@ -0,0 +1,9 @@
1
+ // dompurify@3.4.16 downloaded from https://ga.jspm.io/npm:dompurify@3.4.16/dist/purify.es.mjs
2
+
3
+ /*! @license DOMPurify 3.4.16 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.16/LICENSE */
4
+ function e(e,t){this.v=e,this.k=t}function t(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}function n(e){if(Array.isArray(e))return e}function r(e,t){var n=e==null?null:typeof Symbol<`u`&&e[Symbol.iterator]||e[`@@iterator`];if(n!=null){var r,i,a,o,s=[],c=!0,l=!1;try{if(a=(n=n.call(e)).next,t===0){if(Object(n)!==n)return;c=!1}else for(;!(c=(r=a.call(n)).done)&&(s.push(r.value),s.length!==t);c=!0);}catch(e){l=!0,i=e}finally{try{if(!c&&n.return!=null&&(o=n.return(),Object(o)!==o))return}finally{if(l)throw i}}return s}}function i(){throw TypeError(`Invalid attempt to destructure non-iterable instance.
5
+ In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}
6
+ /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
7
+ function a(e,t){return n(e)||r(e,t)||o(e,t)||i()}function o(e,n){if(e){if(typeof e==`string`)return t(e,n);var r={}.toString.call(e).slice(8,-1);return r===`Object`&&e.constructor&&(r=e.constructor.name),r===`Map`||r===`Set`?Array.from(e):r===`Arguments`||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?t(e,n):void 0}}function s(t){var n,r;function i(n,r){try{var o=t[n](r),s=o.value,c=s instanceof e;Promise.resolve(c?s.v:s).then(function(e){if(c){var r=n===`return`&&s.k?n:`next`;if(!s.k||e.done)return i(r,e);e=t[r](e).value}a(!!o.done,e)},function(e){i(`throw`,e)})}catch(e){a(2,e)}}function a(e,t){e===2?n.reject(t):n.resolve({value:t,done:e}),(n=n.next)?i(n.key,n.arg):r=null}this._invoke=function(e,t){return new Promise(function(a,o){var s={key:e,arg:t,resolve:a,reject:o,next:null};r?r=r.next=s:(n=r=s,i(e,t))})},typeof t.return!=`function`&&(this.return=void 0)}s.prototype[typeof Symbol==`function`&&Symbol.asyncIterator||`@@asyncIterator`]=function(){return this},s.prototype.next=function(e){return this._invoke(`next`,e)},s.prototype.throw=function(e){return this._invoke(`throw`,e)},s.prototype.return=function(e){return this._invoke(`return`,e)};const c=Object.entries,l=Object.setPrototypeOf,u=Object.isFrozen,d=Object.getPrototypeOf,f=Object.getOwnPropertyDescriptor;let p=Object.freeze,m=Object.seal,h=Object.create,ee=typeof Reflect<`u`&&Reflect,g=ee.apply,te=ee.construct;p||(p=function e(t){return t}),m||(m=function e(t){return t}),g||(g=function e(t,n){var r=[...arguments].slice(2);return t.apply(n,r)}),te||(te=function e(t){return new t(...[...arguments].slice(1))});const _=T(Array.prototype.forEach);Array.prototype.indexOf;const ne=T(Array.prototype.lastIndexOf),re=T(Array.prototype.pop),ie=T(Array.prototype.push);Array.prototype.slice;const ae=T(Array.prototype.splice),v=Array.isArray,oe=T(String.prototype.toLowerCase),se=T(String.prototype.toString),ce=T(String.prototype.match),le=T(String.prototype.replace),ue=T(String.prototype.indexOf),de=T(String.prototype.trim),fe=T(Number.prototype.toString),y=T(Boolean.prototype.toString),b=typeof BigInt>`u`?null:T(BigInt.prototype.toString),pe=typeof Symbol>`u`?null:T(Symbol.prototype.toString),x=T(Object.prototype.hasOwnProperty),S=T(Object.prototype.toString),C=T(RegExp.prototype.test),w=E(TypeError);function T(e){return function(t){t instanceof RegExp&&(t.lastIndex=0);var n=[...arguments].slice(1);return g(e,t,n)}}function E(e){return function(){return te(e,[...arguments])}}function D(e,t){let n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:oe;if(l&&l(e,null),!v(t))return e;let r=t.length;for(;r--;){let i=t[r];if(typeof i==`string`){let e=n(i);e!==i&&(u(t)||(t[r]=e),i=e)}e[i]=!0}return e}function me(e){for(let t=0;t<e.length;t++)x(e,t)||(e[t]=null);return e}function O(e){let t=h(null);for(let r of c(e)){var n=a(r,2);let i=n[0],o=n[1];x(e,i)&&(v(o)?t[i]=me(o):o&&typeof o==`object`&&o.constructor===Object?t[i]=O(o):t[i]=o)}return t}function he(e){switch(typeof e){case`string`:return e;case`number`:return fe(e);case`boolean`:return y(e);case`bigint`:return b?b(e):`0`;case`symbol`:return pe?pe(e):`Symbol()`;case`undefined`:return S(e);case`function`:case`object`:{if(e===null)return S(e);let t=e,n=k(t,`toString`);if(typeof n==`function`){let e=n(t);return typeof e==`string`?e:S(e)}return S(e)}default:return S(e)}}function k(e,t){for(;e!==null;){let n=f(e,t);if(n){if(n.get)return T(n.get);if(typeof n.value==`function`)return T(n.value)}e=d(e)}function n(){return null}return n}function ge(e){try{return C(e,``),!0}catch(e){return!1}}const _e=p(/* @__PURE__ */ `a.abbr.acronym.address.area.article.aside.audio.b.bdi.bdo.big.blink.blockquote.body.br.button.canvas.caption.center.cite.code.col.colgroup.content.data.datalist.dd.decorator.del.details.dfn.dialog.dir.div.dl.dt.element.em.fieldset.figcaption.figure.font.footer.form.h1.h2.h3.h4.h5.h6.head.header.hgroup.hr.html.i.img.input.ins.kbd.label.legend.li.main.map.mark.marquee.menu.menuitem.meter.nav.nobr.ol.optgroup.option.output.p.picture.pre.progress.q.rp.rt.ruby.s.samp.search.section.select.shadow.slot.small.source.spacer.span.strike.strong.style.sub.summary.sup.table.tbody.td.template.textarea.tfoot.th.thead.time.tr.track.tt.u.ul.var.video.wbr`.split(`.`)),ve=p(/* @__PURE__ */ `svg.a.altglyph.altglyphdef.altglyphitem.animatecolor.animatemotion.animatetransform.circle.clippath.defs.desc.ellipse.enterkeyhint.exportparts.filter.font.g.glyph.glyphref.hkern.image.inputmode.line.lineargradient.marker.mask.metadata.mpath.part.path.pattern.polygon.polyline.radialgradient.rect.stop.style.switch.symbol.text.textpath.title.tref.tspan.view.vkern`.split(`.`)),ye=p([`feBlend`,`feColorMatrix`,`feComponentTransfer`,`feComposite`,`feConvolveMatrix`,`feDiffuseLighting`,`feDisplacementMap`,`feDistantLight`,`feDropShadow`,`feFlood`,`feFuncA`,`feFuncB`,`feFuncG`,`feFuncR`,`feGaussianBlur`,`feImage`,`feMerge`,`feMergeNode`,`feMorphology`,`feOffset`,`fePointLight`,`feSpecularLighting`,`feSpotLight`,`feTile`,`feTurbulence`]),be=p([`animate`,`color-profile`,`cursor`,`discard`,`font-face`,`font-face-format`,`font-face-name`,`font-face-src`,`font-face-uri`,`foreignobject`,`hatch`,`hatchpath`,`mesh`,`meshgradient`,`meshpatch`,`meshrow`,`missing-glyph`,`script`,`set`,`solidcolor`,`unknown`,`use`]),xe=p(/* @__PURE__ */ `math.menclose.merror.mfenced.mfrac.mglyph.mi.mlabeledtr.mmultiscripts.mn.mo.mover.mpadded.mphantom.mroot.mrow.ms.mspace.msqrt.mstyle.msub.msup.msubsup.mtable.mtd.mtext.mtr.munder.munderover.mprescripts`.split(`.`)),Se=p([`maction`,`maligngroup`,`malignmark`,`mlongdiv`,`mscarries`,`mscarry`,`msgroup`,`mstack`,`msline`,`msrow`,`semantics`,`annotation`,`annotation-xml`,`mprescripts`,`none`]),Ce=p([`#text`]),we=p(/* @__PURE__ */ `accept.action.align.alt.autocapitalize.autocomplete.autopictureinpicture.autoplay.background.bgcolor.border.capture.cellpadding.cellspacing.checked.cite.class.clear.color.cols.colspan.command.commandfor.controls.controlslist.coords.crossorigin.datetime.decoding.default.dir.disabled.disablepictureinpicture.disableremoteplayback.download.draggable.enctype.enterkeyhint.exportparts.face.for.headers.height.hidden.high.href.hreflang.id.inert.inputmode.integrity.ismap.kind.label.lang.list.loading.loop.low.max.maxlength.media.method.min.minlength.multiple.muted.name.nonce.noshade.novalidate.nowrap.open.optimum.part.pattern.placeholder.playsinline.popover.popovertarget.popovertargetaction.poster.preload.pubdate.radiogroup.readonly.rel.required.rev.reversed.role.rows.rowspan.spellcheck.scope.selected.shape.size.sizes.slot.span.srclang.start.src.srcset.step.style.summary.tabindex.title.translate.type.usemap.valign.value.width.wrap.xmlns`.split(`.`)),Te=p(/* @__PURE__ */ `accent-height.accumulate.additive.alignment-baseline.amplitude.ascent.attributename.attributetype.azimuth.basefrequency.baseline-shift.begin.bias.by.class.clip.clippathunits.clip-path.clip-rule.color.color-interpolation.color-interpolation-filters.color-profile.color-rendering.cx.cy.d.dx.dy.diffuseconstant.direction.display.divisor.dominant-baseline.dur.edgemode.elevation.end.exponent.fill.fill-opacity.fill-rule.filter.filterunits.flood-color.flood-opacity.font-family.font-size.font-size-adjust.font-stretch.font-style.font-variant.font-weight.fx.fy.g1.g2.glyph-name.glyphref.gradientunits.gradienttransform.height.href.id.image-rendering.in.in2.intercept.k.k1.k2.k3.k4.kerning.keypoints.keysplines.keytimes.lang.lengthadjust.letter-spacing.kernelmatrix.kernelunitlength.lighting-color.local.marker-end.marker-mid.marker-start.markerheight.markerunits.markerwidth.maskcontentunits.maskunits.max.mask.mask-type.media.method.mode.min.name.numoctaves.offset.operator.opacity.order.orient.orientation.origin.overflow.paint-order.path.pathlength.patterncontentunits.patterntransform.patternunits.pointer-events.points.preservealpha.preserveaspectratio.primitiveunits.r.rx.ry.radius.refx.refy.repeatcount.repeatdur.restart.result.rotate.scale.seed.shape-rendering.slope.specularconstant.specularexponent.spreadmethod.startoffset.stddeviation.stitchtiles.stop-color.stop-opacity.stroke-dasharray.stroke-dashoffset.stroke-linecap.stroke-linejoin.stroke-miterlimit.stroke-opacity.stroke.stroke-width.style.surfacescale.systemlanguage.tabindex.tablevalues.targetx.targety.transform.transform-origin.text-anchor.text-decoration.text-orientation.text-rendering.textlength.type.u1.u2.unicode.values.vector-effect.viewbox.visibility.version.vert-adv-y.vert-origin-x.vert-origin-y.width.word-spacing.wrap.writing-mode.xchannelselector.ychannelselector.x.x1.x2.xmlns.y.y1.y2.z.zoomandpan`.split(`.`)),Ee=p(/* @__PURE__ */ `accent.accentunder.align.bevelled.close.columnalign.columnlines.columnspacing.columnspan.denomalign.depth.dir.display.displaystyle.encoding.fence.frame.height.href.id.largeop.length.linethickness.lquote.lspace.mathbackground.mathcolor.mathsize.mathvariant.maxsize.minsize.movablelimits.notation.numalign.open.rowalign.rowlines.rowspacing.rowspan.rspace.rquote.scriptlevel.scriptminsize.scriptsizemultiplier.selection.separator.separators.stretchy.subscriptshift.supscriptshift.symmetric.voffset.width.xmlns`.split(`.`)),De=p([`xlink:href`,`xml:id`,`xlink:title`,`xml:space`,`xmlns:xlink`]),Oe=m(/{{[\w\W]*|^[\w\W]*}}/g),ke=m(/<%[\w\W]*|^[\w\W]*%>/g),Ae=m(/\${[\w\W]*/g),je=m(/^data-[\-\w.\u00B7-\uFFFF]+$/),Me=m(/^aria-[\-\w]+$/),Ne=m(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),Pe=m(/^(?:\w+script|data):/i),Fe=m(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),Ie=m(/^html$/i),Le=m(/^[a-z][.\w]*(-[.\w]+)+$/i),Re=m(/<[/\w!]/g),ze=m(/<[/\w]/g),Be=m(/<\/no(script|embed|frames)/i),Ve=m(/\/>/i),A={element:1,attribute:2,text:3,cdataSection:4,entityReference:5,entityNode:6,processingInstruction:7,comment:8,document:9,documentType:10,documentFragment:11,notation:12},j=[`style`,`script`,`xmp`,`iframe`,`noembed`,`noframes`,`plaintext`,`noscript`],He=p(D({},j)),Ue=function(){let e={};return _(j,t=>{e[t]=m(RegExp(`</`+t+`(?=[\\t\\n\\f\\r />])`,`i`))}),p(e)}(),We=function e(){return typeof window>`u`?null:window},Ge=function e(t,n){if(typeof t!=`object`||typeof t.createPolicy!=`function`)return null;let r=null,i=`data-tt-policy-suffix`;n&&n.hasAttribute(i)&&(r=n.getAttribute(i));let a=`dompurify`+(r?`#`+r:``);try{return t.createPolicy(a,{createHTML(e){return e},createScriptURL(e){return e}})}catch(e){return console.warn(`TrustedTypes policy `+a+` could not be created.`),null}},Ke=function e(){return{afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},M=function e(t,n,r,i){return x(t,n)&&v(t[n])?D(i.base?O(i.base):{},t[n],i.transform):r},qe=function e(t,n,r){let i=x(t,n)?t[n]:void 0;return i&&typeof i==`object`?O(i):r()};function Je(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:We(),t=e=>Je(e);if(t.version=`3.4.16`,t.removed=[],!e||!e.document||e.document.nodeType!==A.document||!e.Element)return t.isSupported=!1,t;let n=e.document,r=n,i=r.currentScript;e.DocumentFragment;let a=e.HTMLTemplateElement,o=e.Node,s=e.Element,l=e.NodeFilter;e.NamedNodeMap===void 0&&(e.NamedNodeMap||e.MozNamedAttrMap),e.HTMLFormElement;let u=e.DOMParser,d=e.trustedTypes,f=s.prototype,ee=k(f,`cloneNode`),g=k(f,`remove`),te=k(f,`removeAttributeNode`),fe=k(f,`nextSibling`),y=k(f,`childNodes`),b=k(f,`parentNode`),pe=k(f,`shadowRoot`),S=k(f,`attributes`),T=o&&o.prototype?k(o.prototype,`nodeType`):null,E=o&&o.prototype?k(o.prototype,`nodeName`):null,me=o&&o.prototype?k(o.prototype,`ownerDocument`):null,j=function e(t){return T?T(t):t.nodeType},Ye=function e(t){return E?E(t):t.nodeName};if(typeof a==`function`){let e=n.createElement(`template`);e.content&&e.content.ownerDocument&&(n=e.content.ownerDocument)}let N,P=``,Xe,Ze=!1,Qe=0,$e=function e(){if(Qe>0)throw w(`A configured TRUSTED_TYPES_POLICY callback (createHTML or createScriptURL) must not call DOMPurify.sanitize, as that causes infinite recursion. Do not pass a policy whose callbacks wrap DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted Types" section of the README.`)},F=function e(t){$e(),Qe++;try{return N.createHTML(t)}finally{Qe--}},et=function e(t){$e(),Qe++;try{return N.createScriptURL(t)}finally{Qe--}},tt=function e(){return Ze||(Xe=Ge(d,i),Ze=!0),Xe},nt=n,rt=nt.implementation,it=nt.createNodeIterator,at=nt.createDocumentFragment,ot=nt.getElementsByTagName,st=r.importNode,I=Ke();t.isSupported=typeof c==`function`&&typeof b==`function`&&rt&&rt.createHTMLDocument!==void 0;let ct=Oe,lt=ke,ut=Ae,dt=je,ft=Me,pt=Pe,mt=Fe,ht=Le,gt=Ne,L=null,_t=D({},[..._e,...ve,...ye,...xe,...Ce]),R=null,vt=D({},[...we,...Te,...Ee,...De]),z=Object.seal(h(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),yt=null,bt=null,B=Object.seal(h(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}})),xt=!0,St=!0,Ct=!1,wt=!0,V=!1,H=!0,U=!1,Tt=!1,Et=null,Dt=null,Ot=!1,W=!1,kt=!1,At=!1,jt=!0,Mt=!1,Nt=`user-content-`,Pt=!0,Ft=!1,G={},K=null,It=D({},/* @__PURE__ */ `annotation-xml.audio.colgroup.desc.foreignobject.head.iframe.math.mi.mn.mo.ms.mtext.noembed.noframes.noscript.plaintext.script.selectedcontent.style.svg.template.thead.title.video.xmp`.split(`.`)),Lt=null,Rt=D({},[`audio`,`video`,`img`,`source`,`image`,`track`]),zt=null,Bt=D({},[`alt`,`class`,`for`,`id`,`label`,`name`,`pattern`,`placeholder`,`role`,`summary`,`title`,`value`,`style`,`xmlns`]),Vt=`http://www.w3.org/1998/Math/MathML`,Ht=`http://www.w3.org/2000/svg`,q=`http://www.w3.org/1999/xhtml`,J=q,Ut=!1,Wt=null,Gt=D({},[Vt,Ht,q],se),Kt=p([`mi`,`mo`,`mn`,`ms`,`mtext`]),qt=D({},Kt),Jt=p([`annotation-xml`]),Yt=D({},Jt),Xt=D({},[`title`,`style`,`font`,`a`,`script`]),Zt=null,Qt=[`application/xhtml+xml`,`text/html`],$t=`text/html`,Y=null,X=null,en=n.createElement(`form`),tn=function e(t){return t instanceof RegExp||t instanceof Function},nn=function e(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(X&&X===t)return;(!t||typeof t!=`object`)&&(t={}),t=O(t),Zt=Qt.indexOf(t.PARSER_MEDIA_TYPE)===-1?`text/html`:t.PARSER_MEDIA_TYPE,Y=Zt===`application/xhtml+xml`?se:oe,L=M(t,`ALLOWED_TAGS`,_t,{transform:Y}),R=M(t,`ALLOWED_ATTR`,vt,{transform:Y}),Wt=M(t,`ALLOWED_NAMESPACES`,Gt,{transform:se}),zt=M(t,`ADD_URI_SAFE_ATTR`,Bt,{transform:Y,base:Bt}),Lt=M(t,`ADD_DATA_URI_TAGS`,Rt,{transform:Y,base:Rt}),K=M(t,`FORBID_CONTENTS`,It,{transform:Y}),yt=M(t,`FORBID_TAGS`,O({}),{transform:Y}),bt=M(t,`FORBID_ATTR`,O({}),{transform:Y}),G=x(t,`USE_PROFILES`)?t.USE_PROFILES&&typeof t.USE_PROFILES==`object`?O(t.USE_PROFILES):t.USE_PROFILES:!1,xt=t.ALLOW_ARIA_ATTR!==!1,St=t.ALLOW_DATA_ATTR!==!1,Ct=t.ALLOW_UNKNOWN_PROTOCOLS||!1,wt=t.ALLOW_SELF_CLOSE_IN_ATTR!==!1,V=t.SAFE_FOR_TEMPLATES||!1,H=t.SAFE_FOR_XML!==!1,U=t.WHOLE_DOCUMENT||!1,W=t.RETURN_DOM||!1,kt=t.RETURN_DOM_FRAGMENT||!1,At=t.RETURN_TRUSTED_TYPE||!1,Ot=t.FORCE_BODY||!1,jt=t.SANITIZE_DOM!==!1,Mt=t.SANITIZE_NAMED_PROPS||!1,Pt=t.KEEP_CONTENT!==!1,Ft=t.IN_PLACE||!1,gt=ge(t.ALLOWED_URI_REGEXP)?t.ALLOWED_URI_REGEXP:Ne,J=typeof t.NAMESPACE==`string`?t.NAMESPACE:q,qt=qe(t,`MATHML_TEXT_INTEGRATION_POINTS`,()=>D({},Kt)),Yt=qe(t,`HTML_INTEGRATION_POINTS`,()=>D({},Jt));let n=qe(t,`CUSTOM_ELEMENT_HANDLING`,()=>h(null));if(z=h(null),x(n,`tagNameCheck`)&&tn(n.tagNameCheck)&&(z.tagNameCheck=n.tagNameCheck),x(n,`attributeNameCheck`)&&tn(n.attributeNameCheck)&&(z.attributeNameCheck=n.attributeNameCheck),x(n,`allowCustomizedBuiltInElements`)&&typeof n.allowCustomizedBuiltInElements==`boolean`&&(z.allowCustomizedBuiltInElements=n.allowCustomizedBuiltInElements),m(z),V&&(St=!1),kt&&(W=!0),G&&(L=D({},Ce),R=h(null),G.html===!0&&(D(L,_e),D(R,we)),G.svg===!0&&(D(L,ve),D(R,Te),D(R,De)),G.svgFilters===!0&&(D(L,ye),D(R,Te),D(R,De)),G.mathMl===!0&&(D(L,xe),D(R,Ee),D(R,De))),B.tagCheck=null,B.attributeCheck=null,x(t,`ADD_TAGS`)&&(typeof t.ADD_TAGS==`function`?B.tagCheck=t.ADD_TAGS:v(t.ADD_TAGS)&&(L===_t&&(L=O(L)),D(L,t.ADD_TAGS,Y))),x(t,`ADD_ATTR`)&&(typeof t.ADD_ATTR==`function`?B.attributeCheck=t.ADD_ATTR:v(t.ADD_ATTR)&&(R===vt&&(R=O(R)),D(R,t.ADD_ATTR,Y))),x(t,`ADD_FORBID_CONTENTS`)&&v(t.ADD_FORBID_CONTENTS)&&(K===It&&(K=O(K)),D(K,t.ADD_FORBID_CONTENTS,Y)),Pt&&(L[`#text`]=!0),U&&D(L,[`html`,`head`,`body`]),L.table&&(D(L,[`tbody`]),delete yt.tbody),t.TRUSTED_TYPES_POLICY){if(typeof t.TRUSTED_TYPES_POLICY.createHTML!=`function`)throw w(`TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.`);if(typeof t.TRUSTED_TYPES_POLICY.createScriptURL!=`function`)throw w(`TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.`);let e=N;N=t.TRUSTED_TYPES_POLICY;try{P=F(``)}catch(t){throw N=e,t}}else t.TRUSTED_TYPES_POLICY===null?(N=void 0,P=``):(N===void 0&&(N=tt()),N&&typeof P==`string`&&(P=F(``)));p&&p(t),X=t},rn=D({},[...ve,...ye,...be]),an=D({},[...xe,...Se]),on=function e(t,n,r){return n.namespaceURI===q?t===`svg`:n.namespaceURI===Vt?t===`svg`&&(r===`annotation-xml`||qt[r]):!!rn[t]},sn=function e(t,n,r){return n.namespaceURI===q?t===`math`:n.namespaceURI===Ht?t===`math`&&Yt[r]:!!an[t]},cn=function e(t,n,r){return n.namespaceURI===Ht&&!Yt[r]||n.namespaceURI===Vt&&!qt[r]?!1:!an[t]&&(Xt[t]||!rn[t])},ln=function e(t){let n=b(t);(!n||!n.tagName)&&(n={namespaceURI:J,tagName:`template`});let r=oe(t.tagName),i=oe(n.tagName);return Wt[t.namespaceURI]?t.namespaceURI===Ht?on(r,n,i):t.namespaceURI===Vt?sn(r,n,i):t.namespaceURI===q?cn(r,n,i):!!(Zt===`application/xhtml+xml`&&Wt[t.namespaceURI]):!1},Z=function e(n){ie(t.removed,{element:n});try{b(n).removeChild(n)}catch(e){if(g(n),!b(n))throw w(`a node selected for removal could not be detached from its tree and cannot be safely returned; refusing to sanitize in place`)}},un=function e(t,n,r){try{te(t,n)}catch(e){try{t.removeAttribute(r)}catch(e){}}},dn=function e(t){pn(t);let n=y(t);if(n){let e=[];_(n,t=>{ie(e,t)}),_(e,e=>{try{g(e)}catch(e){}})}let r=S(t);if(r)for(let e=r.length-1;e>=0;--e){let n=r[e],i=n&&n.name;typeof i==`string`&&un(t,n,i)}},Q=function e(n,r,i){if(!i)try{i=r.getAttributeNode(n)}catch(e){i=null}ie(t.removed,{attribute:i||null,from:r});try{i?te(r,i):r.removeAttribute(n)}catch(e){try{r.removeAttribute(n)}catch(e){}}if(n===`is`)if(W||kt)try{Z(r)}catch(e){}else try{r.setAttribute(n,``)}catch(e){}},fn=function e(t){let n=S(t);if(n)for(let e=n.length-1;e>=0;--e){let r=n[e],i=r&&r.name;typeof i!=`string`||R[Y(i)]||un(t,r,i)}},pn=function e(t){let n=[t];for(;n.length>0;){let e=n.pop();j(e)===A.element&&fn(e);let t=y(e);if(t)for(let e=t.length-1;e>=0;--e)n.push(t[e])}},mn=function e(t,n){return H?t===`patchsrc`?!0:t===`for`&&n!==`label`&&n!==`output`:!1},hn=function e(t){if(!H)return;let n=[t];for(;n.length>0;){let e=n.pop(),t=j(e);if(t===A.processingInstruction||t===A.comment&&C(ze,e.data)){try{g(e)}catch(e){}continue}if(t===A.element){let t=e,n=Y(Ye(e));try{t.hasAttribute&&t.hasAttribute(`patchsrc`)&&t.removeAttribute(`patchsrc`),t.hasAttribute&&t.hasAttribute(`for`)&&mn(`for`,n)&&t.removeAttribute(`for`)}catch(e){}}let r=y(e);if(r)for(let e=r.length-1;e>=0;--e)n.push(r[e])}},gn=function e(t){let r=null,i=null;if(Ot)t=`<remove></remove>`+t;else{let e=ce(t,/^[\r\n\t ]+/);i=e&&e[0]}Zt===`application/xhtml+xml`&&J===q&&(t=`<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>`+t+`</body></html>`);let a=N?F(t):t;if(J===q)try{r=new u().parseFromString(a,Zt)}catch(e){}if(!r||!r.documentElement){r=rt.createDocument(J,`template`,null);try{r.documentElement.innerHTML=Ut?P:a}catch(e){}}let o=r.body||r.documentElement;return t&&i&&o.insertBefore(n.createTextNode(i),o.childNodes[0]||null),J===q?ot.call(r,U?`html`:`body`)[0]:U?r.documentElement:o},_n=function e(t){let n=me?me(t):t.ownerDocument;return it.call(n||t,t,l.SHOW_ELEMENT|l.SHOW_COMMENT|l.SHOW_TEXT|l.SHOW_PROCESSING_INSTRUCTION|l.SHOW_CDATA_SECTION,null)},vn=function e(t){return t=le(t,ct,` `),t=le(t,lt,` `),t=le(t,ut,` `),t},yn=function e(t){var n;t.normalize();let r=me?me(t):t.ownerDocument,i=it.call(r||t,t,l.SHOW_TEXT|l.SHOW_COMMENT|l.SHOW_CDATA_SECTION|l.SHOW_PROCESSING_INSTRUCTION,null),a=i.nextNode();for(;a;)a.data=vn(a.data),a=i.nextNode();let o=(n=t.querySelectorAll)==null?void 0:n.call(t,`template`);o&&_(o,e=>{xn(e.content)&&yn(e.content)})},bn=function e(t){let n=E?E(t):null;return typeof n!=`string`||Y(n)!==`form`?!1:typeof t.nodeName!=`string`||typeof t.textContent!=`string`||typeof t.removeChild!=`function`||t.attributes!==S(t)||typeof t.removeAttribute!=`function`||typeof t.removeAttributeNode!=`function`||typeof t.getAttributeNode!=`function`||typeof t.setAttribute!=`function`||typeof t.namespaceURI!=`string`||typeof t.insertBefore!=`function`||typeof t.hasChildNodes!=`function`||t.nodeType!==T(t)||t.childNodes!==y(t)},xn=function e(t){if(!T||typeof t!=`object`||!t)return!1;try{return T(t)===A.documentFragment}catch(e){return!1}},Sn=function e(t){if(!T||typeof t!=`object`||!t)return!1;try{return typeof T(t)==`number`}catch(e){return!1}};function $(e,n,r){e.length!==0&&_(e,e=>{e.call(t,n,r,X)})}let Cn=function e(t,n){return!!(H&&t.hasChildNodes()&&!Sn(t.firstElementChild)&&C(Re,t.textContent)&&C(Re,t.innerHTML)||H&&t.namespaceURI===q&&He[n]&&(Sn(t.firstElementChild)||typeof t.textContent==`string`&&C(Ue[n],t.textContent))||t.nodeType===A.processingInstruction||H&&t.nodeType===A.comment&&C(ze,t.data))},wn=function e(t,n){return t instanceof RegExp?C(t,n):t instanceof Function?!!t(n,...[...arguments].slice(2)):!1},Tn=function e(t,n,r){if(!yt[n]&&jn(n)&&wn(z.tagNameCheck,n))return!1;if(Pt&&!K[n]){let e=b(t),n=y(t);if(n&&e){let i=n.length;for(let a=i-1;a>=0;--a){let i=t===r?ee(n[a],!0):n[a];e.insertBefore(i,fe(t))}}}return Z(t),!0},En=function e(t,n,r,i){return t.length===0?n:n===r||n===i?O(n):n},Dn=function e(t,n){return t===n||b(t)!==null?!1:(Ft&&pn(t),!0)},On=function e(n,r){if($(I.beforeSanitizeElements,n,null),Dn(n,r))return!0;if(bn(n))return Z(n),!0;let i=Y(Ye(n));if(L=En(I.uponSanitizeElement,L,_t,Et),$(I.uponSanitizeElement,n,{tagName:i,allowedTags:L}),Dn(n,r))return!0;if(Cn(n,i))return Z(n),!0;if(yt[i]||!(B.tagCheck instanceof Function&&B.tagCheck(i))&&!L[i]){let e=Tn(n,i,r);return e===!1&&($(I.afterSanitizeElements,n,null),Dn(n,r))?!0:e}if(j(n)===A.element&&!ln(n)||(i===`noscript`||i===`noembed`||i===`noframes`)&&C(Be,n.innerHTML))return Z(n),!0;if(V&&n.nodeType===A.text){let e=vn(n.textContent);n.textContent!==e&&(ie(t.removed,{element:n.cloneNode()}),n.textContent=e)}return $(I.afterSanitizeElements,n,null),Dn(n,r)},kn=function e(t,r,i){if(bt[r]||mn(r,t)||jt&&(r===`id`||r===`name`)&&(i in n||i in en))return!1;let a=R[r]||B.attributeCheck instanceof Function&&B.attributeCheck(r,t);return St&&C(dt,r)||xt&&C(ft,r)?!0:a?zt[r]||C(gt,le(i,mt,``))||(r===`src`||r===`xlink:href`||r===`href`)&&t!==`script`&&ue(i,`data:`)===0&&Lt[t]||Ct&&!C(pt,le(i,mt,``))?!0:!i:jn(t)&&wn(z.tagNameCheck,t)&&wn(z.attributeNameCheck,r,t)||r===`is`&&z.allowCustomizedBuiltInElements&&wn(z.tagNameCheck,i)},An=D({},[`annotation-xml`,`color-profile`,`font-face`,`font-face-format`,`font-face-name`,`font-face-src`,`font-face-uri`,`missing-glyph`]),jn=function e(t){return!An[oe(t)]&&C(ht,t)},Mn=function e(t,n,r,i){if(N&&typeof d==`object`&&typeof d.getAttributeType==`function`&&!r)switch(d.getAttributeType(t,n)){case`TrustedHTML`:return F(i);case`TrustedScriptURL`:return et(i)}return i},Nn=function e(t,n,r,i){try{return r?t.setAttributeNS(r,n,i):t.setAttribute(n,i),bn(t)?(Z(t),!1):!0}catch(e){return Q(n,t),!1}},Pn=function e(n,r){if($(I.beforeSanitizeAttributes,n,null),Dn(n,r))return;let i=n.attributes;if(!i||bn(n))return;R=En(I.uponSanitizeAttribute,R,vt,Dt);let a={attrName:``,attrValue:``,keepAttr:!0,allowedAttributes:R,forceKeepAttr:void 0},o=i.length,s=Y(n.nodeName);for(;o--;){let e=i[o],r=e.name,c=e.namespaceURI,l=e.value,u=Y(r),d=l,f=r===`value`?d:de(d),p=!1;if(a.attrName=u,a.attrValue=f,a.keepAttr=!0,a.forceKeepAttr=void 0,$(I.uponSanitizeAttribute,n,a),f=a.attrValue,Mt&&(u===`id`||u===`name`)&&ue(f,Nt)!==0&&(Q(r,n,e),f=Nt+f,p=!0),H&&C(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i,f)){Q(r,n,e);continue}if(u===`attributename`&&ce(f,`href`)){Q(r,n,e);continue}if(!a.forceKeepAttr){if(!a.keepAttr){Q(r,n,e);continue}if(!wt&&C(Ve,f)){Q(r,n,e);continue}if(V&&(f=vn(f)),!kn(s,u,f)){Q(r,n,e);continue}f=Mn(s,u,c,f),f!==d&&Nn(n,r,c,f)&&p&&re(t.removed)}}$(I.afterSanitizeAttributes,n,null),Dn(n,r)},Fn=function e(t){let n=null,r=_n(t);for($(I.beforeSanitizeShadowDOM,t,null);n=r.nextNode();)if($(I.uponSanitizeShadowNode,n,null),On(n,t),Pn(n,t),xn(n.content)&&Fn(n.content),j(n)===A.element){let e=pe(n);xn(e)&&(In(e),Fn(e))}$(I.afterSanitizeShadowDOM,t,null)},In=function e(t){let n=[{node:t,shadow:null}];for(;n.length>0;){let e=n.pop();if(e.shadow){Fn(e.shadow);continue}let t=e.node,r=j(t)===A.element,i=y(t);if(i)for(let e=i.length-1;e>=0;--e)n.push({node:i[e],shadow:null});if(r){let e=E?E(t):null;if(typeof e==`string`&&Y(e)===`template`){let e=t.content;xn(e)&&n.push({node:e,shadow:null})}}if(r){let e=pe(t);xn(e)&&n.push({node:null,shadow:e},{node:e,shadow:null})}}};return t.sanitize=function(e){let n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},i=null,a=null,o=null,s=null;if(Ut=!e,Ut&&(e=`<!-->`),typeof e!=`string`&&!Sn(e)&&(e=he(e),typeof e!=`string`))throw w(`dirty is not a string, aborting`);if(!t.isSupported)return e;Tt?(L=Et,R=Dt):nn(n),(I.uponSanitizeElement.length>0||I.uponSanitizeAttribute.length>0)&&(L=O(L)),I.uponSanitizeAttribute.length>0&&(R=O(R)),t.removed=[];let c=Ft&&typeof e!=`string`&&Sn(e);if(c){hn(e);let t=Ye(e);if(typeof t==`string`){let n=Y(t);if(!L[n]||yt[n])throw dn(e),w(`root node is forbidden and cannot be sanitized in-place`)}if(bn(e))throw dn(e),w(`root node is clobbered and cannot be sanitized in-place`);try{In(e)}catch(t){throw dn(e),t}}else if(Sn(e))i=gn(`<!---->`),a=i.ownerDocument.importNode(e,!0),a.nodeType===A.element&&a.nodeName===`BODY`||a.nodeName===`HTML`?i=a:i.appendChild(a),In(i);else{if(!W&&!V&&!U&&e.indexOf(`<`)===-1)return N&&At?F(e):e;if(i=gn(e),!i)return W?null:At?P:``}i&&Ot&&Z(i.firstChild);let l=c?e:i;try{let e=_n(l);for(;o=e.nextNode();)On(o,l),Pn(o,l),xn(o.content)&&Fn(o.content)}catch(n){throw c&&(dn(e),_(t.removed,e=>{e.element&&pn(e.element)})),n}if(c){let n=!1;if(_(t.removed,t=>{t.element&&(t.element===e&&(n=!0),pn(t.element))}),n)throw w(`a node selected for removal could not be safely returned; refusing to sanitize in place`);return V&&yn(e),e}if(W){if(V&&yn(i),kt)for(s=at.call(i.ownerDocument);i.firstChild;)s.appendChild(i.firstChild);else s=i;return(R.shadowroot||R.shadowrootmode)&&(s=st.call(r,s,!0)),s}let u=U?i.outerHTML:i.innerHTML;return U&&L[`!doctype`]&&i.ownerDocument&&i.ownerDocument.doctype&&i.ownerDocument.doctype.name&&C(Ie,i.ownerDocument.doctype.name)&&(u=`<!DOCTYPE `+i.ownerDocument.doctype.name+`>
8
+ `+u),V&&(u=vn(u)),N&&At?F(u):u},t.setConfig=function(){nn(arguments.length>0&&arguments[0]!==void 0?arguments[0]:{}),Tt=!0,Et=L,Dt=R},t.clearConfig=function(){X=null,Tt=!1,Et=null,Dt=null,N=Xe,P=``},t.isValidAttribute=function(e,t,n){return X||nn({}),kn(Y(e),Y(t),n)},t.addHook=function(e,t){typeof t==`function`&&x(I,e)&&ie(I[e],t)},t.removeHook=function(e,t){if(x(I,e)){if(t!==void 0){let n=ne(I[e],t);return n===-1?void 0:ae(I[e],n,1)[0]}return re(I[e])}},t.removeHooks=function(e){x(I,e)&&(I[e]=[])},t.removeAllHooks=function(){I=Ke()},t}var Ye=Je();export{Ye as default};
9
+