ruby_llm 2.1.1 → 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 +160 -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 +515 -274
  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 +1593 -331
  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 +50 -24
  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 +127 -106
  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 +15 -15
  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 +1 -7
  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 +5 -14
  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 +213 -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: 8000141c827d6b8f899bcf2ec5a23153ecf83f8196fcd35cee7af8de2f9e58e4
4
- data.tar.gz: '069b322ad8df77cd46ee2dd9b72ec02578281ff9e6c96b349e46aff911ee85b8'
3
+ metadata.gz: e784b5ab43c18ac17675ffa3e46c802934a9f8eaa4ef733bd1e7c450c462fff5
4
+ data.tar.gz: 69d10ad8492781735ce7b50d56476bd39d3cf405e9218adb819e735712144761
5
5
  SHA512:
6
- metadata.gz: 6d651244192242fc5f06cf8126932659b46dec1228ed97e3a9876dd7ee581f8972d42fbaca209698b1c42e1704ebb766ca07b450cfd7651d9192f8d46d3f8c42
7
- data.tar.gz: 9511e7a1c10a8b73cebbefb59a26be1fd7dcec10c369f57d60b7a348d180a8037700826400d9da41b630d190b940aa73ba7c5cbdb7fbd0f4b6cffbcbe563d732
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
 
@@ -28,15 +41,145 @@ https://github.com/user-attachments/assets/519fcb20-ab8c-4af3-9185-0953b3ba7b81
28
41
 
29
42
  ---
30
43
 
31
- ## 19 providers. One Ruby API.
44
+ ## 23 providers. One API.
32
45
 
33
46
  Build with the models you want. Move between hosted and local providers without rewriting your application, or connect an OpenAI-compatible endpoint.
34
47
 
35
48
  [Browse models and pricing](https://rubyllm.com/available-models/) · [Connect a provider](https://rubyllm.com/configuration-providers/)
36
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
+
37
180
  ## Start with one line. Add files, tools, and agents
38
181
 
39
- These examples use **2.1.1**. 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/).
40
183
 
41
184
  ```ruby
42
185
  # Just ask
@@ -80,7 +223,7 @@ video.save "paper_boat.mp4"
80
223
  ```ruby
81
224
  # Create embeddings
82
225
  embedding = RubyLLM.embed "Ruby is elegant and expressive"
83
- embedding.vectors
226
+ embedding.vector
84
227
  ```
85
228
 
86
229
  ```ruby
@@ -139,7 +282,7 @@ chat.with_tools(Weather).ask "What's the weather in Berlin?"
139
282
  ```ruby
140
283
  # Define an agent with instructions + tools
141
284
  class WeatherAssistant < RubyLLM::Agent
142
- model "gpt-5.6-luna"
285
+ model "gpt-6-luna"
143
286
  instructions "Be concise and always use tools for weather."
144
287
  tools Weather
145
288
  end
@@ -161,97 +304,12 @@ response = chat.with_schema(ProductSchema).ask "Analyze this product", with: "pr
161
304
  response.parsed
162
305
  ```
163
306
 
164
- ## Evaluate your agents
165
-
166
- Keep questions and expected answers in a dataset. RubyLLM checks correctness by default. Run the same evaluation from Ruby, RSpec, Minitest, or Rake.
167
-
168
- Evaluations are available on `main`. Follow the [development installation guide](https://rubyllm.com/next/getting-started/) to try them.
169
-
170
- `app/agents/support_agent.rb`
171
-
172
- ```ruby
173
- class SupportAgent < RubyLLM::Agent
174
- instructions "Accept returns of unopened items within 30 days."
175
- end
176
- ```
177
-
178
- `app/evals/support_evaluation.rb`
179
-
180
- ```ruby
181
- class SupportEvaluation < RubyLLM::Evaluation
182
- def perform(input)
183
- SupportAgent.new.ask(input)
184
- end
185
- end
186
- ```
187
-
188
- `app/evals/support_evaluation.yml`
189
-
190
- ```yaml
191
- cases:
192
- - name: within_return_window
193
- inputs: Can I return an unopened item after 14 days?
194
- expected_output: Yes, unopened items can be returned within 30 days.
195
- - name: past_return_window
196
- inputs: Can I return an unopened item after 45 days?
197
- expected_output: No, the 30-day return window has passed.
198
- ```
199
-
200
- Run the evaluation:
201
-
202
- ```ruby
203
- report = SupportEvaluation.run
204
- report.passed?
205
- report.cost.total
206
- ```
207
-
208
- 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/).
209
-
210
- ## A complete AI framework for Ruby
211
-
212
- Agents, evaluations, workflows, RAG, images, audio, and video. Built in, with usage tracking and Rails integration to bring them into your app.
213
-
214
- * **Chat:** Conversational AI with `RubyLLM.chat`
215
- * **Vision:** Analyze images and videos
216
- * **Audio:** Transcribe speech with `RubyLLM.transcribe` and generate it with `RubyLLM.speak`
217
- * **Documents:** Ask questions about PDFs, text files, and other supported formats
218
- * **OCR:** Turn documents into markdown with `RubyLLM.ocr`
219
- * **Image generation:** Create images with `RubyLLM.paint`
220
- * **Video generation:** Create videos with `RubyLLM.animate`
221
- * **Embeddings:** Generate embeddings with `RubyLLM.embed`
222
- * **Reranking:** Order retrieval candidates by relevance with `RubyLLM.rerank`
223
- * **Moderation:** Content flags, categories, and scores with `RubyLLM.moderate`
224
- * **Judgments:** Probabilities, choices, and scores with `RubyLLM::Judge` and `RubyLLM.judge`
225
- * **Tools:** Let AI call your Ruby methods
226
- * **Tool approval:** Park a run until a human approves with `requires_approval`
227
- * **The agentic loop:** Drive it yourself with `ask_later`, `step`, and `complete?`
228
- * **Provider tools:** Web search, code execution, and MCP connectors with `with_provider_tools`
229
- * **Agents:** Reusable assistants with `RubyLLM::Agent`
230
- * **Evaluations (main):** Reusable datasets, model criteria, and Ruby assertions with [`RubyLLM::Evaluation`](https://rubyllm.com/next/evaluations/)
231
- * **Prompt templates:** ERB prompts in `app/prompts`, rendered with `RubyLLM.render_prompt`
232
- * **Workflows:** Correlate multi-agent runs in your telemetry with `RubyLLM.workflow`
233
- * **Structured output:** Define a Ruby schema and read the result with `response.parsed`
234
- * **Streaming:** Real-time responses with blocks
235
- * **Rails:** Active Record persistence, Active Storage attachments, Hotwire streaming, and generators
236
- * **Files:** Upload once and reuse across chats with `RubyLLM.upload`
237
- * **Prompt caching:** Turn on the provider's cache with `with_caching` and `cache_until_here`
238
- * **Fallbacks and cancellation:** Retry on backup models with `with_fallbacks`, stop a run with `cancel`
239
- * **Cost tracking:** A per-attempt usage ledger behind `chat.tokens` and `chat.cost`
240
- * **Async:** Fiber-based concurrency
241
- * **Model registry:** Browse capabilities, limits, and pricing across providers
242
- * **Extended thinking:** Control, view, and persist model deliberation
243
- * **Citations:** Normalized source citations from documents, search, and grounding
244
- * **Batches:** Provider-side batch processing with provider-specific discounts via `RubyLLM.batch`
245
- * **Compaction:** Let providers condense long conversations with `with_compaction`
246
- * **Token counting:** Count a request before you send it with `count_tokens`
247
- * **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
248
-
249
307
  ## Installation
250
308
 
251
- Install RubyLLM 2.1:
309
+ Install RubyLLM 2.2:
252
310
 
253
311
  ```bash
254
- bundle add ruby_llm --version 2.1.1
312
+ bundle add ruby_llm --version 2.2.0
255
313
  ```
256
314
 
257
315
  Configure a provider in your script, or in `config/initializers/ruby_llm.rb` in Rails:
@@ -266,42 +324,24 @@ end
266
324
 
267
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/).
268
326
 
269
- ## 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/).
270
328
 
271
- 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
272
330
 
273
- 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:
274
332
 
275
333
  ```bash
276
- # Install Rails Integration
277
- bin/rails generate ruby_llm:install
278
- bin/rails db:migrate
279
- bin/rails ruby_llm:load_models
280
-
281
- # Add Chat UI (optional)
282
- bin/rails generate ruby_llm:chat_ui
334
+ gem exec skill add "$(bundle show ruby_llm)" --skill rubyllm --agents coding
283
335
  ```
284
336
 
285
- ```ruby
286
- class Chat < ApplicationRecord
287
- acts_as_chat
288
- 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:
289
338
 
290
- chat = Chat.create! model: "gpt-5.6-luna"
291
- chat.ask "What's in this file?", with: "report.pdf"
292
339
  ```
293
-
294
- 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.
295
-
296
- ## AI coding assistants
297
-
298
- Give your coding assistant the RubyLLM API and documentation that match your application. From your application directory, install the skill packaged with your gem:
299
-
300
- ```bash
301
- npx skills add "$(bundle show ruby_llm)" --skill rubyllm
340
+ /plugin marketplace add crmne/ruby_llm
341
+ /plugin install rubyllm@ruby_llm
302
342
  ```
303
343
 
304
- 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.
305
345
 
306
346
  ## Documentation
307
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
+