swarm_sdk 2.7.12 → 2.7.14

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 700ee5f9b81c58e030024d664a7c3e27db84c705c876493678c89371f803158a
4
- data.tar.gz: b2a4e2d7dc72208bff64b9c8d5f72dbe9d2772ae5f2f61d8abf3973d5855867c
3
+ metadata.gz: 22ea4e05517149f4cb6f6f18e896e9493e9ed75f69e82bd6891a9b0b471e314e
4
+ data.tar.gz: 94f2f1b7c28fe626889aa4f1fbb611bb36ac4d73ab7de778f76e553538589cbb
5
5
  SHA512:
6
- metadata.gz: 7e375f31f8419422c3b93065f0fa525d63ae063865b2dd62032ac42ff4e9f827111f5cf1dc13cc18e27e215e9e1a0924965791155f553d5af9261f53580680e4
7
- data.tar.gz: f44a0e73852254048fe7219efed8db620bc41f4d274bfd938997ac5281a79c6cfa575e3be7631e404bbd84acfc3dd25649130a06fa10b85d4cac6a0099007d11
6
+ metadata.gz: 2360f50b71a7c2342f2301a8dc5362b0e92402f16bdaee19dc7932b395a33f37e12a385fe752b69411e481ed10418396b7245a5d61669f7d41d12b6f7d0c3f40
7
+ data.tar.gz: 076b1ca993b017aeabfe5e35daa08be81e4cd815e314b2e28b594f298ae2b5b430101a0c9ea01d5263689b21ad888e4fb17d06c4a59904aa38734308a21ad130
@@ -63,6 +63,7 @@ module SwarmSDK
63
63
  @shared_across_delegations = nil # nil = not set (will default to false in Definition)
64
64
  @streaming = nil # nil = not set (will use global config default)
65
65
  @thinking = nil # nil = not set (extended thinking disabled)
66
+ @disable_environment_info = nil # nil = not set (will default to false in Definition)
66
67
  @context_management_config = nil # Context management DSL hooks
67
68
  end
68
69
 
@@ -529,6 +530,29 @@ module SwarmSDK
529
530
  !@coding_agent.nil?
530
531
  end
531
532
 
533
+ # Disable environment info (date, platform, OS, working directory) in system prompt
534
+ #
535
+ # When true, omits the environment information section from the agent's system prompt.
536
+ # Defaults to false if not set.
537
+ #
538
+ # @param enabled [Boolean] Whether to disable environment info
539
+ # @return [void]
540
+ #
541
+ # @example
542
+ # disable_environment_info true # Omit environment info from prompt
543
+ def disable_environment_info(enabled)
544
+ @disable_environment_info = enabled
545
+ end
546
+
547
+ # Check if disable_environment_info has been explicitly set
548
+ #
549
+ # Used by Swarm::Builder to determine if all_agents disable_environment_info should apply.
550
+ #
551
+ # @return [Boolean] true if disable_environment_info was explicitly set
552
+ def disable_environment_info_set?
553
+ !@disable_environment_info.nil?
554
+ end
555
+
532
556
  # Check if parameters have been set
533
557
  #
534
558
  # Used by Swarm::Builder for merging all_agents parameters.
@@ -586,6 +610,7 @@ module SwarmSDK
586
610
  agent_config[:shared_across_delegations] = @shared_across_delegations unless @shared_across_delegations.nil?
587
611
  agent_config[:streaming] = @streaming unless @streaming.nil?
588
612
  agent_config[:thinking] = @thinking if @thinking
613
+ agent_config[:disable_environment_info] = @disable_environment_info unless @disable_environment_info.nil?
589
614
 
590
615
  # Convert DSL hooks to HookDefinition format
591
616
  agent_config[:hooks] = convert_hooks_to_definitions if @hooks.any?
@@ -526,17 +526,24 @@ module SwarmSDK
526
526
  #
527
527
  # This method:
528
528
  # 1. Serializes concurrent asks via @ask_semaphore
529
- # 2. Adds CLEAN user message to history (no reminders)
530
- # 3. Injects system reminders as ephemeral content (sent to LLM but not stored)
531
- # 4. Triggers user_prompt hooks
532
- # 5. Acquires global semaphore for LLM call
533
- # 6. Delegates to RubyLLM::Chat for actual execution
529
+ # 2. Optionally clears conversation context (inside semaphore for safety)
530
+ # 3. Adds CLEAN user message to history (no reminders)
531
+ # 4. Injects system reminders as ephemeral content (sent to LLM but not stored)
532
+ # 5. Triggers user_prompt hooks
533
+ # 6. Acquires global semaphore for LLM call
534
+ # 7. Delegates to RubyLLM::Chat for actual execution
534
535
  #
535
536
  # @param prompt [String] User prompt
537
+ # @param clear_context [Boolean] When true, clears conversation history before
538
+ # processing. Clearing happens inside the ask_semaphore, making it safe for
539
+ # concurrent callers (e.g., parallel delegations to the same agent).
536
540
  # @param options [Hash] Additional options (source: for hooks)
537
541
  # @return [RubyLLM::Message] LLM response
538
- def ask(prompt, **options)
542
+ def ask(prompt, clear_context: false, **options)
539
543
  @ask_semaphore.acquire do
544
+ # Clear inside semaphore so concurrent callers don't corrupt each other's messages
545
+ clear_conversation if clear_context
546
+
540
547
  if @turn_timeout
541
548
  execute_with_turn_timeout(prompt, options)
542
549
  else
@@ -651,57 +658,63 @@ module SwarmSDK
651
658
 
652
659
  # Execute ask without timeout (original ask implementation)
653
660
  def execute_ask(prompt, options)
654
- is_first = first_message?
655
-
656
- # Collect system reminders to inject as ephemeral content
657
- reminders = collect_system_reminders(prompt, is_first)
661
+ @hook_swarm&.mark_agent_active(@agent_name, self)
658
662
 
659
- # Trigger user_prompt hook (with clean prompt, not reminders)
660
- source = options.delete(:source) || "user"
661
- final_prompt = prompt
662
- if @hook_executor
663
- hook_result = trigger_user_prompt(prompt, source: source)
663
+ begin
664
+ is_first = first_message?
665
+
666
+ # Collect system reminders to inject as ephemeral content
667
+ reminders = collect_system_reminders(prompt, is_first)
668
+
669
+ # Trigger user_prompt hook (with clean prompt, not reminders)
670
+ source = options.delete(:source) || "user"
671
+ final_prompt = prompt
672
+ if @hook_executor
673
+ hook_result = trigger_user_prompt(prompt, source: source)
674
+
675
+ if hook_result[:halted]
676
+ return RubyLLM::Message.new(
677
+ role: :assistant,
678
+ content: hook_result[:halt_message],
679
+ model_id: model_id,
680
+ )
681
+ end
664
682
 
665
- if hook_result[:halted]
666
- return RubyLLM::Message.new(
667
- role: :assistant,
668
- content: hook_result[:halt_message],
669
- model_id: model_id,
670
- )
683
+ final_prompt = hook_result[:modified_prompt] if hook_result[:modified_prompt]
671
684
  end
672
685
 
673
- final_prompt = hook_result[:modified_prompt] if hook_result[:modified_prompt]
674
- end
675
-
676
- # Add CLEAN user message to history (no reminders embedded)
677
- @llm_chat.add_message(role: :user, content: final_prompt)
686
+ # Add CLEAN user message to history (no reminders embedded)
687
+ @llm_chat.add_message(role: :user, content: final_prompt)
678
688
 
679
- # Track reminders as ephemeral content for this LLM call only
680
- # They'll be injected by around_llm_request hook but not stored
681
- reminders.each do |reminder|
682
- @context_manager.add_ephemeral_reminder(reminder, messages_array: @llm_chat.messages)
683
- end
684
-
685
- # Execute complete() which handles tool loop and ephemeral injection
686
- response = execute_with_global_semaphore do
687
- catch(:finish_agent) do
688
- catch(:finish_swarm) do
689
- if @streaming_enabled
690
- # Reset chunk type tracking for new streaming request
691
- @last_chunk_type = nil
689
+ # Track reminders as ephemeral content for this LLM call only
690
+ # They'll be injected by around_llm_request hook but not stored
691
+ reminders.each do |reminder|
692
+ @context_manager.add_ephemeral_reminder(reminder, messages_array: @llm_chat.messages)
693
+ end
692
694
 
693
- @llm_chat.complete(**options) do |chunk|
694
- emit_content_chunk(chunk)
695
+ # Execute complete() which handles tool loop and ephemeral injection
696
+ response = execute_with_global_semaphore do
697
+ catch(:finish_agent) do
698
+ catch(:finish_swarm) do
699
+ if @streaming_enabled
700
+ # Reset chunk type tracking for new streaming request
701
+ @last_chunk_type = nil
702
+
703
+ @llm_chat.complete(**options) do |chunk|
704
+ emit_content_chunk(chunk)
705
+ end
706
+ else
707
+ @llm_chat.complete(**options)
695
708
  end
696
- else
697
- @llm_chat.complete(**options)
698
709
  end
699
710
  end
700
711
  end
701
- end
702
712
 
703
- # Handle finish markers from hooks
704
- handle_finish_marker(response)
713
+ # Handle finish markers from hooks
714
+ handle_finish_marker(response)
715
+ ensure
716
+ @hook_swarm&.mark_agent_inactive(@agent_name)
717
+ end
705
718
  end
706
719
 
707
720
  # --- Tool Execution Hook ---
@@ -43,7 +43,8 @@ module SwarmSDK
43
43
  :plugin_configs,
44
44
  :shared_across_delegations,
45
45
  :streaming,
46
- :thinking
46
+ :thinking,
47
+ :disable_environment_info
47
48
 
48
49
  attr_accessor :bypass_permissions, :max_concurrent_tools
49
50
 
@@ -118,6 +119,9 @@ module SwarmSDK
118
119
  # Extended thinking configuration (nil = disabled)
119
120
  @thinking = config[:thinking]
120
121
 
122
+ # When true, omits date/platform/OS/working directory from system prompts
123
+ @disable_environment_info = config.fetch(:disable_environment_info, false)
124
+
121
125
  # Build system prompt after directory and memory are set
122
126
  @system_prompt = build_full_system_prompt(config[:system_prompt])
123
127
 
@@ -201,6 +205,7 @@ module SwarmSDK
201
205
  hooks: @hooks,
202
206
  shared_across_delegations: @shared_across_delegations,
203
207
  streaming: @streaming,
208
+ disable_environment_info: @disable_environment_info,
204
209
  # Permissions are core SDK functionality (not plugin-specific)
205
210
  default_permissions: @default_permissions,
206
211
  permissions: @agent_permissions,
@@ -301,6 +306,7 @@ module SwarmSDK
301
306
  disable_default_tools: @disable_default_tools,
302
307
  directory: @directory,
303
308
  definition: self,
309
+ disable_environment_info: @disable_environment_info,
304
310
  )
305
311
  end
306
312
 
@@ -394,6 +400,7 @@ module SwarmSDK
394
400
  :shared_across_delegations,
395
401
  :streaming,
396
402
  :directories,
403
+ :disable_environment_info,
397
404
  ]
398
405
 
399
406
  config.reject { |k, _| standard_keys.include?(k.to_sym) }
@@ -82,6 +82,7 @@ module SwarmSDK
82
82
  def emit_request_event(env, timestamp)
83
83
  request_data = {
84
84
  provider: @provider_name,
85
+ url: env.url.to_s,
85
86
  body: parse_body(env.body),
86
87
  timestamp: timestamp.utc.iso8601,
87
88
  }
@@ -28,24 +28,29 @@ module SwarmSDK
28
28
  # @param disable_default_tools [Boolean, Array, nil] Default tools disable configuration
29
29
  # @param directory [String] Agent's working directory
30
30
  # @param definition [Definition] Full definition for plugin contributions
31
+ # @param disable_environment_info [Boolean] Whether to omit environment info from prompt
31
32
  # @return [String] Complete system prompt
32
- def build(custom_prompt:, coding_agent:, disable_default_tools:, directory:, definition:)
33
+ def build(custom_prompt:, coding_agent:, disable_default_tools:, directory:, definition:,
34
+ disable_environment_info: false)
33
35
  new(
34
36
  custom_prompt: custom_prompt,
35
37
  coding_agent: coding_agent,
36
38
  disable_default_tools: disable_default_tools,
37
39
  directory: directory,
38
40
  definition: definition,
41
+ disable_environment_info: disable_environment_info,
39
42
  ).build
40
43
  end
41
44
  end
42
45
 
43
- def initialize(custom_prompt:, coding_agent:, disable_default_tools:, directory:, definition:)
46
+ def initialize(custom_prompt:, coding_agent:, disable_default_tools:, directory:, definition:,
47
+ disable_environment_info: false)
44
48
  @custom_prompt = custom_prompt
45
49
  @coding_agent = coding_agent
46
50
  @disable_default_tools = disable_default_tools
47
51
  @directory = directory
48
52
  @definition = definition
53
+ @disable_environment_info = disable_environment_info
49
54
  end
50
55
 
51
56
  def build
@@ -80,7 +85,11 @@ module SwarmSDK
80
85
  non_coding_base = render_non_coding_base_prompt
81
86
 
82
87
  if @custom_prompt && !@custom_prompt.strip.empty?
83
- "#{non_coding_base}\n\n#{@custom_prompt}"
88
+ if non_coding_base.empty?
89
+ @custom_prompt.to_s
90
+ else
91
+ "#{non_coding_base}\n\n#{@custom_prompt}"
92
+ end
84
93
  else
85
94
  non_coding_base
86
95
  end
@@ -91,6 +100,7 @@ module SwarmSDK
91
100
  end
92
101
 
93
102
  def render_base_system_prompt
103
+ disable_environment_info = @disable_environment_info
94
104
  cwd = @directory || Dir.pwd
95
105
  platform = RUBY_PLATFORM
96
106
  os_version = begin
@@ -105,6 +115,8 @@ module SwarmSDK
105
115
  end
106
116
 
107
117
  def render_non_coding_base_prompt
118
+ return "" if @disable_environment_info
119
+
108
120
  cwd = @directory || Dir.pwd
109
121
  platform = RUBY_PLATFORM
110
122
  os_version = begin
@@ -265,6 +265,7 @@ module SwarmSDK
265
265
  builder.parameters(config[:parameters]) if config[:parameters]
266
266
  builder.headers(config[:headers]) if config[:headers]
267
267
  builder.coding_agent(config[:coding_agent]) unless config[:coding_agent].nil?
268
+ builder.disable_environment_info(config[:disable_environment_info]) unless config[:disable_environment_info].nil?
268
269
  builder.bypass_permissions(config[:bypass_permissions]) if config[:bypass_permissions]
269
270
  builder.disable_default_tools(config[:disable_default_tools]) unless config[:disable_default_tools].nil?
270
271
 
@@ -451,6 +452,10 @@ module SwarmSDK
451
452
  agent_builder.streaming(all_agents_hash[:streaming])
452
453
  end
453
454
 
455
+ if !all_agents_hash[:disable_environment_info].nil? && !agent_builder.disable_environment_info_set?
456
+ agent_builder.disable_environment_info(all_agents_hash[:disable_environment_info])
457
+ end
458
+
454
459
  if all_agents_hash[:thinking] && !agent_builder.thinking_set?
455
460
  agent_builder.thinking(**all_agents_hash[:thinking])
456
461
  end
@@ -31,6 +31,9 @@ module SwarmSDK
31
31
 
32
32
  @mcp_clients.clear
33
33
 
34
+ # Shutdown sub-swarms if registry exists
35
+ @swarm_registry&.shutdown_all
36
+
34
37
  # Clear delegation instances
35
38
  delegation_instances_hash&.clear
36
39
  end
@@ -105,6 +105,7 @@ module SwarmSDK
105
105
  allow_filesystem_tools: ["SWARM_SDK_ALLOW_FILESYSTEM_TOOLS", true],
106
106
  env_interpolation: ["SWARM_SDK_ENV_INTERPOLATION", true],
107
107
  streaming: ["SWARM_SDK_STREAMING", true],
108
+ mcp_ssl_verify: ["SWARM_SDK_MCP_SSL_VERIFY", true],
108
109
  }.freeze
109
110
 
110
111
  class << self
@@ -345,7 +346,7 @@ module SwarmSDK
345
346
  # @return [Integer, Float, Boolean, String] The parsed value
346
347
  def parse_env_value(value, key)
347
348
  case key
348
- when :allow_filesystem_tools, :env_interpolation, :streaming
349
+ when :allow_filesystem_tools, :env_interpolation, :streaming, :mcp_ssl_verify
349
350
  # Convert string to boolean
350
351
  case value.to_s.downcase
351
352
  when "true", "yes", "1", "on", "enabled"
@@ -100,6 +100,7 @@ module SwarmSDK
100
100
  coding_agent(all_agents_cfg[:coding_agent]) unless all_agents_cfg[:coding_agent].nil?
101
101
  disable_default_tools(all_agents_cfg[:disable_default_tools]) unless all_agents_cfg[:disable_default_tools].nil?
102
102
  streaming(all_agents_cfg[:streaming]) unless all_agents_cfg[:streaming].nil?
103
+ disable_environment_info(all_agents_cfg[:disable_environment_info]) unless all_agents_cfg[:disable_environment_info].nil?
103
104
  thinking(**all_agents_cfg[:thinking]) if all_agents_cfg[:thinking]
104
105
 
105
106
  if all_agents_hks.any?
@@ -165,6 +166,7 @@ module SwarmSDK
165
166
  disable_default_tools(config[:disable_default_tools]) unless config[:disable_default_tools].nil?
166
167
  shared_across_delegations(config[:shared_across_delegations]) unless config[:shared_across_delegations].nil?
167
168
  streaming(config[:streaming]) unless config[:streaming].nil?
169
+ disable_environment_info(config[:disable_environment_info]) unless config[:disable_environment_info].nil?
168
170
  thinking(**config[:thinking]) if config[:thinking]
169
171
 
170
172
  if config[:tools]&.any?
@@ -75,6 +75,18 @@ module SwarmSDK
75
75
  end
76
76
  end
77
77
 
78
+ # Stop all observer tasks immediately
79
+ #
80
+ # Interrupts in-flight observer LLM calls by stopping the barrier.
81
+ # Called during swarm interruption instead of wait_for_completion.
82
+ #
83
+ # @return [void]
84
+ def stop
85
+ @barrier&.stop
86
+ rescue StandardError => e
87
+ RubyLLM.logger.debug("SwarmSDK: Error stopping observer barrier: #{e.message}")
88
+ end
89
+
78
90
  # Cleanup all subscriptions
79
91
  #
80
92
  # Unsubscribes from LogCollector to prevent memory leaks.
@@ -90,6 +90,7 @@ NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTAN
90
90
  You MUST answer concisely with fewer than 4 lines of text (not including tool use or code generation), unless user asks for detail.
91
91
 
92
92
 
93
+ <% unless disable_environment_info %>
93
94
  # Today's date
94
95
 
95
96
  <today-date>
@@ -104,6 +105,7 @@ Working directory: <%= cwd %>
104
105
  Platform: <%= platform %>
105
106
  OS Version: <%= os_version %>
106
107
  </env>
108
+ <% end %>
107
109
 
108
110
  IMPORTANT: Assist with defensive security tasks only. Refuse to create, modify, or improve code that may be used maliciously. Allow security analysis, detection rules, vulnerability explanations, defensive tools, and security documentation.
109
111
 
@@ -23,6 +23,35 @@ module SwarmSDK
23
23
  !success?
24
24
  end
25
25
 
26
+ # Check if execution was interrupted via swarm.stop
27
+ #
28
+ # @return [Boolean] true if execution was interrupted
29
+ #
30
+ # @example
31
+ # result = swarm.execute("Build auth")
32
+ # result.interrupted? # => false
33
+ #
34
+ # # After calling swarm.stop during execution:
35
+ # result.interrupted? # => true
36
+ def interrupted?
37
+ @metadata[:interrupted] == true
38
+ end
39
+
40
+ # Get the reason execution finished
41
+ #
42
+ # Possible values:
43
+ # - "finished" - Normal completion
44
+ # - "interrupted" - Stopped via swarm.stop
45
+ # - "error" - Execution failed with an error
46
+ #
47
+ # @return [String] The finish reason
48
+ #
49
+ # @example
50
+ # result.finish_reason # => "finished"
51
+ def finish_reason
52
+ @metadata[:finish_reason] || (success? ? "finished" : "error")
53
+ end
54
+
26
55
  # Calculate total cost from logs
27
56
  #
28
57
  # Delegates to total_cost for consistency. This attribute is calculated
@@ -153,30 +153,34 @@ module RubyLLM
153
153
  end
154
154
  end
155
155
 
156
- # Override complete to use emit() and support around_llm_request hook
157
- # Follows fork pattern: tool call handling wraps message addition
156
+ # Override complete to use emit() and support around_llm_request hook.
157
+ # Uses a trampoline loop instead of mutual recursion with handle_tool_calls
158
+ # to avoid stack growth during multi-round tool-call conversations.
158
159
  def complete(&block)
159
- # Execute LLM request (potentially wrapped by around_llm_request hook)
160
- response = execute_llm_request(&block)
160
+ loop do
161
+ response = execute_llm_request(&block)
161
162
 
162
- emit(:new_message) unless block_given?
163
+ emit(:new_message) unless block_given?
163
164
 
164
- if @schema && response.content.is_a?(String)
165
- begin
166
- response.content = JSON.parse(response.content)
167
- rescue JSON::ParserError
168
- # If parsing fails, keep content as string
165
+ if @schema && response.content.is_a?(String)
166
+ begin
167
+ response.content = JSON.parse(response.content)
168
+ rescue JSON::ParserError
169
+ # If parsing fails, keep content as string
170
+ end
169
171
  end
170
- end
171
172
 
172
- add_message(response)
173
- emit(:end_message, response)
174
- if response.tool_call?
175
- # For tool calls: add message, emit end_message, then handle tools
176
- handle_tool_calls(response, &block)
177
- else
178
- # For final responses: add message and emit end_message
179
- response
173
+ add_message(response)
174
+ emit(:end_message, response)
175
+
176
+ if response.tool_call?
177
+ halt_result = handle_tool_calls(response, &block)
178
+ return halt_result if halt_result
179
+
180
+ # Loop continues: next LLM call with zero stack growth
181
+ else
182
+ return response
183
+ end
180
184
  end
181
185
  end
182
186
 
@@ -238,8 +242,9 @@ module RubyLLM
238
242
  end
239
243
  end
240
244
 
241
- # Override handle_tool_calls to use emit and support around_tool_execution hook
242
- def handle_tool_calls(response, &block)
245
+ # Execute tool calls and return halt result (or nil to continue the loop).
246
+ # Does NOT recurse back into complete() — the trampoline loop handles that.
247
+ def handle_tool_calls(response, &_block)
243
248
  halt_result = nil
244
249
 
245
250
  response.tool_calls.each_value do |tool_call|
@@ -259,7 +264,7 @@ module RubyLLM
259
264
  halt_result = result if result.is_a?(Tool::Halt)
260
265
  end
261
266
 
262
- halt_result || complete(&block)
267
+ halt_result
263
268
  end
264
269
 
265
270
  # Execute tool with around_tool_execution hook if set
@@ -3,6 +3,7 @@
3
3
  # Extends RubyLLM::Configuration with additional options:
4
4
  # - anthropic_api_base: Configurable Anthropic API base URL
5
5
  # - read_timeout, open_timeout, write_timeout: Granular timeout configuration
6
+ # - Fixes Anthropic completion_url leading slash that breaks proxy base URLs
6
7
  #
7
8
  # Fork Reference: Commits da6144b, 3daa4fb
8
9
 
@@ -29,13 +30,25 @@ module RubyLLM
29
30
  end
30
31
  end
31
32
 
32
- # Patch Anthropic provider to use configurable base URL
33
+ # Patch Anthropic provider to use configurable base URL and fix completion_url
33
34
  module Providers
34
35
  class Anthropic
35
36
  # Override api_base to use configurable base URL
36
37
  def api_base
37
38
  @config.anthropic_api_base || "https://api.anthropic.com"
38
39
  end
40
+
41
+ # Fix completion_url to use relative path (no leading slash).
42
+ # The leading slash causes Faraday to discard the base URL path component,
43
+ # breaking proxy configurations where api_base includes a path segment
44
+ # (e.g., https://proxy.dev/apis/anthropic/v1/messages → https://proxy.dev/v1/messages).
45
+ # stream_url delegates to completion_url, so this fixes both sync and streaming.
46
+ # Can be removed once RubyLLM releases a version including upstream fix (commit da6144b).
47
+ module Chat
48
+ def completion_url
49
+ "v1/messages"
50
+ end
51
+ end
39
52
  end
40
53
  end
41
54
  end
@@ -39,3 +39,6 @@ require_relative "message_management_patch"
39
39
 
40
40
  # 7. Responses API patch (depends on configuration, uses error classes)
41
41
  require_relative "responses_api_patch"
42
+
43
+ # 8. MCP SSL patch (configures SSL for HTTPX connections in ruby_llm-mcp)
44
+ require_relative "mcp_ssl_patch"