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
data/lib/ruby_llm/mcp.rb
CHANGED
|
@@ -31,6 +31,7 @@ module RubyLLM
|
|
|
31
31
|
# writing.
|
|
32
32
|
class MCP
|
|
33
33
|
include Support::Inspectable
|
|
34
|
+
include Support::Rescuable
|
|
34
35
|
|
|
35
36
|
INPUT_ROUNDS = 10
|
|
36
37
|
INPUT_REQUESTS = %i[form url].freeze
|
|
@@ -47,14 +48,36 @@ module RubyLLM
|
|
|
47
48
|
UNKNOWN_TOOL_ERRORS = [-32_601, -32_602].freeze
|
|
48
49
|
|
|
49
50
|
SETTINGS = %i[
|
|
50
|
-
@url @command @directory @env @headers @bearer_token @timeout @input_names @only @except @prefix
|
|
51
|
+
@url @allow_http @command @directory @env @headers @bearer_token @timeout @input_names @only @except @prefix
|
|
51
52
|
@tool_declarations @approvals @deferrals @callbacks @oauth @input_requests @extensions @log_level
|
|
52
53
|
].freeze
|
|
53
54
|
private_constant :SETTINGS, :INPUT_ROUNDS, :INPUT_REQUESTS, :INLINE_SETTINGS, :EXTENSIONS, :POLL_INTERVAL,
|
|
54
55
|
:LOG_LEVELS, :UNKNOWN_TOOL_ERRORS
|
|
55
56
|
|
|
57
|
+
##
|
|
58
|
+
# :singleton-method: rescue_from
|
|
59
|
+
# :call-seq: rescue_from(*exception_classes, with: nil, &block)
|
|
60
|
+
#
|
|
61
|
+
# Registers a handler for exceptions raised while one of the server's
|
|
62
|
+
# tools runs in a chat, the way Tool.rescue_from does for a Tool. Name
|
|
63
|
+
# the handler with +with:+ or pass a block; either runs on the MCP
|
|
64
|
+
# instance, so declared ::inputs are available. The handler's return
|
|
65
|
+
# value becomes the call's result. Re-raise to stop the chat.
|
|
66
|
+
#
|
|
67
|
+
# class Linear < RubyLLM::MCP
|
|
68
|
+
# url "https://mcp.linear.app/mcp"
|
|
69
|
+
#
|
|
70
|
+
# rescue_from RubyLLM::MCP::Error do |error|
|
|
71
|
+
# { error: "Linear couldn't answer: #{error.message}" }
|
|
72
|
+
# end
|
|
73
|
+
# end
|
|
74
|
+
#
|
|
75
|
+
# A handler declared here comes before one declared on MCP::Tool. Tool
|
|
76
|
+
# classes added with ::tool use their own handlers.
|
|
77
|
+
|
|
56
78
|
class << self
|
|
57
79
|
attr_writer :default_name # :nodoc:
|
|
80
|
+
attr_reader :allow_http # :nodoc:
|
|
58
81
|
|
|
59
82
|
def inherited(subclass) # :nodoc:
|
|
60
83
|
super
|
|
@@ -65,15 +88,22 @@ module RubyLLM
|
|
|
65
88
|
subclass.transport(@transport) if @transport
|
|
66
89
|
end
|
|
67
90
|
|
|
68
|
-
# Sets the server's Streamable HTTP endpoint.
|
|
69
|
-
#
|
|
70
|
-
# configured value.
|
|
91
|
+
# Sets the server's Streamable HTTP endpoint. Called with no argument,
|
|
92
|
+
# returns the configured value.
|
|
71
93
|
#
|
|
72
94
|
# url "https://mcp.linear.app/mcp"
|
|
73
95
|
#
|
|
74
|
-
|
|
96
|
+
# Plain HTTP is only allowed for loopback addresses. Pass
|
|
97
|
+
# <tt>allow_http: true</tt> for a server on a network you trust, such as
|
|
98
|
+
# a Compose or Kubernetes service. Anyone on that network can read and
|
|
99
|
+
# change the traffic, including any token you send.
|
|
100
|
+
#
|
|
101
|
+
# url "http://tools:4011/mcp", allow_http: true
|
|
102
|
+
#
|
|
103
|
+
def url(value = nil, allow_http: false)
|
|
75
104
|
return @url if value.nil?
|
|
76
105
|
|
|
106
|
+
@allow_http = allow_http
|
|
77
107
|
@url = value
|
|
78
108
|
end
|
|
79
109
|
|
|
@@ -106,8 +136,8 @@ module RubyLLM
|
|
|
106
136
|
# <tt>notify(message, version:)</tt> and
|
|
107
137
|
# <tt>cancel(notification, version:)</tt> send a notification, and
|
|
108
138
|
# +close+ releases the connection until the next request. Raise
|
|
109
|
-
#
|
|
110
|
-
# its own authentication and timeouts.
|
|
139
|
+
# RubyLLM::ConnectionError when the server cannot be reached. The
|
|
140
|
+
# transport handles its own authentication and timeouts.
|
|
111
141
|
def transport(value = nil, &block)
|
|
112
142
|
return @transport if value.nil? && block.nil?
|
|
113
143
|
|
|
@@ -124,8 +154,11 @@ module RubyLLM
|
|
|
124
154
|
@directory = value
|
|
125
155
|
end
|
|
126
156
|
|
|
127
|
-
# Adds environment variables for a stdio server's process
|
|
128
|
-
#
|
|
157
|
+
# Adds environment variables for a stdio server's process, which
|
|
158
|
+
# inherits only a short list of safe variables from the application.
|
|
159
|
+
# Values override inherited ones and may be blocks or method names,
|
|
160
|
+
# read each time the process starts. Called with no arguments, returns
|
|
161
|
+
# them.
|
|
129
162
|
#
|
|
130
163
|
# env NODE_ENV: "production", API_KEY: -> { user.api_key }
|
|
131
164
|
#
|
|
@@ -145,9 +178,7 @@ module RubyLLM
|
|
|
145
178
|
@headers = headers.merge(name.to_s => block || value)
|
|
146
179
|
end
|
|
147
180
|
|
|
148
|
-
def headers # :nodoc:
|
|
149
|
-
@headers || {}
|
|
150
|
-
end
|
|
181
|
+
def headers = @headers || {} # :nodoc:
|
|
151
182
|
|
|
152
183
|
# Sets the bearer token sent in the +Authorization+ header. Pass the
|
|
153
184
|
# token, a method name, or a block. Called with no argument, returns the
|
|
@@ -204,16 +235,25 @@ module RubyLLM
|
|
|
204
235
|
# oauth owner: :user, client_id: ENV["WIKI_CLIENT_ID"], client_secret: ENV["WIKI_CLIENT_SECRET"],
|
|
205
236
|
# identity_provider: { issuer: "https://acme.okta.com", client_id: ENV["OKTA_CLIENT_ID"],
|
|
206
237
|
# client_secret: ENV["OKTA_CLIENT_SECRET"], id_token: -> { user.id_token } }
|
|
238
|
+
#
|
|
239
|
+
# +authorization_params:+ adds query parameters to MCP#authorization_url
|
|
240
|
+
# that the authorization server needs, such as Google's
|
|
241
|
+
# +access_type+, which it requires before it issues a refresh token.
|
|
242
|
+
# It may be a block or method name, and so may its values. The
|
|
243
|
+
# parameters RubyLLM sets itself, such as +client_id+, +state+, and
|
|
244
|
+
# +scope+, raise ArgumentError.
|
|
245
|
+
#
|
|
246
|
+
# oauth authorization_params: { access_type: "offline", prompt: "consent" }
|
|
207
247
|
def oauth(owner: nil, scopes: nil, client_id: nil, client_secret: nil, grant: nil, private_key: nil,
|
|
208
|
-
assertion: nil, identity_provider: nil)
|
|
248
|
+
assertion: nil, identity_provider: nil, authorization_params: nil)
|
|
209
249
|
raise ArgumentError, "Unknown OAuth grant: #{grant}" unless grant.nil? || OAuth::GRANTS.include?(grant)
|
|
210
250
|
|
|
211
|
-
|
|
251
|
+
OAuth.check_authorization_params(authorization_params) if authorization_params.is_a?(Hash)
|
|
252
|
+
@oauth = { owner:, scopes:, client_id:, client_secret:, grant:, private_key:, assertion:, identity_provider:,
|
|
253
|
+
authorization_params: }
|
|
212
254
|
end
|
|
213
255
|
|
|
214
|
-
def oauth_settings # :nodoc:
|
|
215
|
-
@oauth
|
|
216
|
-
end
|
|
256
|
+
def oauth_settings = @oauth # :nodoc:
|
|
217
257
|
|
|
218
258
|
# Sets how many seconds a request to the server may take. Defaults to
|
|
219
259
|
# the configured +request_timeout+.
|
|
@@ -334,9 +374,7 @@ module RubyLLM
|
|
|
334
374
|
@deferrals = deferrals + [names.flatten.map(&:to_s)]
|
|
335
375
|
end
|
|
336
376
|
|
|
337
|
-
def deferrals # :nodoc:
|
|
338
|
-
@deferrals || []
|
|
339
|
-
end
|
|
377
|
+
def deferrals = @deferrals || [] # :nodoc:
|
|
340
378
|
|
|
341
379
|
# Registers a callback for the progress the server reports while it
|
|
342
380
|
# works on a request. Pass a method name or a block; either runs on
|
|
@@ -369,11 +407,14 @@ module RubyLLM
|
|
|
369
407
|
add_callback(:after_change, method, block)
|
|
370
408
|
end
|
|
371
409
|
|
|
372
|
-
# Sets the kinds of
|
|
373
|
-
# +:
|
|
374
|
-
# show them to anyone, so a
|
|
375
|
-
#
|
|
376
|
-
#
|
|
410
|
+
# Sets the kinds of questions the server may ask the person (see
|
|
411
|
+
# Question): +:form+, for values, +:url+, to visit a page, or both, the
|
|
412
|
+
# default. Pass +false+ when your app cannot show them to anyone, so a
|
|
413
|
+
# call never waits on an answer that will not come. RubyLLM declines
|
|
414
|
+
# questions of other kinds, and asks to visit anything but a web page
|
|
415
|
+
# (an +http+ or +https+ address), so a link to the page can't run
|
|
416
|
+
# script in your app. Called with no arguments, returns the accepted
|
|
417
|
+
# kinds.
|
|
377
418
|
#
|
|
378
419
|
# input_requests false
|
|
379
420
|
# input_requests :url
|
|
@@ -388,13 +429,15 @@ module RubyLLM
|
|
|
388
429
|
@input_requests = kinds
|
|
389
430
|
end
|
|
390
431
|
|
|
391
|
-
# Registers a callback for the server's
|
|
392
|
-
#
|
|
393
|
-
#
|
|
394
|
-
#
|
|
432
|
+
# Registers a callback for the server's questions to the person. Pass
|
|
433
|
+
# a method name or a block; either runs on the MCP instance with a
|
|
434
|
+
# Question to answer or decline. In a chat, a question no callback
|
|
435
|
+
# answers pauses the tool call; see Chat#pending_questions. Answer
|
|
436
|
+
# only the questions you recognize: an answer a question can't take
|
|
437
|
+
# raises ArgumentError from the call.
|
|
395
438
|
#
|
|
396
439
|
# before_input_request :ask_operator
|
|
397
|
-
# before_input_request { |
|
|
440
|
+
# before_input_request { |question| question.decline if question.url? }
|
|
398
441
|
#
|
|
399
442
|
def before_input_request(method = nil, &block)
|
|
400
443
|
add_callback(:before_input_request, method, block)
|
|
@@ -457,12 +500,21 @@ module RubyLLM
|
|
|
457
500
|
default_name_for(url:, command:) if url || command
|
|
458
501
|
end
|
|
459
502
|
|
|
503
|
+
def derived_name? # :nodoc:
|
|
504
|
+
name.nil? && @default_name.nil?
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def endpoint # :nodoc:
|
|
508
|
+
url || Array(command).join(' ')
|
|
509
|
+
end
|
|
510
|
+
|
|
460
511
|
# Builds an anonymous MCP class from keywords, as RubyLLM.mcp does.
|
|
461
|
-
def define(name: nil, headers: {}, env: {}, oauth: nil, extensions: nil, **settings) # :nodoc:
|
|
512
|
+
def define(name: nil, headers: {}, env: {}, oauth: nil, extensions: nil, allow_http: false, **settings) # :nodoc:
|
|
462
513
|
check_inline_settings(name, settings)
|
|
463
514
|
|
|
464
515
|
Class.new(self) do
|
|
465
516
|
settings.each { |setting, value| public_send(setting, value) unless value.nil? }
|
|
517
|
+
url(settings[:url], allow_http:)
|
|
466
518
|
headers.each { |header_name, value| header(header_name, value) }
|
|
467
519
|
Array(extensions).each { |extension_name, options| extension(extension_name, **options.to_h) }
|
|
468
520
|
env(**env)
|
|
@@ -495,14 +547,15 @@ module RubyLLM
|
|
|
495
547
|
def default_name_for(url:, command:)
|
|
496
548
|
return File.basename(Array(command).first.to_s) unless url
|
|
497
549
|
|
|
498
|
-
labels =
|
|
550
|
+
labels = HTTP.parse(url).host.split('.')
|
|
551
|
+
labels = labels[0...-1] - %w[mcp api www] unless labels.one? || labels.all?(/\A\d+\z/)
|
|
499
552
|
labels.join('_')
|
|
500
553
|
end
|
|
501
554
|
end
|
|
502
555
|
|
|
503
556
|
# Creates an MCP. Keywords are the values of the declared ::inputs.
|
|
504
557
|
# Pass a Context as +context:+ to connect with its configuration: its
|
|
505
|
-
# +faraday_adapter+, +http_proxy+, and +request_timeout+ apply to the
|
|
558
|
+
# +faraday_adapter+, +http_proxy+, +ssl+, and +request_timeout+ apply to the
|
|
506
559
|
# server's requests and to its OAuth requests.
|
|
507
560
|
#
|
|
508
561
|
# Linear.new(user: current_user)
|
|
@@ -545,6 +598,8 @@ module RubyLLM
|
|
|
545
598
|
end
|
|
546
599
|
end
|
|
547
600
|
|
|
601
|
+
def tools_listed? = !@tools.nil? # :nodoc:
|
|
602
|
+
|
|
548
603
|
# Calls the server tool +name+ with +arguments+ and returns an
|
|
549
604
|
# MCP::Result. Every server tool is also a method:
|
|
550
605
|
#
|
|
@@ -629,9 +684,9 @@ module RubyLLM
|
|
|
629
684
|
end
|
|
630
685
|
|
|
631
686
|
# Returns whether +tool+, one of this MCP's tools, is deferred
|
|
632
|
-
# according to ::defer or
|
|
687
|
+
# according to ::defer or the tool's own Tool#deferred?.
|
|
633
688
|
def defers?(tool) # :nodoc:
|
|
634
|
-
return true if tool.
|
|
689
|
+
return true if tool.deferred?
|
|
635
690
|
|
|
636
691
|
self.class.deferrals.any? do |names|
|
|
637
692
|
names.empty? || (tool.respond_to?(:server_name) && names.include?(tool.server_name))
|
|
@@ -641,13 +696,12 @@ module RubyLLM
|
|
|
641
696
|
# Runs +tool+, one of this MCP's tools, with the model's +arguments+.
|
|
642
697
|
def run(tool, arguments, input: nil) # :nodoc:
|
|
643
698
|
arguments = arguments.transform_keys(&:to_sym)
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
outcome
|
|
647
|
-
return outcome if outcome.is_a?(Task)
|
|
699
|
+
params = { name: tool.server_name, arguments: arguments.merge(tool.resolved_fixed_arguments) }
|
|
700
|
+
outcome = Outages.calling(self, tool, input) { call_tool(params, input:) }
|
|
701
|
+
return outcome if outcome.is_a?(Task) || outcome.is_a?(Outages::Failure)
|
|
648
702
|
|
|
649
703
|
result = Result.new(outcome, tool.ui_uri)
|
|
650
|
-
return { error: result.text } if result.error?
|
|
704
|
+
return result.ui_uri ? result : { error: result.text } if result.error?
|
|
651
705
|
|
|
652
706
|
tool.wrap ? apply(tool.wrap, result, **arguments) : result
|
|
653
707
|
end
|
|
@@ -672,7 +726,7 @@ module RubyLLM
|
|
|
672
726
|
def await_task(task, timeout: nil, interval: nil) # :nodoc:
|
|
673
727
|
limit = timeout || self.class.timeout || config.request_timeout
|
|
674
728
|
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + limit
|
|
675
|
-
until task.
|
|
729
|
+
until task.complete?
|
|
676
730
|
ask_for_task_input(task) if task.status == :input_required
|
|
677
731
|
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
678
732
|
raise Error, "Task #{task.id} did not finish in #{limit} seconds" unless remaining.positive?
|
|
@@ -680,7 +734,7 @@ module RubyLLM
|
|
|
680
734
|
Support::Cancellation.pause([interval || poll_interval(task), remaining].min)
|
|
681
735
|
task.refresh
|
|
682
736
|
end
|
|
683
|
-
raise task.error unless task.
|
|
737
|
+
raise task.error unless task.succeeded?
|
|
684
738
|
|
|
685
739
|
task
|
|
686
740
|
end
|
|
@@ -741,8 +795,9 @@ module RubyLLM
|
|
|
741
795
|
# reports.listen(tasks: chat.pending_tasks)
|
|
742
796
|
#
|
|
743
797
|
# Without +resources+ or +tasks+, does nothing for a server that
|
|
744
|
-
# announces no changes. Raises
|
|
745
|
-
#
|
|
798
|
+
# announces no changes. Raises RubyLLM::ConnectionError when a server
|
|
799
|
+
# over HTTP cannot be reached, and MCP::Error when the server does not
|
|
800
|
+
# send updates for the resources or tasks.
|
|
746
801
|
def listen(resources: [], tasks: [])
|
|
747
802
|
uris = resources.map { |resource| resource.respond_to?(:uri) ? resource.uri : resource.to_s }
|
|
748
803
|
ids = tasks.map { |task| task.respond_to?(:id) ? task.id : task.to_s }
|
|
@@ -793,9 +848,9 @@ module RubyLLM
|
|
|
793
848
|
end
|
|
794
849
|
|
|
795
850
|
def responses(requests)
|
|
796
|
-
requests.to_h do |
|
|
797
|
-
|
|
798
|
-
[
|
|
851
|
+
requests.to_h do |question|
|
|
852
|
+
question = Question.from_h(question) if question.is_a?(Hash)
|
|
853
|
+
[question.key, question.response&.except('note')]
|
|
799
854
|
end
|
|
800
855
|
end
|
|
801
856
|
|
|
@@ -816,8 +871,8 @@ module RubyLLM
|
|
|
816
871
|
def check_task(task, answered_requests)
|
|
817
872
|
update_task(task, answered_requests) if answered_requests
|
|
818
873
|
task.refresh
|
|
819
|
-
return task.data['result'] if task.
|
|
820
|
-
raise task.error if task.
|
|
874
|
+
return task.data['result'] if task.succeeded?
|
|
875
|
+
raise task.error if task.complete?
|
|
821
876
|
|
|
822
877
|
ask_for_task_input(task) if task.status == :input_required
|
|
823
878
|
task
|
|
@@ -838,7 +893,8 @@ module RubyLLM
|
|
|
838
893
|
end
|
|
839
894
|
|
|
840
895
|
def poll_interval(task)
|
|
841
|
-
task.poll_interval
|
|
896
|
+
interval = task.poll_interval
|
|
897
|
+
interval&.positive? ? interval : POLL_INTERVAL
|
|
842
898
|
end
|
|
843
899
|
|
|
844
900
|
def finish(task)
|
|
@@ -846,7 +902,7 @@ module RubyLLM
|
|
|
846
902
|
rescue StandardError
|
|
847
903
|
begin
|
|
848
904
|
task.cancel
|
|
849
|
-
rescue Error, Faraday::Error => e
|
|
905
|
+
rescue RubyLLM::Error, Faraday::Error => e
|
|
850
906
|
RubyLLM.logger.debug { "#{name} could not cancel task #{task.id}: #{e.message}" }
|
|
851
907
|
end
|
|
852
908
|
raise
|
|
@@ -856,18 +912,20 @@ module RubyLLM
|
|
|
856
912
|
result.fetch('inputRequests', {}).filter_map do |key, request|
|
|
857
913
|
next unless request['method'] == 'elicitation/create'
|
|
858
914
|
|
|
859
|
-
|
|
860
|
-
next
|
|
915
|
+
Question.new(key, request['params'] || {}).tap do |question|
|
|
916
|
+
next question.decline unless accepts?(question)
|
|
861
917
|
|
|
862
918
|
self.class.callbacks(:before_input_request).each do |callback|
|
|
863
|
-
apply(callback,
|
|
919
|
+
apply(callback, question) unless question.answered?
|
|
864
920
|
end
|
|
865
921
|
end
|
|
866
922
|
end
|
|
867
923
|
end
|
|
868
924
|
|
|
869
|
-
def accepts?(
|
|
870
|
-
self.class.input_requests.include?(
|
|
925
|
+
def accepts?(question)
|
|
926
|
+
return self.class.input_requests.include?(:form) unless question.url?
|
|
927
|
+
|
|
928
|
+
self.class.input_requests.include?(:url) && HTTP.web?(question.url)
|
|
871
929
|
end
|
|
872
930
|
|
|
873
931
|
def send_request(method, params)
|
|
@@ -1032,17 +1090,23 @@ module RubyLLM
|
|
|
1032
1090
|
if settings.transport
|
|
1033
1091
|
resolve(settings.transport)
|
|
1034
1092
|
elsif settings.url
|
|
1035
|
-
HTTP.new(resolve(settings.url), headers: method(:request_headers),
|
|
1036
|
-
|
|
1093
|
+
HTTP.new(resolve(settings.url), allow_http: settings.allow_http, headers: method(:request_headers),
|
|
1094
|
+
timeout: settings.timeout, unauthorized: method(:unauthorized),
|
|
1095
|
+
responded: method(:responded), idempotent: method(:idempotent?), config:)
|
|
1037
1096
|
elsif settings.command
|
|
1038
1097
|
Stdio.new(settings.command.map { |part| resolve(part) },
|
|
1039
|
-
env: settings.env.transform_values { |value| resolve(value) },
|
|
1098
|
+
env: -> { settings.env.transform_values { |value| resolve(value) } },
|
|
1040
1099
|
directory: resolve(settings.directory), timeout: settings.timeout, config:)
|
|
1041
1100
|
else
|
|
1042
1101
|
raise ConfigurationError, "#{settings.name || 'MCP'} needs a url, a command, or a transport"
|
|
1043
1102
|
end
|
|
1044
1103
|
end
|
|
1045
1104
|
|
|
1105
|
+
def idempotent?(name)
|
|
1106
|
+
definition = @server_tools&.find { |server_tool| server_tool['name'] == name }
|
|
1107
|
+
!definition.nil? && Tool.new(self, definition).idempotent?
|
|
1108
|
+
end
|
|
1109
|
+
|
|
1046
1110
|
def request_headers(verb)
|
|
1047
1111
|
headers = self.class.headers.transform_values { |value| resolve(value) }
|
|
1048
1112
|
return headers.merge(oauth.authorization_headers(verb)) if self.class.oauth_settings
|