swarm_memory 2.1.4 → 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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_memory/version.rb +1 -1
  3. data/lib/swarm_memory.rb +7 -2
  4. metadata +6 -185
  5. data/lib/claude_swarm/base_executor.rb +0 -133
  6. data/lib/claude_swarm/claude_code_executor.rb +0 -349
  7. data/lib/claude_swarm/claude_mcp_server.rb +0 -78
  8. data/lib/claude_swarm/cli.rb +0 -697
  9. data/lib/claude_swarm/commands/ps.rb +0 -215
  10. data/lib/claude_swarm/commands/show.rb +0 -139
  11. data/lib/claude_swarm/configuration.rb +0 -373
  12. data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
  13. data/lib/claude_swarm/json_handler.rb +0 -91
  14. data/lib/claude_swarm/mcp_generator.rb +0 -243
  15. data/lib/claude_swarm/openai/chat_completion.rb +0 -256
  16. data/lib/claude_swarm/openai/executor.rb +0 -256
  17. data/lib/claude_swarm/openai/responses.rb +0 -319
  18. data/lib/claude_swarm/orchestrator.rb +0 -878
  19. data/lib/claude_swarm/process_tracker.rb +0 -78
  20. data/lib/claude_swarm/session_cost_calculator.rb +0 -209
  21. data/lib/claude_swarm/session_path.rb +0 -42
  22. data/lib/claude_swarm/settings_generator.rb +0 -77
  23. data/lib/claude_swarm/system_utils.rb +0 -46
  24. data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
  25. data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
  26. data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
  27. data/lib/claude_swarm/tools/task_tool.rb +0 -63
  28. data/lib/claude_swarm/version.rb +0 -5
  29. data/lib/claude_swarm/worktree_manager.rb +0 -475
  30. data/lib/claude_swarm/yaml_loader.rb +0 -22
  31. data/lib/claude_swarm.rb +0 -67
  32. data/lib/swarm_cli/cli.rb +0 -201
  33. data/lib/swarm_cli/command_registry.rb +0 -61
  34. data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
  35. data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
  36. data/lib/swarm_cli/commands/migrate.rb +0 -55
  37. data/lib/swarm_cli/commands/run.rb +0 -173
  38. data/lib/swarm_cli/config_loader.rb +0 -98
  39. data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
  40. data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
  41. data/lib/swarm_cli/interactive_repl.rb +0 -924
  42. data/lib/swarm_cli/mcp_serve_options.rb +0 -44
  43. data/lib/swarm_cli/mcp_tools_options.rb +0 -59
  44. data/lib/swarm_cli/migrate_options.rb +0 -54
  45. data/lib/swarm_cli/migrator.rb +0 -132
  46. data/lib/swarm_cli/options.rb +0 -151
  47. data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
  48. data/lib/swarm_cli/ui/components/content_block.rb +0 -120
  49. data/lib/swarm_cli/ui/components/divider.rb +0 -57
  50. data/lib/swarm_cli/ui/components/panel.rb +0 -62
  51. data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
  52. data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
  53. data/lib/swarm_cli/ui/formatters/number.rb +0 -58
  54. data/lib/swarm_cli/ui/formatters/text.rb +0 -77
  55. data/lib/swarm_cli/ui/formatters/time.rb +0 -73
  56. data/lib/swarm_cli/ui/icons.rb +0 -36
  57. data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
  58. data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
  59. data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
  60. data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
  61. data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
  62. data/lib/swarm_cli/version.rb +0 -5
  63. data/lib/swarm_cli.rb +0 -46
  64. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
  65. data/lib/swarm_sdk/agent/builder.rb +0 -552
  66. data/lib/swarm_sdk/agent/chat.rb +0 -774
  67. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
  68. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  69. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  70. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
  71. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
  72. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  73. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  74. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
  75. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  76. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
  77. data/lib/swarm_sdk/agent/context.rb +0 -116
  78. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  79. data/lib/swarm_sdk/agent/definition.rb +0 -477
  80. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
  81. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  82. data/lib/swarm_sdk/builders/base_builder.rb +0 -409
  83. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  84. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  85. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  86. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  87. data/lib/swarm_sdk/configuration/parser.rb +0 -353
  88. data/lib/swarm_sdk/configuration/translator.rb +0 -255
  89. data/lib/swarm_sdk/configuration.rb +0 -135
  90. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  91. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
  92. data/lib/swarm_sdk/context_compactor.rb +0 -335
  93. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  94. data/lib/swarm_sdk/context_management/context.rb +0 -328
  95. data/lib/swarm_sdk/defaults.rb +0 -196
  96. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  97. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  98. data/lib/swarm_sdk/hooks/context.rb +0 -197
  99. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  100. data/lib/swarm_sdk/hooks/error.rb +0 -29
  101. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  102. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  103. data/lib/swarm_sdk/hooks/result.rb +0 -150
  104. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
  105. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  106. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  107. data/lib/swarm_sdk/log_collector.rb +0 -227
  108. data/lib/swarm_sdk/log_stream.rb +0 -127
  109. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  110. data/lib/swarm_sdk/model_aliases.json +0 -8
  111. data/lib/swarm_sdk/models.json +0 -1
  112. data/lib/swarm_sdk/models.rb +0 -120
  113. data/lib/swarm_sdk/node_context.rb +0 -245
  114. data/lib/swarm_sdk/observer/builder.rb +0 -81
  115. data/lib/swarm_sdk/observer/config.rb +0 -45
  116. data/lib/swarm_sdk/observer/manager.rb +0 -236
  117. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  118. data/lib/swarm_sdk/permissions/config.rb +0 -239
  119. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  120. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  121. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  122. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  123. data/lib/swarm_sdk/plugin.rb +0 -309
  124. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  125. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  126. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  127. data/lib/swarm_sdk/restore_result.rb +0 -65
  128. data/lib/swarm_sdk/result.rb +0 -123
  129. data/lib/swarm_sdk/snapshot.rb +0 -156
  130. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  131. data/lib/swarm_sdk/state_restorer.rb +0 -476
  132. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  133. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
  134. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
  135. data/lib/swarm_sdk/swarm/builder.rb +0 -249
  136. data/lib/swarm_sdk/swarm/executor.rb +0 -213
  137. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
  138. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
  139. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
  140. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  141. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
  142. data/lib/swarm_sdk/swarm.rb +0 -717
  143. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  144. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  145. data/lib/swarm_sdk/tools/bash.rb +0 -282
  146. data/lib/swarm_sdk/tools/clock.rb +0 -44
  147. data/lib/swarm_sdk/tools/delegate.rb +0 -267
  148. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  149. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  150. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  151. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  152. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  153. data/lib/swarm_sdk/tools/edit.rb +0 -145
  154. data/lib/swarm_sdk/tools/glob.rb +0 -166
  155. data/lib/swarm_sdk/tools/grep.rb +0 -235
  156. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  157. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -163
  158. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  159. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  160. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  161. data/lib/swarm_sdk/tools/read.rb +0 -261
  162. data/lib/swarm_sdk/tools/registry.rb +0 -205
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  165. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  166. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  167. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -272
  168. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  169. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  170. data/lib/swarm_sdk/tools/think.rb +0 -98
  171. data/lib/swarm_sdk/tools/todo_write.rb +0 -235
  172. data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
  173. data/lib/swarm_sdk/tools/write.rb +0 -112
  174. data/lib/swarm_sdk/utils.rb +0 -68
  175. data/lib/swarm_sdk/validation_result.rb +0 -33
  176. data/lib/swarm_sdk/version.rb +0 -5
  177. data/lib/swarm_sdk/workflow/agent_config.rb +0 -79
  178. data/lib/swarm_sdk/workflow/builder.rb +0 -143
  179. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  180. data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
  181. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
  182. data/lib/swarm_sdk/workflow.rb +0 -554
  183. data/lib/swarm_sdk.rb +0 -524
  184. /data/lib/swarm_memory/{errors.rb → error.rb} +0 -0
@@ -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