swarm_memory 2.1.5 → 2.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_memory/version.rb +1 -1
  3. metadata +5 -184
  4. data/lib/claude_swarm/base_executor.rb +0 -133
  5. data/lib/claude_swarm/claude_code_executor.rb +0 -349
  6. data/lib/claude_swarm/claude_mcp_server.rb +0 -78
  7. data/lib/claude_swarm/cli.rb +0 -697
  8. data/lib/claude_swarm/commands/ps.rb +0 -215
  9. data/lib/claude_swarm/commands/show.rb +0 -139
  10. data/lib/claude_swarm/configuration.rb +0 -373
  11. data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
  12. data/lib/claude_swarm/json_handler.rb +0 -91
  13. data/lib/claude_swarm/mcp_generator.rb +0 -230
  14. data/lib/claude_swarm/openai/chat_completion.rb +0 -256
  15. data/lib/claude_swarm/openai/executor.rb +0 -256
  16. data/lib/claude_swarm/openai/responses.rb +0 -319
  17. data/lib/claude_swarm/orchestrator.rb +0 -878
  18. data/lib/claude_swarm/process_tracker.rb +0 -78
  19. data/lib/claude_swarm/session_cost_calculator.rb +0 -209
  20. data/lib/claude_swarm/session_path.rb +0 -42
  21. data/lib/claude_swarm/settings_generator.rb +0 -77
  22. data/lib/claude_swarm/system_utils.rb +0 -46
  23. data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
  24. data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
  25. data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
  26. data/lib/claude_swarm/tools/task_tool.rb +0 -63
  27. data/lib/claude_swarm/version.rb +0 -5
  28. data/lib/claude_swarm/worktree_manager.rb +0 -475
  29. data/lib/claude_swarm/yaml_loader.rb +0 -22
  30. data/lib/claude_swarm.rb +0 -67
  31. data/lib/swarm_cli/cli.rb +0 -201
  32. data/lib/swarm_cli/command_registry.rb +0 -61
  33. data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
  34. data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
  35. data/lib/swarm_cli/commands/migrate.rb +0 -55
  36. data/lib/swarm_cli/commands/run.rb +0 -173
  37. data/lib/swarm_cli/config_loader.rb +0 -98
  38. data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
  39. data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
  40. data/lib/swarm_cli/interactive_repl.rb +0 -924
  41. data/lib/swarm_cli/mcp_serve_options.rb +0 -44
  42. data/lib/swarm_cli/mcp_tools_options.rb +0 -59
  43. data/lib/swarm_cli/migrate_options.rb +0 -54
  44. data/lib/swarm_cli/migrator.rb +0 -132
  45. data/lib/swarm_cli/options.rb +0 -151
  46. data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
  47. data/lib/swarm_cli/ui/components/content_block.rb +0 -120
  48. data/lib/swarm_cli/ui/components/divider.rb +0 -57
  49. data/lib/swarm_cli/ui/components/panel.rb +0 -62
  50. data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
  51. data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
  52. data/lib/swarm_cli/ui/formatters/number.rb +0 -58
  53. data/lib/swarm_cli/ui/formatters/text.rb +0 -77
  54. data/lib/swarm_cli/ui/formatters/time.rb +0 -73
  55. data/lib/swarm_cli/ui/icons.rb +0 -36
  56. data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
  57. data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
  58. data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
  59. data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
  60. data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
  61. data/lib/swarm_cli/version.rb +0 -5
  62. data/lib/swarm_cli.rb +0 -46
  63. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
  64. data/lib/swarm_sdk/agent/builder.rb +0 -552
  65. data/lib/swarm_sdk/agent/chat.rb +0 -774
  66. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
  67. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  68. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  69. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
  70. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
  71. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  72. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  73. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
  74. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  75. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
  76. data/lib/swarm_sdk/agent/context.rb +0 -116
  77. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  78. data/lib/swarm_sdk/agent/definition.rb +0 -477
  79. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
  80. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  81. data/lib/swarm_sdk/builders/base_builder.rb +0 -409
  82. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  83. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  84. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  85. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  86. data/lib/swarm_sdk/configuration/parser.rb +0 -353
  87. data/lib/swarm_sdk/configuration/translator.rb +0 -255
  88. data/lib/swarm_sdk/configuration.rb +0 -135
  89. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  90. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
  91. data/lib/swarm_sdk/context_compactor.rb +0 -335
  92. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  93. data/lib/swarm_sdk/context_management/context.rb +0 -328
  94. data/lib/swarm_sdk/defaults.rb +0 -196
  95. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  96. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  97. data/lib/swarm_sdk/hooks/context.rb +0 -197
  98. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  99. data/lib/swarm_sdk/hooks/error.rb +0 -29
  100. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  101. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  102. data/lib/swarm_sdk/hooks/result.rb +0 -150
  103. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
  104. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  105. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  106. data/lib/swarm_sdk/log_collector.rb +0 -227
  107. data/lib/swarm_sdk/log_stream.rb +0 -127
  108. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  109. data/lib/swarm_sdk/model_aliases.json +0 -8
  110. data/lib/swarm_sdk/models.json +0 -1
  111. data/lib/swarm_sdk/models.rb +0 -120
  112. data/lib/swarm_sdk/node_context.rb +0 -245
  113. data/lib/swarm_sdk/observer/builder.rb +0 -81
  114. data/lib/swarm_sdk/observer/config.rb +0 -45
  115. data/lib/swarm_sdk/observer/manager.rb +0 -236
  116. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  117. data/lib/swarm_sdk/permissions/config.rb +0 -239
  118. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  119. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  120. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  121. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  122. data/lib/swarm_sdk/plugin.rb +0 -309
  123. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  124. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  125. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  126. data/lib/swarm_sdk/restore_result.rb +0 -65
  127. data/lib/swarm_sdk/result.rb +0 -123
  128. data/lib/swarm_sdk/snapshot.rb +0 -156
  129. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  130. data/lib/swarm_sdk/state_restorer.rb +0 -476
  131. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  132. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
  133. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
  134. data/lib/swarm_sdk/swarm/builder.rb +0 -249
  135. data/lib/swarm_sdk/swarm/executor.rb +0 -213
  136. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
  137. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
  138. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
  139. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  140. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
  141. data/lib/swarm_sdk/swarm.rb +0 -717
  142. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  143. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  144. data/lib/swarm_sdk/tools/bash.rb +0 -282
  145. data/lib/swarm_sdk/tools/clock.rb +0 -44
  146. data/lib/swarm_sdk/tools/delegate.rb +0 -267
  147. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  148. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  149. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  150. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  151. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  152. data/lib/swarm_sdk/tools/edit.rb +0 -145
  153. data/lib/swarm_sdk/tools/glob.rb +0 -166
  154. data/lib/swarm_sdk/tools/grep.rb +0 -235
  155. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  156. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -163
  157. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  158. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  159. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  160. data/lib/swarm_sdk/tools/read.rb +0 -261
  161. data/lib/swarm_sdk/tools/registry.rb +0 -205
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  165. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  166. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -272
  167. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  168. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  169. data/lib/swarm_sdk/tools/think.rb +0 -98
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -235
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/utils.rb +0 -68
  174. data/lib/swarm_sdk/validation_result.rb +0 -33
  175. data/lib/swarm_sdk/version.rb +0 -5
  176. data/lib/swarm_sdk/workflow/agent_config.rb +0 -79
  177. data/lib/swarm_sdk/workflow/builder.rb +0 -143
  178. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  179. data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
  180. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
  181. data/lib/swarm_sdk/workflow.rb +0 -554
  182. data/lib/swarm_sdk.rb +0 -524
@@ -1,256 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "openai"
4
- require "faraday/net_http_persistent"
5
- require "faraday/retry"
6
-
7
- module ClaudeSwarm
8
- module OpenAI
9
- class Executor < BaseExecutor
10
- # Static configuration for Faraday retry middleware
11
- FARADAY_RETRY_CONFIG = {
12
- max: 3, # Maximum number of retries
13
- interval: 0.5, # Initial delay between retries (in seconds)
14
- interval_randomness: 0.5, # Randomness factor for retry intervals
15
- backoff_factor: 2, # Exponential backoff factor
16
- exceptions: [
17
- Faraday::TimeoutError,
18
- Faraday::ConnectionFailed,
19
- Faraday::ServerError, # Retry on 5xx errors
20
- ].freeze,
21
- retry_statuses: [429, 500, 502, 503, 504].freeze, # HTTP status codes to retry
22
- }.freeze
23
-
24
- # Static configuration for OpenAI client
25
- OPENAI_CLIENT_CONFIG = {
26
- log_errors: true,
27
- request_timeout: 1800, # 30 minutes
28
- }.freeze
29
-
30
- def initialize(working_directory: Dir.pwd, model: nil, mcp_config: nil, vibe: false,
31
- instance_name: nil, instance_id: nil, calling_instance: nil, calling_instance_id: nil,
32
- claude_session_id: nil, additional_directories: [], debug: false,
33
- temperature: nil, api_version: "chat_completion", openai_token_env: "OPENAI_API_KEY",
34
- base_url: nil, reasoning_effort: nil, zdr: false)
35
- # Call parent initializer for common attributes
36
- super(
37
- working_directory: working_directory,
38
- model: model,
39
- mcp_config: mcp_config,
40
- vibe: vibe,
41
- instance_name: instance_name,
42
- instance_id: instance_id,
43
- calling_instance: calling_instance,
44
- calling_instance_id: calling_instance_id,
45
- claude_session_id: claude_session_id,
46
- additional_directories: additional_directories,
47
- debug: debug
48
- )
49
-
50
- # OpenAI-specific attributes
51
- @temperature = temperature
52
- @api_version = api_version
53
- @base_url = base_url
54
- @reasoning_effort = reasoning_effort
55
- @zdr = zdr
56
-
57
- # Conversation state for maintaining context
58
- @conversation_messages = []
59
- @previous_response_id = nil
60
-
61
- # Setup OpenAI client
62
- setup_openai_client(openai_token_env)
63
-
64
- # Setup MCP client for tools
65
- setup_mcp_client
66
-
67
- # Create API handler based on api_version
68
- @api_handler = create_api_handler
69
- end
70
-
71
- def execute(prompt, options = {})
72
- # Log the request
73
- log_request(prompt)
74
-
75
- # Start timing
76
- start_time = Time.now
77
-
78
- # Execute using the appropriate handler
79
- result = @api_handler.execute(prompt, options)
80
-
81
- # Calculate duration
82
- duration_ms = ((Time.now - start_time) * 1000).round
83
-
84
- # Build and return response
85
- build_response(result, duration_ms)
86
- rescue StandardError => e
87
- logger.error { "Unexpected error for #{@instance_name}: #{e.class} - #{e.message}" }
88
- logger.error { "Backtrace: #{e.backtrace.join("\n")}" }
89
- raise
90
- end
91
-
92
- def reset_session
93
- super
94
- @api_handler&.reset_session
95
- end
96
-
97
- # Session JSON logger for the API handlers
98
- def session_json_logger
99
- self
100
- end
101
-
102
- def log(event)
103
- append_to_session_json(event)
104
- end
105
-
106
- private
107
-
108
- def setup_openai_client(token_env)
109
- openai_client_config = build_openai_client_config(token_env)
110
-
111
- @openai_client = ::OpenAI::Client.new(openai_client_config) do |faraday|
112
- # Use persistent HTTP connections for better performance
113
- faraday.adapter(:net_http_persistent)
114
-
115
- # Add retry middleware with custom configuration
116
- faraday.request(:retry, **build_faraday_retry_config)
117
- end
118
- rescue KeyError
119
- raise ExecutionError, "OpenAI API key not found in environment variable: #{token_env}"
120
- end
121
-
122
- def setup_mcp_client
123
- return unless @mcp_config && File.exist?(@mcp_config)
124
-
125
- # Read MCP config to find MCP servers
126
- mcp_data = JsonHandler.parse_file!(@mcp_config)
127
-
128
- # Build MCP configurations from servers
129
- mcp_configs = build_mcp_configs(mcp_data["mcpServers"])
130
- return if mcp_configs.empty?
131
-
132
- # Create MCP client with unbundled environment to avoid bundler conflicts
133
- # This ensures MCP servers run in a clean environment without inheriting
134
- # Claude Swarm's BUNDLE_* environment variables
135
- Bundler.with_unbundled_env do
136
- @mcp_client = MCPClient.create_client(
137
- mcp_server_configs: mcp_configs,
138
- logger: @logger,
139
- )
140
-
141
- # List available tools from all MCP servers
142
- load_mcp_tools(mcp_configs)
143
- end
144
- rescue StandardError => e
145
- logger.error { "Failed to setup MCP client: #{e.message}" }
146
- @mcp_client = nil
147
- @available_tools = []
148
- end
149
-
150
- def calculate_cost(_result)
151
- # Simplified cost calculation
152
- # In reality, we'd need to track token usage
153
- "$0.00"
154
- end
155
-
156
- def create_api_handler
157
- handler_params = {
158
- openai_client: @openai_client,
159
- mcp_client: @mcp_client,
160
- available_tools: @available_tools,
161
- executor: self,
162
- instance_name: @instance_name,
163
- model: @model,
164
- temperature: @temperature,
165
- reasoning_effort: @reasoning_effort,
166
- zdr: @zdr,
167
- }
168
-
169
- if @api_version == "responses"
170
- OpenAI::Responses.new(**handler_params)
171
- else
172
- OpenAI::ChatCompletion.new(**handler_params)
173
- end
174
- end
175
-
176
- def log_streaming_content(content)
177
- # Log streaming content similar to ClaudeCodeExecutor
178
- logger.debug { "#{instance_info} streaming: #{content}" }
179
- end
180
-
181
- def build_faraday_retry_config
182
- FARADAY_RETRY_CONFIG.merge(
183
- retry_block: method(:handle_retry_logging),
184
- )
185
- end
186
-
187
- def handle_retry_logging(env:, options:, retry_count:, exception:, will_retry:)
188
- retry_delay = options.interval * (options.backoff_factor**(retry_count - 1))
189
- error_info = exception&.message || "HTTP #{env.status}"
190
-
191
- message = if will_retry
192
- "Request failed (attempt #{retry_count}/#{options.max}): #{error_info}. Retrying in #{retry_delay} seconds..."
193
- else
194
- "Request failed after #{retry_count} attempts: #{error_info}. Giving up."
195
- end
196
-
197
- @logger.warn(message)
198
- end
199
-
200
- def build_openai_client_config(token_env)
201
- OPENAI_CLIENT_CONFIG.merge(access_token: ENV.fetch(token_env)).tap do |config|
202
- config[:uri_base] = @base_url if @base_url
203
- end
204
- end
205
-
206
- def build_stdio_config(name, server_config)
207
- # Combine command and args into a single array
208
- command_array = [server_config["command"]]
209
- command_array.concat(server_config["args"] || [])
210
-
211
- MCPClient.stdio_config(
212
- command: command_array,
213
- name: name,
214
- ).tap do |config|
215
- config[:read_timeout] = 1800
216
- end
217
- end
218
-
219
- def build_mcp_configs(mcp_servers)
220
- return [] if mcp_servers.nil? || mcp_servers.empty?
221
-
222
- mcp_servers.filter_map do |name, server_config|
223
- case server_config["type"]
224
- when "stdio"
225
- build_stdio_config(name, server_config)
226
- when "sse"
227
- logger.warn { "SSE MCP servers not yet supported for OpenAI instances: #{name}" }
228
- # TODO: Add SSE support when available in ruby-mcp-client
229
- nil
230
- end
231
- end
232
- end
233
-
234
- def load_mcp_tools(mcp_configs)
235
- @available_tools = @mcp_client.list_tools
236
- logger.info { "Loaded #{@available_tools.size} tools from #{mcp_configs.size} MCP server(s)" }
237
- rescue StandardError => e
238
- logger.error { "Failed to load MCP tools: #{e.message}" }
239
- @available_tools = []
240
- end
241
-
242
- def build_response(result, duration_ms)
243
- {
244
- "type" => "result",
245
- "result" => result,
246
- "duration_ms" => duration_ms,
247
- "total_cost" => calculate_cost(result),
248
- "session_id" => @session_id,
249
- }.tap do |response|
250
- log_response(response)
251
- @last_response = response
252
- end
253
- end
254
- end
255
- end
256
- end
@@ -1,319 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ClaudeSwarm
4
- module OpenAI
5
- class Responses
6
- MAX_TURNS_WITH_TOOLS = 100_000 # virtually infinite
7
-
8
- def initialize(openai_client:, mcp_client:, available_tools:, executor:, instance_name:, model:, temperature: nil, reasoning_effort: nil, zdr: false)
9
- @openai_client = openai_client
10
- @mcp_client = mcp_client
11
- @available_tools = available_tools
12
- @executor = executor
13
- @instance_name = instance_name
14
- @model = model
15
- @temperature = temperature
16
- @reasoning_effort = reasoning_effort
17
- @zdr = zdr
18
- @system_prompt = nil
19
- end
20
-
21
- def execute(prompt, options = {})
22
- # Store system prompt for first call
23
- @system_prompt = options[:system_prompt] if options[:system_prompt]
24
-
25
- # Start with initial prompt
26
- initial_input = prompt
27
-
28
- # Process with recursive tool handling - start with empty conversation
29
- process_responses_api(initial_input, [], nil)
30
- end
31
-
32
- def reset_session
33
- @system_prompt = nil
34
- end
35
-
36
- private
37
-
38
- def process_responses_api(input, conversation_array, previous_response_id, depth = 0)
39
- # Prevent infinite recursion
40
- if depth > MAX_TURNS_WITH_TOOLS
41
- @executor.logger.error { "Maximum recursion depth reached in tool execution" }
42
- return "Error: Maximum tool call depth exceeded"
43
- end
44
-
45
- # Build parameters
46
- parameters = {
47
- model: @model,
48
- }
49
-
50
- parameters[:temperature] = @temperature if @temperature
51
- parameters[:reasoning] = { effort: @reasoning_effort } if @reasoning_effort
52
-
53
- # On first call, use string input (can include system prompt)
54
- # On subsequent calls with function results, use array input
55
- if conversation_array.empty?
56
- # Initial call - string input
57
- parameters[:input] = if depth.zero? && @system_prompt
58
- "#{@system_prompt}\n\n#{input}"
59
- else
60
- input
61
- end
62
- conversation_array << { role: "user", content: parameters[:input] }
63
- else
64
- # Follow-up call with conversation array (function calls + outputs)
65
- parameters[:input] = conversation_array
66
-
67
- # Log conversation array to debug duplicates
68
- @executor.logger.info { "Conversation array size: #{conversation_array.size}" }
69
- conversation_ids = conversation_array.map do |item|
70
- item["call_id"] || item["id"] || "no-id-#{item["type"]}"
71
- end.compact
72
- @executor.logger.info { "Conversation item IDs: #{conversation_ids.inspect}" }
73
- end
74
-
75
- # Add previous response ID for conversation continuity (unless zdr is enabled)
76
- parameters[:previous_response_id] = @zdr ? nil : previous_response_id
77
-
78
- # Add tools if available
79
- if @available_tools&.any?
80
- # Convert tools to responses API format
81
- parameters[:tools] = @available_tools.map do |tool|
82
- {
83
- "type" => "function",
84
- "name" => tool.name,
85
- "description" => tool.description,
86
- "parameters" => tool.schema || {},
87
- }
88
- end
89
- @executor.logger.info { "Available tools for responses API: #{parameters[:tools].map { |t| t["name"] }.join(", ")}" }
90
- end
91
-
92
- # Log the request parameters
93
- @executor.logger.info { "Responses API Request (depth=#{depth}): #{JsonHandler.pretty_generate!(parameters)}" }
94
-
95
- # Append to session JSON
96
- append_to_session_json({
97
- type: "openai_request",
98
- api: "responses",
99
- depth: depth,
100
- parameters: parameters,
101
- })
102
-
103
- # Make the API call without streaming
104
- begin
105
- response = @openai_client.responses.create(parameters: parameters)
106
- rescue StandardError => e
107
- @executor.logger.error { "Responses API error: #{e.class} - #{e.message}" }
108
- @executor.logger.error { "Request parameters: #{JsonHandler.pretty_generate!(parameters)}" }
109
-
110
- # Try to extract and log the response body for better debugging
111
- if e.respond_to?(:response) && e.response
112
- begin
113
- error_body = e.response[:body]
114
- @executor.logger.error { "Error response body: #{error_body}" }
115
- rescue StandardError => parse_error
116
- @executor.logger.error { "Could not parse error response: #{parse_error.message}" }
117
- end
118
- end
119
-
120
- # Log error to session JSON
121
- append_to_session_json({
122
- type: "openai_error",
123
- api: "responses",
124
- error: {
125
- class: e.class.to_s,
126
- message: e.message,
127
- response_body: e.respond_to?(:response) && e.response ? e.response[:body] : nil,
128
- backtrace: e.backtrace.first(5),
129
- },
130
- })
131
-
132
- return "Error calling OpenAI responses API: #{e.message}"
133
- end
134
-
135
- # Log the full response
136
- @executor.logger.info { "Responses API Full Response (depth=#{depth}): #{JsonHandler.pretty_generate!(response)}" }
137
-
138
- # Append to session JSON
139
- append_to_session_json({
140
- type: "openai_response",
141
- api: "responses",
142
- depth: depth,
143
- response: response,
144
- })
145
-
146
- # Extract response details
147
- response_id = response["id"]
148
-
149
- # Handle response based on output structure
150
- output = response["output"]
151
- if output.nil?
152
- @executor.logger.error { "No output in response" }
153
- return "Error: No output in OpenAI response"
154
- end
155
-
156
- # Check if output is an array (as per documentation)
157
- if output.is_a?(Array) && output.any?
158
- new_conversation = conversation_array.dup
159
- new_conversation.concat(output)
160
- # Check if there are function calls
161
- function_calls = output.select { |item| item["type"] == "function_call" }
162
- if function_calls.any?
163
- append_new_outputs(function_calls, new_conversation)
164
- process_responses_api(nil, new_conversation, response_id, depth + 1)
165
- else
166
- extract_text_response(output)
167
- end
168
- else
169
- @executor.logger.error { "Unexpected output format: #{output.inspect}" }
170
- "Error: Unexpected response format"
171
- end
172
- end
173
-
174
- def extract_text_response(output)
175
- text_output = output.find { |item| item["content"] }
176
- return "" unless text_output && text_output["content"].is_a?(Array)
177
-
178
- text_content = text_output["content"].find { |item| item["text"] }
179
- text_content ? text_content["text"] : ""
180
- end
181
-
182
- def build_conversation_with_outputs(function_calls)
183
- # Log tool calls
184
- @executor.logger.info { "Responses API - Handling #{function_calls.size} function calls" }
185
-
186
- # Log IDs to check for duplicates
187
- call_ids = function_calls.map { |fc| fc["call_id"] || fc["id"] }
188
- @executor.logger.info { "Function call IDs: #{call_ids.inspect}" }
189
- @executor.logger.warn { "WARNING: Duplicate function call IDs detected!" } if call_ids.size != call_ids.uniq.size
190
-
191
- # Append to session JSON
192
- append_to_session_json({
193
- type: "tool_calls",
194
- api: "responses",
195
- tool_calls: function_calls,
196
- })
197
-
198
- # Build conversation array with function outputs only
199
- # The API already knows about the function calls from the previous response
200
- conversation = []
201
-
202
- # Then execute tools and add outputs
203
- function_calls.each do |function_call|
204
- tool_name = function_call["name"]
205
- tool_args_str = function_call["arguments"]
206
- # Use the call_id field for matching with function outputs
207
- call_id = function_call["call_id"]
208
-
209
- # Log both IDs to debug
210
- @executor.logger.info { "Function call has id=#{function_call["id"]}, call_id=#{function_call["call_id"]}" }
211
-
212
- begin
213
- # Parse arguments
214
- tool_args = JsonHandler.parse!(tool_args_str)
215
-
216
- # Log tool execution
217
- @executor.logger.info { "Responses API - Executing tool: #{tool_name} with args: #{JsonHandler.pretty_generate!(tool_args)}" }
218
-
219
- # Execute tool via MCP
220
- result = @mcp_client.call_tool(tool_name, tool_args)
221
-
222
- # Log result
223
- @executor.logger.info { "Responses API - Tool result for #{tool_name}: #{result}" }
224
-
225
- # Append to session JSON
226
- append_to_session_json({
227
- type: "tool_execution",
228
- api: "responses",
229
- tool_name: tool_name,
230
- arguments: tool_args,
231
- result: result.to_s,
232
- })
233
-
234
- # Add function output to conversation
235
- conversation << {
236
- type: "function_call_output",
237
- call_id: call_id,
238
- output: result.to_json, # Must be JSON string
239
- }
240
- rescue StandardError => e
241
- @executor.logger.error { "Responses API - Tool execution failed for #{tool_name}: #{e.message}" }
242
- @executor.logger.error { e.backtrace.join("\n") }
243
-
244
- # Append error to session JSON
245
- append_to_session_json({
246
- type: "tool_error",
247
- api: "responses",
248
- tool_name: tool_name,
249
- arguments: tool_args_str,
250
- error: {
251
- class: e.class.to_s,
252
- message: e.message,
253
- backtrace: e.backtrace.first(5),
254
- },
255
- })
256
-
257
- # Add error output to conversation
258
- conversation << {
259
- type: "function_call_output",
260
- call_id: call_id,
261
- output: { error: e.message }.to_json,
262
- }
263
- end
264
- end
265
-
266
- @executor.logger.info { "Responses API - Built conversation with #{conversation.size} function outputs" }
267
- @executor.logger.debug { "Final conversation structure: #{JsonHandler.pretty_generate!(conversation)}" }
268
- conversation
269
- end
270
-
271
- def append_new_outputs(function_calls, conversation)
272
- # Only add the new function outputs
273
- # Don't add function calls - the API already knows about them
274
-
275
- function_calls.each do |fc|
276
- # Execute and add output only
277
- tool_name = fc["name"]
278
- tool_args_str = fc["arguments"]
279
- call_id = fc["call_id"]
280
-
281
- begin
282
- # Parse arguments
283
- tool_args = JsonHandler.parse!(tool_args_str)
284
-
285
- # Log tool execution
286
- @executor.logger.info { "Responses API - Executing tool: #{tool_name} with args: #{JsonHandler.pretty_generate!(tool_args)}" }
287
-
288
- # Execute tool via MCP
289
- result = @mcp_client.call_tool(tool_name, tool_args)
290
-
291
- # Log result
292
- @executor.logger.info { "Responses API - Tool result for #{tool_name}: #{result}" }
293
-
294
- # Add function output to conversation
295
- conversation << {
296
- type: "function_call_output",
297
- call_id: call_id,
298
- output: result.to_json, # Must be JSON string
299
- }
300
- rescue StandardError => e
301
- @executor.logger.error { "Responses API - Tool execution failed for #{tool_name}: #{e.message}" }
302
-
303
- # Add error output to conversation
304
- conversation << {
305
- type: "function_call_output",
306
- call_id: call_id,
307
- output: { error: e.message }.to_json,
308
- }
309
- end
310
- end
311
- end
312
-
313
- def append_to_session_json(event)
314
- # Delegate to the executor's log method
315
- @executor.log(event) if @executor.respond_to?(:log)
316
- end
317
- end
318
- end
319
- end