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.
- checksums.yaml +4 -4
- data/README.md +162 -120
- data/app/assets/javascripts/ruby_llm/chat_message_controller.js +163 -0
- data/app/assets/javascripts/ruby_llm/dompurify.js +9 -0
- data/app/assets/javascripts/ruby_llm/marked.js +71 -0
- data/config/importmap.rb +5 -0
- data/lib/generators/ruby_llm/agent/agent_generator.rb +11 -3
- data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +6 -3
- data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +201 -55
- data/lib/generators/ruby_llm/chat_ui/templates/agents/chat_agent.rb.tt +15 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/answers_controller.rb.tt +31 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +16 -5
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +27 -9
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/queued_messages_controller.rb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/javascript/chat_message_controller.js +1 -0
- data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +5 -8
- data/lib/generators/ruby_llm/chat_ui/templates/jobs/resume_interrupted_chats_job.rb.tt +7 -0
- data/lib/generators/ruby_llm/chat_ui/templates/migrations/add_reply_error_migration.rb.tt +5 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_files.html.erb.tt +15 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +5 -7
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_model_select.html.erb.tt +7 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +32 -3
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +17 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_attachments.html.erb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +28 -18
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_queued_message.html.erb.tt +18 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +4 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +4 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +4 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/questions/_question.html.erb.tt +101 -0
- data/lib/generators/ruby_llm/chat_ui/templates/test/agents/chat_agent_test.rb.tt +28 -0
- data/lib/generators/ruby_llm/chat_ui/templates/test/application_system_test_case.rb.tt +5 -0
- data/lib/generators/ruby_llm/chat_ui/templates/test/system/chats_test.rb.tt +240 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/answers/create.turbo_stream.erb.tt +6 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_files.html.erb.tt +15 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +5 -7
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_model_select.html.erb.tt +7 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_questions.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +29 -3
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +16 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_attachments.html.erb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_error.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +26 -16
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_queued_message.html.erb.tt +8 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +4 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +3 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +1 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +3 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/questions/_question.html.erb.tt +78 -0
- data/lib/generators/ruby_llm/evaluation/evaluation_generator.rb +43 -0
- data/lib/generators/ruby_llm/evaluation/templates/evaluation.rb.tt +9 -0
- data/lib/generators/ruby_llm/evaluation/templates/evaluation.yml.tt +4 -0
- data/lib/generators/ruby_llm/generator_helpers.rb +116 -3
- data/lib/generators/ruby_llm/install/install_generator.rb +5 -47
- data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +4 -0
- data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +5 -3
- data/lib/generators/ruby_llm/install/templates/create_ruby_llm_records_migration.rb.tt +41 -1
- data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +2 -1
- data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +1 -2
- data/lib/generators/ruby_llm/judge/judge_generator.rb +35 -0
- data/lib/generators/ruby_llm/judge/templates/judge.rb.tt +2 -0
- data/lib/generators/ruby_llm/mcp/mcp_generator.rb +58 -0
- data/lib/generators/ruby_llm/mcp/templates/mcp.rb.tt +7 -0
- data/lib/generators/ruby_llm/provider/scaffold.rb +5 -12
- data/lib/generators/ruby_llm/provider/templates/core/provider.rb.erb +6 -0
- data/lib/generators/ruby_llm/provider/templates/gem/chat_spec.rb.erb +0 -1
- data/lib/generators/ruby_llm/provider/templates/gem/chat_streaming_spec.rb.erb +0 -1
- data/lib/generators/ruby_llm/provider/templates/gem/moderation_spec.rb.erb +1 -1
- data/lib/generators/ruby_llm/sandbox/sandbox_generator.rb +68 -0
- data/lib/generators/ruby_llm/sandbox/templates/sandbox.rb.tt +3 -0
- data/lib/generators/ruby_llm/skill/skill_generator.rb +48 -0
- data/lib/generators/ruby_llm/tool/templates/tailwind/tool_call.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/tool/templates/tailwind/tool_result.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +0 -3
- data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/tool/tool_generator.rb +1 -5
- data/lib/generators/ruby_llm/upgrade/templates/upgrade_ruby_llm_to_2_2.rb.tt +164 -0
- data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +41 -3
- data/lib/ruby_llm/accounting/operation.rb +73 -0
- data/lib/ruby_llm/accounting/usage.rb +74 -24
- data/lib/ruby_llm/active_record/acts_as.rb +78 -11
- data/lib/ruby_llm/active_record/attachment_helpers.rb +10 -4
- data/lib/ruby_llm/active_record/batch.rb +67 -9
- data/lib/ruby_llm/active_record/chat_agent_hooks.rb +17 -0
- data/lib/ruby_llm/active_record/chat_approvals.rb +71 -0
- data/lib/ruby_llm/active_record/chat_cancellation.rb +106 -0
- data/lib/ruby_llm/active_record/chat_column_guard.rb +51 -0
- data/lib/ruby_llm/active_record/chat_file.rb +37 -0
- data/lib/ruby_llm/active_record/chat_files.rb +30 -0
- data/lib/ruby_llm/active_record/chat_helper.rb +73 -0
- data/lib/ruby_llm/active_record/chat_methods.rb +522 -277
- data/lib/ruby_llm/active_record/chat_placeholders.rb +67 -0
- data/lib/ruby_llm/active_record/chat_questions.rb +177 -0
- data/lib/ruby_llm/active_record/chat_queue.rb +120 -0
- data/lib/ruby_llm/active_record/chat_reply_error.rb +35 -0
- data/lib/ruby_llm/active_record/chat_run.rb +51 -0
- data/lib/ruby_llm/active_record/chat_tool_call_state.rb +77 -0
- data/lib/ruby_llm/active_record/chunk_methods.rb +49 -0
- data/lib/ruby_llm/active_record/claim_heartbeat.rb +149 -0
- data/lib/ruby_llm/active_record/file_store.rb +269 -0
- data/lib/ruby_llm/active_record/mcp_credential.rb +24 -9
- data/lib/ruby_llm/active_record/message_broadcasting.rb +85 -0
- data/lib/ruby_llm/active_record/message_methods.rb +83 -11
- data/lib/ruby_llm/active_record/model.rb +37 -9
- data/lib/ruby_llm/active_record/provider_file.rb +21 -4
- data/lib/ruby_llm/active_record/queued_message.rb +50 -0
- data/lib/ruby_llm/active_record/record.rb +3 -1
- data/lib/ruby_llm/active_record/run_claims.rb +608 -0
- data/lib/ruby_llm/active_record/solid_queue_shutdown.rb +33 -0
- data/lib/ruby_llm/active_record/system_instructions.rb +30 -0
- data/lib/ruby_llm/active_record/tool_call.rb +55 -4
- data/lib/ruby_llm/active_record/usage.rb +79 -9
- data/lib/ruby_llm/agent/callbacks.rb +98 -0
- data/lib/ruby_llm/agent/context_budget.rb +26 -0
- data/lib/ruby_llm/agent/join_callbacks.rb +28 -0
- data/lib/ruby_llm/agent/model_options.rb +56 -0
- data/lib/ruby_llm/agent/ownership.rb +22 -0
- data/lib/ruby_llm/agent/tool_conversion.rb +19 -0
- data/lib/ruby_llm/agent.rb +620 -236
- data/lib/ruby_llm/agent_tool.rb +91 -0
- data/lib/ruby_llm/aliases.json +84 -91
- data/lib/ruby_llm/attachment.rb +101 -20
- data/lib/ruby_llm/batch.rb +132 -46
- data/lib/ruby_llm/cached_content.rb +4 -4
- data/lib/ruby_llm/chat/agent_run.rb +22 -0
- data/lib/ruby_llm/chat/context_budget.rb +119 -0
- data/lib/ruby_llm/chat/file_references.rb +259 -0
- data/lib/ruby_llm/chat/message_queue.rb +56 -0
- data/lib/ruby_llm/chat/ownership.rb +18 -0
- data/lib/ruby_llm/chat/rescuable.rb +48 -0
- data/lib/ruby_llm/chat/run.rb +32 -0
- data/lib/ruby_llm/chat/tool_call_ids.rb +46 -0
- data/lib/ruby_llm/chat/tool_call_state.rb +115 -0
- data/lib/ruby_llm/chat/tool_concurrency.rb +90 -49
- data/lib/ruby_llm/chat/tool_search.rb +36 -1
- data/lib/ruby_llm/chat/tool_sequences.rb +84 -0
- data/lib/ruby_llm/chat.rb +1598 -332
- data/lib/ruby_llm/chunk.rb +27 -1
- data/lib/ruby_llm/citation.rb +2 -1
- data/lib/ruby_llm/code_execution/language.rb +28 -0
- data/lib/ruby_llm/code_execution/node_installation.rb +56 -0
- data/lib/ruby_llm/code_execution/packages.rb +241 -0
- data/lib/ruby_llm/code_execution/ruby_installation.rb +170 -0
- data/lib/ruby_llm/code_execution/workspace.rb +314 -0
- data/lib/ruby_llm/code_execution.rb +781 -0
- data/lib/ruby_llm/configuration.rb +151 -4
- data/lib/ruby_llm/content_filter.rb +68 -0
- data/lib/ruby_llm/cost.rb +68 -41
- data/lib/ruby_llm/downloaded_file.rb +10 -9
- data/lib/ruby_llm/embedding.rb +30 -45
- data/lib/ruby_llm/embedding_request.rb +1 -1
- data/lib/ruby_llm/error.rb +167 -14
- data/lib/ruby_llm/evaluation/dataset.rb +5 -4
- data/lib/ruby_llm/evaluation/evidence.rb +5 -3
- data/lib/ruby_llm/evaluation/report.rb +7 -3
- data/lib/ruby_llm/evaluation/result.rb +1 -1
- data/lib/ruby_llm/evaluation.rb +2 -1
- data/lib/ruby_llm/fallback.rb +32 -4
- data/lib/ruby_llm/file_store/converters/ocr.rb +28 -0
- data/lib/ruby_llm/file_store/converters/office/markup.rb +148 -0
- data/lib/ruby_llm/file_store/converters/office/outline.rb +193 -0
- data/lib/ruby_llm/file_store/converters/office/prose.rb +161 -0
- data/lib/ruby_llm/file_store/converters/office/workbook.rb +187 -0
- data/lib/ruby_llm/file_store/converters/office/zip.rb +149 -0
- data/lib/ruby_llm/file_store/converters/office.rb +106 -0
- data/lib/ruby_llm/file_store/converters/text.rb +27 -0
- data/lib/ruby_llm/file_store/converters.rb +84 -0
- data/lib/ruby_llm/file_store/directory.rb +190 -0
- data/lib/ruby_llm/file_store/memory.rb +75 -0
- data/lib/ruby_llm/file_store/tools/base.rb +99 -0
- data/lib/ruby_llm/file_store/tools/delete.rb +48 -0
- data/lib/ruby_llm/file_store/tools/edit.rb +155 -0
- data/lib/ruby_llm/file_store/tools/list.rb +57 -0
- data/lib/ruby_llm/file_store/tools/read.rb +120 -0
- data/lib/ruby_llm/file_store/tools/search.rb +208 -0
- data/lib/ruby_llm/file_store/tools/write.rb +40 -0
- data/lib/ruby_llm/file_store/tools.rb +39 -0
- data/lib/ruby_llm/file_store.rb +338 -0
- data/lib/ruby_llm/files/saving.rb +14 -0
- data/lib/ruby_llm/image.rb +67 -45
- data/lib/ruby_llm/input_required_error.rb +28 -0
- data/lib/ruby_llm/judge/builder.rb +6 -1
- data/lib/ruby_llm/judge.rb +1 -1
- data/lib/ruby_llm/judgment.rb +6 -24
- data/lib/ruby_llm/mcp/client.rb +67 -28
- data/lib/ruby_llm/mcp/collection.rb +39 -2
- data/lib/ruby_llm/mcp/content.rb +7 -2
- data/lib/ruby_llm/mcp/error.rb +8 -2
- data/lib/ruby_llm/mcp/http.rb +117 -37
- data/lib/ruby_llm/mcp/input_request.rb +3 -112
- data/lib/ruby_llm/mcp/input_required_error.rb +3 -21
- data/lib/ruby_llm/mcp/insufficient_scope_error.rb +27 -0
- data/lib/ruby_llm/mcp/listener.rb +7 -1
- data/lib/ruby_llm/mcp/oauth.rb +102 -39
- data/lib/ruby_llm/mcp/outages.rb +172 -0
- data/lib/ruby_llm/mcp/resource.rb +9 -6
- data/lib/ruby_llm/mcp/resource_template.rb +3 -2
- data/lib/ruby_llm/mcp/session_expired.rb +10 -2
- data/lib/ruby_llm/mcp/stdio.rb +66 -18
- data/lib/ruby_llm/mcp/task.rb +13 -40
- data/lib/ruby_llm/mcp/tool.rb +34 -11
- data/lib/ruby_llm/mcp.rb +123 -59
- data/lib/ruby_llm/message.rb +217 -23
- data/lib/ruby_llm/model/modalities.rb +1 -1
- data/lib/ruby_llm/model/pricing.rb +26 -4
- data/lib/ruby_llm/model/pricing_category.rb +38 -15
- data/lib/ruby_llm/model/pricing_tier.rb +3 -2
- data/lib/ruby_llm/model.rb +24 -6
- data/lib/ruby_llm/models/lookup.rb +37 -4
- data/lib/ruby_llm/models/registry.rb +4 -19
- data/lib/ruby_llm/models.json +36186 -27832
- data/lib/ruby_llm/models.rb +44 -39
- data/lib/ruby_llm/moderation.rb +10 -30
- data/lib/ruby_llm/ocr.rb +5 -22
- data/lib/ruby_llm/open_telemetry/attributes.rb +68 -19
- data/lib/ruby_llm/open_telemetry/content.rb +105 -0
- data/lib/ruby_llm/open_telemetry.rb +18 -4
- data/lib/ruby_llm/prompt/context.rb +2 -2
- data/lib/ruby_llm/prompt.rb +7 -5
- data/lib/ruby_llm/protocol/binary_streaming.rb +2 -8
- data/lib/ruby_llm/protocol/cache_lifetime.rb +22 -0
- data/lib/ruby_llm/protocol/native_content.rb +69 -0
- data/lib/ruby_llm/protocol/replayed_arguments.rb +28 -0
- data/lib/ruby_llm/protocol/stream_accumulator.rb +73 -33
- data/lib/ruby_llm/protocol/streaming.rb +105 -22
- data/lib/ruby_llm/protocol/tool_namespaces.rb +100 -0
- data/lib/ruby_llm/protocol/unsupported_operations.rb +42 -0
- data/lib/ruby_llm/protocol.rb +134 -109
- data/lib/ruby_llm/protocols/anthropic/batches.rb +41 -4
- data/lib/ruby_llm/protocols/anthropic/block_binding.rb +30 -0
- data/lib/ruby_llm/protocols/anthropic/chat.rb +135 -60
- data/lib/ruby_llm/protocols/anthropic/files.rb +9 -0
- data/lib/ruby_llm/protocols/anthropic/media.rb +14 -14
- data/lib/ruby_llm/protocols/anthropic/models.rb +16 -10
- data/lib/ruby_llm/protocols/anthropic/streaming.rb +43 -22
- data/lib/ruby_llm/protocols/anthropic/tools.rb +36 -24
- data/lib/ruby_llm/protocols/anthropic.rb +61 -6
- data/lib/ruby_llm/protocols/azure/speech/transcription.rb +125 -0
- data/lib/ruby_llm/protocols/azure/speech.rb +14 -0
- data/lib/ruby_llm/protocols/bedrock/batches.rb +26 -8
- data/lib/ruby_llm/protocols/bedrock/event_stream.rb +205 -0
- data/lib/ruby_llm/protocols/bedrock/files.rb +15 -6
- data/lib/ruby_llm/protocols/bedrock/guardrails.rb +1 -1
- data/lib/ruby_llm/protocols/bedrock/rerank.rb +2 -0
- data/lib/ruby_llm/protocols/chat_completions/chat.rb +158 -58
- data/lib/ruby_llm/protocols/chat_completions/content_filters.rb +45 -0
- data/lib/ruby_llm/protocols/chat_completions/embedding_batches.rb +23 -2
- data/lib/ruby_llm/protocols/chat_completions/embeddings.rb +26 -3
- data/lib/ruby_llm/protocols/chat_completions/images.rb +7 -9
- data/lib/ruby_llm/protocols/chat_completions/media.rb +20 -20
- data/lib/ruby_llm/protocols/chat_completions/moderation.rb +8 -5
- data/lib/ruby_llm/protocols/chat_completions/speech.rb +8 -2
- data/lib/ruby_llm/protocols/chat_completions/streaming.rb +38 -17
- data/lib/ruby_llm/protocols/chat_completions/think_tags.rb +148 -0
- data/lib/ruby_llm/protocols/chat_completions/tools.rb +15 -28
- data/lib/ruby_llm/protocols/chat_completions/transcription.rb +17 -2
- data/lib/ruby_llm/protocols/chat_completions.rb +4 -0
- data/lib/ruby_llm/protocols/cohere/batches.rb +15 -8
- data/lib/ruby_llm/protocols/cohere/chat.rb +47 -25
- data/lib/ruby_llm/protocols/cohere/datasets.rb +6 -2
- data/lib/ruby_llm/protocols/cohere/embeddings.rb +7 -5
- data/lib/ruby_llm/protocols/cohere/media.rb +7 -7
- data/lib/ruby_llm/protocols/cohere/rerank.rb +2 -1
- data/lib/ruby_llm/protocols/cohere/streaming.rb +26 -11
- data/lib/ruby_llm/protocols/cohere/tools.rb +17 -28
- data/lib/ruby_llm/protocols/cohere/transcription.rb +5 -4
- data/lib/ruby_llm/protocols/converse/batches.rb +2 -24
- data/lib/ruby_llm/protocols/converse/chat.rb +210 -75
- data/lib/ruby_llm/protocols/converse/media.rb +24 -22
- data/lib/ruby_llm/protocols/converse/reasoning.rb +23 -9
- data/lib/ruby_llm/protocols/converse/streaming.rb +68 -220
- data/lib/ruby_llm/protocols/converse.rb +17 -1
- data/lib/ruby_llm/protocols/deepgram/speech.rb +1 -2
- data/lib/ruby_llm/protocols/deepgram/streaming_transcription.rb +1 -3
- data/lib/ruby_llm/protocols/deepgram/transcription.rb +28 -19
- data/lib/ruby_llm/protocols/deepgram.rb +3 -2
- data/lib/ruby_llm/protocols/deepseek/files.rb +1 -1
- data/lib/ruby_llm/protocols/elevenlabs/assets.rb +6 -4
- data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +4 -0
- data/lib/ruby_llm/protocols/elevenlabs/speech.rb +3 -3
- data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +5 -2
- data/lib/ruby_llm/protocols/elevenlabs/transcription.rb +6 -3
- data/lib/ruby_llm/protocols/files.rb +27 -12
- data/lib/ruby_llm/protocols/gemini/caches.rb +4 -4
- data/lib/ruby_llm/protocols/gemini/chat.rb +118 -51
- data/lib/ruby_llm/protocols/gemini/embedding_batches.rb +9 -5
- data/lib/ruby_llm/protocols/gemini/embeddings.rb +3 -3
- data/lib/ruby_llm/protocols/gemini/file_transcription.rb +4 -0
- data/lib/ruby_llm/protocols/gemini/files.rb +5 -4
- data/lib/ruby_llm/protocols/gemini/images.rb +11 -9
- data/lib/ruby_llm/protocols/gemini/live_transcription.rb +1 -1
- data/lib/ruby_llm/protocols/gemini/long_decode.rb +104 -0
- data/lib/ruby_llm/protocols/gemini/media.rb +31 -28
- data/lib/ruby_llm/protocols/gemini/retry_info.rb +29 -0
- data/lib/ruby_llm/protocols/gemini/schemas.rb +126 -0
- data/lib/ruby_llm/protocols/gemini/speech.rb +9 -3
- data/lib/ruby_llm/protocols/gemini/streaming.rb +37 -13
- data/lib/ruby_llm/protocols/gemini/tools.rb +18 -10
- data/lib/ruby_llm/protocols/gemini/transcription.rb +13 -9
- data/lib/ruby_llm/protocols/gemini/videos.rb +14 -3
- data/lib/ruby_llm/protocols/gemini.rb +16 -0
- data/lib/ruby_llm/protocols/gpustack/responses.rb +3 -3
- data/lib/ruby_llm/protocols/gpustack/tokenization.rb +14 -0
- data/lib/ruby_llm/protocols/interactions/chat.rb +29 -14
- data/lib/ruby_llm/protocols/interactions/content.rb +19 -14
- data/lib/ruby_llm/protocols/interactions/streaming.rb +1 -1
- data/lib/ruby_llm/protocols/interactions/tools.rb +10 -13
- data/lib/ruby_llm/protocols/interactions/videos.rb +99 -0
- data/lib/ruby_llm/protocols/interactions.rb +1 -0
- data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +28 -10
- data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +1 -1
- data/lib/ruby_llm/protocols/invoke_model/nova_embeddings.rb +45 -11
- data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +5 -2
- data/lib/ruby_llm/protocols/invoke_model/titan_multimodal_embeddings.rb +11 -3
- data/lib/ruby_llm/protocols/invoke_model.rb +21 -2
- data/lib/ruby_llm/protocols/meta/transcription/stream.rb +108 -0
- data/lib/ruby_llm/protocols/meta/transcription.rb +76 -0
- data/lib/ruby_llm/protocols/minimax/files.rb +58 -0
- data/lib/ruby_llm/protocols/minimax/images.rb +54 -0
- data/lib/ruby_llm/protocols/minimax/speech.rb +53 -0
- data/lib/ruby_llm/protocols/minimax/transcription.rb +82 -0
- data/lib/ruby_llm/protocols/minimax/videos.rb +93 -0
- data/lib/ruby_llm/protocols/minimax.rb +21 -0
- data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +44 -21
- data/lib/ruby_llm/protocols/mistral/files.rb +6 -4
- data/lib/ruby_llm/protocols/mistral/multi_completion.rb +15 -4
- data/lib/ruby_llm/protocols/moonshot/files.rb +48 -0
- data/lib/ruby_llm/protocols/openai/batches.rb +3 -2
- data/lib/ruby_llm/protocols/openai/decisions.rb +6 -4
- data/lib/ruby_llm/protocols/openai/files.rb +13 -6
- data/lib/ruby_llm/protocols/openrouter/batches.rb +13 -17
- data/lib/ruby_llm/protocols/openrouter/responses.rb +6 -10
- data/lib/ruby_llm/protocols/perplexity/agent.rb +19 -35
- data/lib/ruby_llm/protocols/perplexity/batches.rb +37 -0
- data/lib/ruby_llm/protocols/perplexity/files.rb +19 -9
- data/lib/ruby_llm/protocols/responses/approvals.rb +12 -11
- data/lib/ruby_llm/protocols/responses/chat.rb +165 -75
- data/lib/ruby_llm/protocols/responses/citation_markers.rb +167 -0
- data/lib/ruby_llm/protocols/responses/compaction.rb +2 -2
- data/lib/ruby_llm/protocols/responses/media.rb +17 -9
- data/lib/ruby_llm/protocols/responses/streaming.rb +71 -12
- data/lib/ruby_llm/protocols/responses/token_counting.rb +1 -1
- data/lib/ruby_llm/protocols/responses/tools.rb +48 -1
- data/lib/ruby_llm/protocols/responses.rb +21 -0
- data/lib/ruby_llm/protocols/system_one/responses.rb +9 -2
- data/lib/ruby_llm/protocols/vertexai/files.rb +4 -3
- data/lib/ruby_llm/protocols/vertexai/text_to_speech.rb +116 -0
- data/lib/ruby_llm/protocols/xai/files.rb +13 -6
- data/lib/ruby_llm/protocols/zai/files.rb +37 -0
- data/lib/ruby_llm/provider.rb +128 -31
- data/lib/ruby_llm/providers/anthropic.rb +5 -1
- data/lib/ruby_llm/providers/azure/chat.rb +17 -3
- data/lib/ruby_llm/providers/azure/chat_completions.rb +1 -0
- data/lib/ruby_llm/providers/azure/decisions.rb +17 -0
- data/lib/ruby_llm/providers/azure/embeddings.rb +1 -2
- data/lib/ruby_llm/providers/azure/media.rb +6 -6
- data/lib/ruby_llm/providers/azure/mistral.rb +17 -0
- data/lib/ruby_llm/providers/azure/models.rb +16 -2
- data/lib/ruby_llm/providers/azure/moderation.rb +16 -0
- data/lib/ruby_llm/providers/azure/responses.rb +9 -0
- data/lib/ruby_llm/providers/azure/videos.rb +2 -2
- data/lib/ruby_llm/providers/azure.rb +43 -1
- data/lib/ruby_llm/providers/bedrock/anthropic/batches.rb +32 -0
- data/lib/ruby_llm/providers/bedrock/anthropic.rb +134 -0
- data/lib/ruby_llm/providers/bedrock/auth.rb +12 -3
- data/lib/ruby_llm/providers/bedrock/connection.rb +19 -0
- data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +5 -1
- data/lib/ruby_llm/providers/bedrock/mantle/chat_completions.rb +2 -2
- data/lib/ruby_llm/providers/bedrock/mantle/responses.rb +12 -2
- data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +1 -1
- data/lib/ruby_llm/providers/bedrock/mantle.rb +7 -0
- data/lib/ruby_llm/providers/bedrock/models.rb +2 -9
- data/lib/ruby_llm/providers/bedrock.rb +51 -8
- data/lib/ruby_llm/providers/cohere.rb +5 -1
- data/lib/ruby_llm/providers/deepgram/chat.rb +25 -0
- data/lib/ruby_llm/providers/deepgram.rb +21 -2
- data/lib/ruby_llm/providers/deepseek/chat.rb +7 -16
- data/lib/ruby_llm/providers/deepseek/responses.rb +25 -8
- data/lib/ruby_llm/providers/deepseek.rb +5 -1
- data/lib/ruby_llm/providers/elevenlabs.rb +2 -1
- data/lib/ruby_llm/providers/gemini.rb +13 -1
- data/lib/ruby_llm/providers/gpustack/chat.rb +23 -3
- data/lib/ruby_llm/providers/gpustack/embeddings.rb +2 -2
- data/lib/ruby_llm/providers/gpustack/media.rb +8 -8
- data/lib/ruby_llm/providers/gpustack/transcription.rb +14 -6
- data/lib/ruby_llm/providers/gpustack.rb +1 -1
- data/lib/ruby_llm/providers/hetzner/chat.rb +23 -6
- data/lib/ruby_llm/providers/hetzner.rb +5 -1
- data/lib/ruby_llm/providers/meta/responses.rb +81 -0
- data/lib/ruby_llm/providers/meta.rb +37 -0
- data/lib/ruby_llm/providers/minimax/anthropic.rb +95 -0
- data/lib/ruby_llm/providers/minimax/chat_completions.rb +48 -0
- data/lib/ruby_llm/providers/minimax/responses.rb +52 -0
- data/lib/ruby_llm/providers/minimax.rb +104 -0
- data/lib/ruby_llm/providers/mistral/chat.rb +34 -4
- data/lib/ruby_llm/providers/mistral/chat_completions/batches.rb +8 -3
- data/lib/ruby_llm/providers/mistral/embeddings.rb +1 -12
- data/lib/ruby_llm/providers/mistral/input_tokens.rb +35 -0
- data/lib/ruby_llm/providers/mistral/media.rb +8 -8
- data/lib/ruby_llm/providers/mistral/transcription.rb +8 -6
- data/lib/ruby_llm/providers/mistral.rb +13 -1
- data/lib/ruby_llm/providers/moonshot/capabilities.rb +16 -0
- data/lib/ruby_llm/providers/moonshot/chat.rb +100 -0
- data/lib/ruby_llm/providers/moonshot/documents.rb +79 -0
- data/lib/ruby_llm/providers/moonshot/responses.rb +55 -0
- data/lib/ruby_llm/providers/moonshot.rb +54 -0
- data/lib/ruby_llm/providers/ollama/chat.rb +5 -6
- data/lib/ruby_llm/providers/ollama/embeddings.rb +41 -0
- data/lib/ruby_llm/providers/ollama/media.rb +6 -6
- data/lib/ruby_llm/providers/ollama/speech.rb +24 -0
- data/lib/ruby_llm/providers/ollama.rb +13 -1
- data/lib/ruby_llm/providers/ollama_cloud.rb +22 -1
- data/lib/ruby_llm/providers/openai/responses.rb +8 -0
- data/lib/ruby_llm/providers/openai.rb +7 -3
- data/lib/ruby_llm/providers/openrouter/chat.rb +15 -9
- data/lib/ruby_llm/providers/openrouter/embeddings.rb +12 -8
- data/lib/ruby_llm/providers/openrouter/images.rb +16 -10
- data/lib/ruby_llm/providers/openrouter/judgments.rb +47 -0
- data/lib/ruby_llm/providers/openrouter/media.rb +5 -5
- data/lib/ruby_llm/providers/openrouter/models.rb +19 -7
- data/lib/ruby_llm/providers/openrouter/streaming.rb +4 -4
- data/lib/ruby_llm/providers/openrouter/videos.rb +1 -1
- data/lib/ruby_llm/providers/openrouter.rb +32 -5
- data/lib/ruby_llm/providers/perplexity/chat.rb +3 -3
- data/lib/ruby_llm/providers/perplexity/embeddings.rb +1 -1
- data/lib/ruby_llm/providers/perplexity/media.rb +6 -6
- data/lib/ruby_llm/providers/perplexity.rb +13 -2
- data/lib/ruby_llm/providers/typesafe.rb +1 -1
- data/lib/ruby_llm/providers/vertexai/anthropic.rb +5 -1
- data/lib/ruby_llm/providers/vertexai/credentials.rb +27 -3
- data/lib/ruby_llm/providers/vertexai/embed_content.rb +1 -1
- data/lib/ruby_llm/providers/vertexai/embeddings.rb +2 -3
- data/lib/ruby_llm/providers/vertexai/gemini.rb +1 -1
- data/lib/ruby_llm/providers/vertexai/mistral.rb +5 -0
- data/lib/ruby_llm/providers/vertexai/models.rb +24 -2
- data/lib/ruby_llm/providers/vertexai/transcription.rb +2 -2
- data/lib/ruby_llm/providers/vertexai/videos.rb +1 -1
- data/lib/ruby_llm/providers/vertexai.rb +56 -9
- data/lib/ruby_llm/providers/xai/chat.rb +3 -3
- data/lib/ruby_llm/providers/xai/chat_completions/batches.rb +8 -3
- data/lib/ruby_llm/providers/xai/images.rb +9 -4
- data/lib/ruby_llm/providers/xai/models.rb +2 -2
- data/lib/ruby_llm/providers/xai/responses.rb +10 -0
- data/lib/ruby_llm/providers/xai/speech.rb +2 -2
- data/lib/ruby_llm/providers/xai/transcription.rb +14 -9
- data/lib/ruby_llm/providers/xai/videos.rb +1 -1
- data/lib/ruby_llm/providers/xai.rb +9 -1
- data/lib/ruby_llm/providers/zai/chat.rb +117 -0
- data/lib/ruby_llm/providers/zai/ocr.rb +65 -0
- data/lib/ruby_llm/providers/zai/token_counting.rb +25 -0
- data/lib/ruby_llm/providers/zai/videos.rb +67 -0
- data/lib/ruby_llm/providers/zai.rb +66 -0
- data/lib/ruby_llm/question.rb +390 -0
- data/lib/ruby_llm/queued_message.rb +74 -0
- data/lib/ruby_llm/railtie.rb +43 -0
- data/lib/ruby_llm/request_shape/part.rb +2 -2
- data/lib/ruby_llm/request_shape/problem.rb +2 -2
- data/lib/ruby_llm/request_shape/tool_round.rb +2 -2
- data/lib/ruby_llm/request_shape/turn.rb +2 -2
- data/lib/ruby_llm/request_shape.rb +1 -1
- data/lib/ruby_llm/rerank.rb +10 -37
- data/lib/ruby_llm/research_job.rb +26 -13
- data/lib/ruby_llm/sandbox/landlock.rb +78 -0
- data/lib/ruby_llm/sandbox/mxc.rb +137 -0
- data/lib/ruby_llm/sandbox.rb +141 -0
- data/lib/ruby_llm/search_results.rb +9 -4
- data/lib/ruby_llm/server_tool_call.rb +1 -1
- data/lib/ruby_llm/skill/loader.rb +131 -0
- data/lib/ruby_llm/skill.rb +263 -0
- data/lib/ruby_llm/speech.rb +25 -53
- data/lib/ruby_llm/stored_file.rb +94 -0
- data/lib/ruby_llm/support/cancellation.rb +4 -4
- data/lib/ruby_llm/support/deprecated_enumerable.rb +13 -0
- data/lib/ruby_llm/support/deprecated_job_predicates.rb +24 -0
- data/lib/ruby_llm/support/inspectable.rb +1 -1
- data/lib/ruby_llm/support/instrumentation.rb +6 -5
- data/lib/ruby_llm/support/rescuable.rb +84 -0
- data/lib/ruby_llm/support/utils.rb +26 -3
- data/lib/ruby_llm/task.rb +126 -0
- data/lib/ruby_llm/test_helper.rb +129 -0
- data/lib/ruby_llm/thinking.rb +3 -3
- data/lib/ruby_llm/tokens.rb +52 -12
- data/lib/ruby_llm/tool.rb +339 -5
- data/lib/ruby_llm/tool_call/collection.rb +132 -0
- data/lib/ruby_llm/tool_call.rb +68 -6
- data/lib/ruby_llm/tools/provider_tools.rb +23 -0
- data/lib/ruby_llm/tools/search.rb +118 -0
- data/lib/ruby_llm/transcription/segment.rb +67 -0
- data/lib/ruby_llm/transcription/wav_audio.rb +2 -1
- data/lib/ruby_llm/transcription.rb +25 -49
- data/lib/ruby_llm/transcription_chunk.rb +9 -9
- data/lib/ruby_llm/transport/connection.rb +146 -33
- data/lib/ruby_llm/transport/error_middleware.rb +94 -49
- data/lib/ruby_llm/transport/event_stream_parser.rb +18 -4
- data/lib/ruby_llm/transport/json_response.rb +9 -3
- data/lib/ruby_llm/transport/log_formatter.rb +20 -0
- data/lib/ruby_llm/transport/release_request.rb +35 -0
- data/lib/ruby_llm/transport/websocket_connection.rb +26 -30
- data/lib/ruby_llm/uploaded_file.rb +15 -6
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm/video.rb +34 -10
- data/lib/ruby_llm/video_job.rb +55 -42
- data/lib/ruby_llm/web_source.rb +38 -0
- data/lib/ruby_llm/workflow.rb +2 -2
- data/lib/ruby_llm.rb +63 -25
- data/lib/tasks/ruby_llm/evaluations.rb +1 -1
- data/skills/rubyllm/SKILL.md +128 -54
- data/skills/rubyllm/references/cost-and-tracing.md +8 -0
- data/skills/rubyllm/references/files-code-and-context.md +6 -0
- data/skills/rubyllm/references/operations.md +8 -0
- data/skills/rubyllm/references/rails-chats.md +22 -0
- data/skills/rubyllm/references/tools-and-agents.md +11 -0
- metadata +219 -14
- data/lib/generators/ruby_llm/chat_ui/templates/helpers/messages_helper.rb.tt +0 -25
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e784b5ab43c18ac17675ffa3e46c802934a9f8eaa4ef733bd1e7c450c462fff5
|
|
4
|
+
data.tar.gz: 69d10ad8492781735ce7b50d56476bd39d3cf405e9218adb819e735712144761
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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>
|
|
8
|
+
<strong>The Ruby AI framework</strong>
|
|
9
9
|
|
|
10
|
-
<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
|
-
|
|
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
|
-
##
|
|
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.
|
|
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.
|
|
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-
|
|
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.
|
|
309
|
+
Install RubyLLM 2.2:
|
|
250
310
|
|
|
251
311
|
```bash
|
|
252
|
-
bundle add ruby_llm --version 2.
|
|
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
|
-
|
|
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
|
-
|
|
329
|
+
## AI coding assistants
|
|
270
330
|
|
|
271
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
|