ruby_llm 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +160 -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 +515 -274
- 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 +1593 -331
- 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 +50 -24
- 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 +127 -106
- 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 +15 -15
- 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 +1 -7
- 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 +5 -14
- 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 +213 -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
|
@@ -14,6 +14,7 @@ module RubyLLM
|
|
|
14
14
|
# their content is needed.
|
|
15
15
|
class Resource
|
|
16
16
|
include Support::Inspectable
|
|
17
|
+
include Files::Saving
|
|
17
18
|
|
|
18
19
|
# The URI that identifies the resource on its server.
|
|
19
20
|
attr_reader :uri
|
|
@@ -61,17 +62,19 @@ module RubyLLM
|
|
|
61
62
|
content.b
|
|
62
63
|
end
|
|
63
64
|
|
|
65
|
+
##
|
|
66
|
+
# :method: save
|
|
67
|
+
# :call-seq:
|
|
68
|
+
# save(path)
|
|
69
|
+
#
|
|
64
70
|
# Writes the content to +path+, expanding it first. Returns +path+.
|
|
65
|
-
def save(path)
|
|
66
|
-
File.binwrite(File.expand_path(path), to_blob)
|
|
67
|
-
path
|
|
68
|
-
end
|
|
69
71
|
|
|
70
72
|
# Returns the resource as an Attachment, which is how chats take it
|
|
71
|
-
# through +with:+.
|
|
73
|
+
# through +with:+. The attachment has the MIME type the server reports
|
|
74
|
+
# when the URI has no file extension.
|
|
72
75
|
def to_attachment
|
|
73
76
|
filename = Content.filename(uri)
|
|
74
|
-
|
|
77
|
+
Content.file(to_blob, mime_type, filename.empty? ? name : filename)
|
|
75
78
|
end
|
|
76
79
|
|
|
77
80
|
private
|
|
@@ -56,9 +56,10 @@ module RubyLLM
|
|
|
56
56
|
def self.expand_variable(spec, operator, separator, reserved, variables) # :nodoc:
|
|
57
57
|
explode = spec.end_with?('*')
|
|
58
58
|
name, length = spec.delete_suffix('*').split(':')
|
|
59
|
-
|
|
59
|
+
values = Array(variables[name])
|
|
60
|
+
return if values.empty?
|
|
60
61
|
|
|
61
|
-
parts =
|
|
62
|
+
parts = values.map { |part| encode(length ? part.to_s[0, length.to_i] : part.to_s, reserved) }
|
|
62
63
|
named = NAMED_OPERATORS.include?(operator)
|
|
63
64
|
return parts.map { |part| named ? "#{name}=#{part}" : part }.join(separator) if explode
|
|
64
65
|
|
|
@@ -4,7 +4,15 @@ module RubyLLM
|
|
|
4
4
|
class MCP
|
|
5
5
|
# Raised by a transport when the session of a server that predates
|
|
6
6
|
# 2026-07-28 has ended, so the client starts a new one: the server
|
|
7
|
-
# answered 404, or its stdio process exited.
|
|
8
|
-
|
|
7
|
+
# answered 404, or its stdio process exited. +session+ is the session
|
|
8
|
+
# that ended, as the transport's +session+ reader returned it.
|
|
9
|
+
class SessionExpired < Error # :nodoc:
|
|
10
|
+
attr_reader :session
|
|
11
|
+
|
|
12
|
+
def initialize(message = nil, session: nil, **)
|
|
13
|
+
@session = session
|
|
14
|
+
super(message, **)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
9
17
|
end
|
|
10
18
|
end
|
data/lib/ruby_llm/mcp/stdio.rb
CHANGED
|
@@ -12,6 +12,11 @@ module RubyLLM
|
|
|
12
12
|
# time. Its stderr is the parent's. A server that predates 2026-07-28
|
|
13
13
|
# keeps its session for the life of its process, so after a restart its
|
|
14
14
|
# requests raise SessionExpired until the client initializes it again.
|
|
15
|
+
# The process inherits only INHERITED_ENV from the application, and +env+
|
|
16
|
+
# adds to and overrides those: a Hash, or a callable that returns one,
|
|
17
|
+
# called each time the process starts. Where the platform has process
|
|
18
|
+
# groups the server leads its own, so stopping it reaches the processes
|
|
19
|
+
# it started.
|
|
15
20
|
#
|
|
16
21
|
# Subscriptions share the channel, so whichever thread reads a message
|
|
17
22
|
# that belongs to one hands it to the subscription's listener: a
|
|
@@ -19,9 +24,20 @@ module RubyLLM
|
|
|
19
24
|
# request reads. A listener stops when CancelledError is raised in its
|
|
20
25
|
# thread, so it reads and writes with that deferred.
|
|
21
26
|
class Stdio # :nodoc:
|
|
27
|
+
# Raised when the server's process exits or does not answer in time.
|
|
28
|
+
class Unavailable < Error # :nodoc:
|
|
29
|
+
end
|
|
30
|
+
|
|
22
31
|
SHUTDOWN_GRACE = 2
|
|
32
|
+
SHUTDOWN_CHECK = 0.05
|
|
23
33
|
CHECK_INTERVAL = 0.5
|
|
24
34
|
SUBSCRIPTION_ID = 'io.modelcontextprotocol/subscriptionId'
|
|
35
|
+
INHERITED_ENV = %w[
|
|
36
|
+
GEM_HOME GEM_PATH HOME LANG LC_ALL LC_CTYPE LOGNAME PATH SHELL TERM TMPDIR USER
|
|
37
|
+
APPDATA COMSPEC HOMEDRIVE HOMEPATH LOCALAPPDATA PATHEXT PROCESSOR_ARCHITECTURE
|
|
38
|
+
PROGRAMDATA PROGRAMFILES PROGRAMFILES(X86) PROGRAMW6432 SYSTEMDRIVE SYSTEMROOT
|
|
39
|
+
TEMP TMP USERNAME USERPROFILE WINDIR
|
|
40
|
+
].freeze
|
|
25
41
|
|
|
26
42
|
# The messages of the subscription opened by request +id+, or with no
|
|
27
43
|
# +id+, the changes a server that predates subscriptions announces.
|
|
@@ -39,9 +55,12 @@ module RubyLLM
|
|
|
39
55
|
end
|
|
40
56
|
end
|
|
41
57
|
|
|
58
|
+
# The session of a server that predates 2026-07-28: the process it was initialized in, or +nil+.
|
|
59
|
+
attr_reader :session
|
|
60
|
+
|
|
42
61
|
def initialize(command, env: {}, directory: nil, timeout: nil, config: RubyLLM.config)
|
|
43
62
|
@command = Array(command).map(&:to_s)
|
|
44
|
-
@env = env
|
|
63
|
+
@env = env
|
|
45
64
|
@directory = directory&.to_s
|
|
46
65
|
@timeout = timeout || config.request_timeout
|
|
47
66
|
@lock = Mutex.new
|
|
@@ -76,12 +95,10 @@ module RubyLLM
|
|
|
76
95
|
|
|
77
96
|
yield reply
|
|
78
97
|
end
|
|
79
|
-
raise
|
|
98
|
+
raise Unavailable, "#{name} exited" unless @process.equal?(process)
|
|
80
99
|
|
|
81
100
|
receive(subscription)
|
|
82
101
|
end
|
|
83
|
-
rescue IOError
|
|
84
|
-
raise Error, "#{name} exited"
|
|
85
102
|
ensure
|
|
86
103
|
uninterrupted { @lock.synchronize { @subscriptions.delete(subscription) } }
|
|
87
104
|
end
|
|
@@ -95,11 +112,11 @@ module RubyLLM
|
|
|
95
112
|
def check_session(version)
|
|
96
113
|
return if version.nil? || version == Client::VERSION || (@process&.alive? && @process.equal?(@session))
|
|
97
114
|
|
|
98
|
-
raise SessionExpired
|
|
115
|
+
raise SessionExpired.new("#{name} exited", session: @session)
|
|
99
116
|
end
|
|
100
117
|
|
|
101
118
|
def subscribe(subscription, message)
|
|
102
|
-
raise
|
|
119
|
+
raise Unavailable, "#{name} exited" unless message || @process&.alive?
|
|
103
120
|
|
|
104
121
|
@subscriptions << subscription
|
|
105
122
|
write(message) if message
|
|
@@ -120,6 +137,8 @@ module RubyLLM
|
|
|
120
137
|
return sleep(CHECK_INTERVAL) unless read
|
|
121
138
|
|
|
122
139
|
@stdout.wait_readable(CHECK_INTERVAL) if subscription.messages.empty?
|
|
140
|
+
rescue IOError
|
|
141
|
+
raise Unavailable, "#{name} exited"
|
|
123
142
|
end
|
|
124
143
|
|
|
125
144
|
def drain
|
|
@@ -164,11 +183,13 @@ module RubyLLM
|
|
|
164
183
|
|
|
165
184
|
def write(message)
|
|
166
185
|
start unless @process&.alive?
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
186
|
+
begin
|
|
187
|
+
@stdin.puts JSON.generate(message)
|
|
188
|
+
@stdin.flush
|
|
189
|
+
rescue Errno::EPIPE, IOError
|
|
190
|
+
stop
|
|
191
|
+
raise Unavailable, "#{name} exited"
|
|
192
|
+
end
|
|
172
193
|
end
|
|
173
194
|
|
|
174
195
|
def read(deadline)
|
|
@@ -194,7 +215,7 @@ module RubyLLM
|
|
|
194
215
|
return line if line
|
|
195
216
|
|
|
196
217
|
remaining = deadline - monotonic_now
|
|
197
|
-
raise
|
|
218
|
+
raise Unavailable, "#{name} did not answer in time" unless remaining.positive?
|
|
198
219
|
|
|
199
220
|
Support::Cancellation.check
|
|
200
221
|
next unless @stdout.wait_readable([remaining, CHECK_INTERVAL].min)
|
|
@@ -209,7 +230,7 @@ module RubyLLM
|
|
|
209
230
|
|
|
210
231
|
def exited
|
|
211
232
|
stop
|
|
212
|
-
raise
|
|
233
|
+
raise Unavailable, "#{name} exited"
|
|
213
234
|
end
|
|
214
235
|
|
|
215
236
|
def answer(request)
|
|
@@ -218,26 +239,53 @@ module RubyLLM
|
|
|
218
239
|
|
|
219
240
|
def start
|
|
220
241
|
options = @directory ? { chdir: @directory } : {}
|
|
221
|
-
|
|
242
|
+
options[:pgroup] = true if groups?
|
|
243
|
+
env = ENV.slice(*INHERITED_ENV).merge(environment)
|
|
244
|
+
@stdin, @stdout, @process = Open3.popen2(env, *@command, unsetenv_others: true, **options)
|
|
222
245
|
@buffer = +''
|
|
223
246
|
end
|
|
224
247
|
|
|
248
|
+
def environment
|
|
249
|
+
variables = @env.respond_to?(:call) ? @env.call : @env
|
|
250
|
+
variables.to_h { |key, value| [key.to_s, value.to_s] }
|
|
251
|
+
end
|
|
252
|
+
|
|
225
253
|
def stop
|
|
226
254
|
return unless @process
|
|
227
255
|
|
|
228
256
|
@stdin.close unless @stdin.closed?
|
|
229
257
|
@stdout.close unless @stdout.closed?
|
|
230
|
-
|
|
258
|
+
@process.join(SHUTDOWN_GRACE)
|
|
259
|
+
terminate if groups? || @process.alive?
|
|
231
260
|
@process = nil
|
|
232
261
|
end
|
|
233
262
|
|
|
234
263
|
def terminate
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
rescue Errno::ESRCH
|
|
264
|
+
signal('TERM')
|
|
265
|
+
signal('KILL') unless exited?
|
|
266
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
238
267
|
nil
|
|
239
268
|
end
|
|
240
269
|
|
|
270
|
+
def exited?
|
|
271
|
+
deadline = monotonic_now + SHUTDOWN_GRACE
|
|
272
|
+
while monotonic_now < deadline
|
|
273
|
+
signal(0)
|
|
274
|
+
sleep SHUTDOWN_CHECK
|
|
275
|
+
end
|
|
276
|
+
false
|
|
277
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
278
|
+
true
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def signal(signal)
|
|
282
|
+
Process.kill(signal, groups? ? -@process.pid : @process.pid)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def groups?
|
|
286
|
+
!Gem.win_platform?
|
|
287
|
+
end
|
|
288
|
+
|
|
241
289
|
def name
|
|
242
290
|
File.basename(@command.first.to_s)
|
|
243
291
|
end
|
data/lib/ruby_llm/mcp/task.rb
CHANGED
|
@@ -18,30 +18,22 @@ module RubyLLM
|
|
|
18
18
|
# task.poll_interval # => 5.0
|
|
19
19
|
#
|
|
20
20
|
# MCP#call waits for the task of a tool you call directly.
|
|
21
|
-
class Task
|
|
22
|
-
include Support::Inspectable
|
|
23
|
-
|
|
24
|
-
DONE = %i[completed failed cancelled].freeze
|
|
25
|
-
private_constant :DONE
|
|
26
|
-
|
|
21
|
+
class Task < RubyLLM::Task
|
|
27
22
|
# The task's id on its server.
|
|
28
23
|
attr_reader :id
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
attr_reader :tool_call
|
|
32
|
-
|
|
33
|
-
attr_reader :data, :answered # :nodoc:
|
|
25
|
+
attr_reader :answered # :nodoc:
|
|
34
26
|
|
|
35
27
|
def self.load(mcp, state, tool_call: nil) # :nodoc:
|
|
36
28
|
new(mcp, state['task'], answered: state['answered'], tool_call:)
|
|
37
29
|
end
|
|
38
30
|
|
|
39
31
|
def initialize(mcp, data, answered: nil, tool_call: nil) # :nodoc:
|
|
32
|
+
super(tool_call:)
|
|
40
33
|
@mcp = mcp
|
|
41
34
|
@data = data
|
|
42
35
|
@id = data['taskId']
|
|
43
36
|
@answered = Array(answered)
|
|
44
|
-
@tool_call = tool_call
|
|
45
37
|
end
|
|
46
38
|
|
|
47
39
|
# The task's state: +:working+, +:input_required+, +:completed+,
|
|
@@ -68,43 +60,23 @@ module RubyLLM
|
|
|
68
60
|
Time.iso8601(data['createdAt']) + (data['ttlMs'] / 1000.0) if data['ttlMs'] && data['createdAt']
|
|
69
61
|
end
|
|
70
62
|
|
|
71
|
-
# Returns
|
|
72
|
-
def done?
|
|
73
|
-
DONE.include?(status)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Returns +true+ when the task finished with a #result.
|
|
77
|
-
def completed?
|
|
78
|
-
status == :completed
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Returns +true+ when the task failed with an error.
|
|
82
|
-
def failed?
|
|
83
|
-
status == :failed
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Returns +true+ when the task was cancelled before it finished.
|
|
87
|
-
def cancelled?
|
|
88
|
-
status == :cancelled
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Returns the MCP::Result once the task #completed?, and +nil+ until
|
|
63
|
+
# Returns the MCP::Result once the task #succeeded?, and +nil+ until
|
|
92
64
|
# then. Raises MCP::Error when the task failed or was cancelled.
|
|
93
65
|
def result
|
|
94
66
|
raise error if failed? || cancelled?
|
|
95
67
|
|
|
96
|
-
Result.new(data['result']) if
|
|
68
|
+
Result.new(data['result']) if succeeded?
|
|
97
69
|
end
|
|
98
70
|
|
|
99
71
|
# Checks on the task once and returns +self+, which now tells whether
|
|
100
|
-
# the task is #
|
|
101
|
-
# the task is #
|
|
72
|
+
# the task is #complete? and how soon to check again. Does nothing once
|
|
73
|
+
# the task is #complete?.
|
|
102
74
|
def refresh
|
|
103
|
-
@data = connected.poll_task(id) unless
|
|
75
|
+
@data = connected.poll_task(id) unless complete?
|
|
104
76
|
self
|
|
105
77
|
end
|
|
106
78
|
|
|
107
|
-
# Checks on the task until it is #
|
|
79
|
+
# Checks on the task until it is #complete?, sleeping #poll_interval
|
|
108
80
|
# seconds in between, and returns +self+. Answers the server's
|
|
109
81
|
# requests for input with MCP.before_input_request callbacks.
|
|
110
82
|
# +timeout+ defaults to the server's MCP.timeout, and the task is
|
|
@@ -119,14 +91,15 @@ module RubyLLM
|
|
|
119
91
|
# Asks the server to cancel the task, and returns +self+. The server
|
|
120
92
|
# may still finish it, so check with #refresh.
|
|
121
93
|
def cancel
|
|
122
|
-
connected.cancel_task(id) unless
|
|
94
|
+
connected.cancel_task(id) unless complete?
|
|
123
95
|
self
|
|
124
96
|
end
|
|
125
97
|
|
|
126
98
|
def error # :nodoc:
|
|
127
99
|
details = data['error'] || {}
|
|
128
|
-
|
|
129
|
-
|
|
100
|
+
reported = details['message'] || status_message
|
|
101
|
+
Error.new(("Task #{id} was #{status}" unless reported), provider_message: reported,
|
|
102
|
+
code: details['code'], data: details['data'])
|
|
130
103
|
end
|
|
131
104
|
|
|
132
105
|
def record_answers(keys) # :nodoc:
|
data/lib/ruby_llm/mcp/tool.rb
CHANGED
|
@@ -45,6 +45,11 @@ module RubyLLM
|
|
|
45
45
|
|
|
46
46
|
attr_reader :fixed_arguments, :wrap # :nodoc:
|
|
47
47
|
|
|
48
|
+
# The fixed arguments, with lambdas called on the MCP instance.
|
|
49
|
+
def resolved_fixed_arguments # :nodoc:
|
|
50
|
+
fixed_arguments.transform_values { |value| value.is_a?(Proc) ? @mcp.instance_exec(&value) : value }
|
|
51
|
+
end
|
|
52
|
+
|
|
48
53
|
def initialize(mcp, definition, prefix: nil, as: nil, description: nil, fixed_arguments: {}, wrap: nil) # :nodoc:
|
|
49
54
|
super()
|
|
50
55
|
@mcp = mcp
|
|
@@ -53,7 +58,7 @@ module RubyLLM
|
|
|
53
58
|
@description = description || definition['description']
|
|
54
59
|
@fixed_arguments = fixed_arguments.transform_keys(&:to_sym)
|
|
55
60
|
@wrap = wrap
|
|
56
|
-
@annotations = definition['annotations']
|
|
61
|
+
@annotations = definition['annotations'].is_a?(Hash) ? definition['annotations'] : {}
|
|
57
62
|
@meta = definition['_meta'] || {}
|
|
58
63
|
@ui_uri = Apps.uri(meta)
|
|
59
64
|
@visibility = Apps.visibility(meta)
|
|
@@ -73,7 +78,8 @@ module RubyLLM
|
|
|
73
78
|
end
|
|
74
79
|
|
|
75
80
|
# Returns whether calling the tool again with the same arguments has
|
|
76
|
-
# no further effect.
|
|
81
|
+
# no further effect. Only these calls are sent again when a stream
|
|
82
|
+
# breaks before the answer.
|
|
77
83
|
def idempotent?
|
|
78
84
|
@annotations['idempotentHint'] == true
|
|
79
85
|
end
|
|
@@ -85,32 +91,49 @@ module RubyLLM
|
|
|
85
91
|
|
|
86
92
|
# Returns whether the tool pauses for approval, as declared with
|
|
87
93
|
# MCP.requires_approval.
|
|
88
|
-
def requires_approval?
|
|
94
|
+
def requires_approval?(_tool_call = nil)
|
|
89
95
|
@mcp.requires_approval?(self)
|
|
90
96
|
end
|
|
91
97
|
|
|
92
98
|
# Calls the tool on the server and returns its MCP::Result, what the
|
|
93
99
|
# +wrap:+ method made of it, or <tt>{ error: }</tt> when the tool
|
|
94
|
-
# failed
|
|
95
|
-
#
|
|
96
|
-
#
|
|
100
|
+
# failed, or when the server failed the call: it could not be
|
|
101
|
+
# reached, answered HTTP 429 or a 5xx status, or answered with a
|
|
102
|
+
# JSON-RPC error. A server that could not be reached sits out the
|
|
103
|
+
# chat's next requests, as when listing its tools. A refusal, such as
|
|
104
|
+
# missing credentials, still raises. A tool with a UI returns its
|
|
105
|
+
# failed MCP::Result instead, so your app can still render the UI. A
|
|
106
|
+
# chat sends the model the result's content. When the server runs
|
|
107
|
+
# the call as a task, returns the MCP::Task without waiting, and a
|
|
108
|
+
# chat pauses the tool call until the task is done.
|
|
97
109
|
# Raises MCP::InputRequiredError when the server needs input that no
|
|
98
110
|
# MCP.before_input_request callback gave.
|
|
99
111
|
def call(**arguments)
|
|
100
112
|
@mcp.run(self, arguments.except(:tool_call))
|
|
101
113
|
end
|
|
102
114
|
|
|
103
|
-
# Resumes a call that paused on
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
@mcp.run(self, arguments, input:)
|
|
115
|
+
# Resumes a call that paused on +task+, which it checks on once.
|
|
116
|
+
def resume(task, **arguments)
|
|
117
|
+
@mcp.run(self, arguments, input: task.to_h)
|
|
107
118
|
end
|
|
108
119
|
|
|
109
120
|
# Returns the MCP::Task a call paused on, from its saved state.
|
|
110
|
-
def task(state, tool_call: nil)
|
|
121
|
+
def task(state, tool_call: nil)
|
|
111
122
|
Task.load(@mcp, state, tool_call:)
|
|
112
123
|
end
|
|
113
124
|
|
|
125
|
+
def resume_call(state, arguments, **) # :nodoc:
|
|
126
|
+
@mcp.run(self, arguments, input: state)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Runs the handler the MCP class declares for +exception+, or else
|
|
130
|
+
# one declared on this class.
|
|
131
|
+
def rescue_with_handler(exception) # :nodoc:
|
|
132
|
+
return super unless @mcp.class.rescue_handler_for(exception)
|
|
133
|
+
|
|
134
|
+
@mcp.rescue_with_handler(exception)
|
|
135
|
+
end
|
|
136
|
+
|
|
114
137
|
private
|
|
115
138
|
|
|
116
139
|
def model_schema(schema)
|