swarm_sdk 2.7.14 → 3.0.0.alpha1

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 (181) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +16 -0
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +4 -1
  4. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  5. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  6. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  7. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  8. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  9. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  10. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  11. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  12. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  13. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  14. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  15. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  16. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  17. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  18. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  19. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  20. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  24. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  25. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  26. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  27. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  28. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  29. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  30. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  31. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  32. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  33. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  34. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  35. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  36. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  37. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  38. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  39. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  40. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  41. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  42. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  43. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  44. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  45. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  46. data/lib/swarm_sdk/v3/tools/read.rb +181 -0
  47. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  48. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  49. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  50. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  51. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  52. data/lib/swarm_sdk/v3.rb +145 -0
  53. metadata +83 -148
  54. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  55. data/lib/swarm_sdk/agent/builder.rb +0 -705
  56. data/lib/swarm_sdk/agent/chat.rb +0 -1438
  57. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  58. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  59. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  60. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  61. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  62. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  63. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  64. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  65. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  66. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  67. data/lib/swarm_sdk/agent/context.rb +0 -115
  68. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  69. data/lib/swarm_sdk/agent/definition.rb +0 -588
  70. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  71. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -173
  72. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  73. data/lib/swarm_sdk/agent_registry.rb +0 -146
  74. data/lib/swarm_sdk/builders/base_builder.rb +0 -558
  75. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  76. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -42
  77. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  78. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  79. data/lib/swarm_sdk/config.rb +0 -368
  80. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  81. data/lib/swarm_sdk/configuration/translator.rb +0 -285
  82. data/lib/swarm_sdk/configuration.rb +0 -165
  83. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  84. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  85. data/lib/swarm_sdk/context_compactor.rb +0 -335
  86. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  87. data/lib/swarm_sdk/context_management/context.rb +0 -328
  88. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  89. data/lib/swarm_sdk/defaults.rb +0 -251
  90. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  91. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  92. data/lib/swarm_sdk/hooks/context.rb +0 -197
  93. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  94. data/lib/swarm_sdk/hooks/error.rb +0 -29
  95. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  96. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  97. data/lib/swarm_sdk/hooks/result.rb +0 -150
  98. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  99. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  100. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  101. data/lib/swarm_sdk/log_collector.rb +0 -227
  102. data/lib/swarm_sdk/log_stream.rb +0 -127
  103. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  104. data/lib/swarm_sdk/model_aliases.json +0 -8
  105. data/lib/swarm_sdk/models.json +0 -44002
  106. data/lib/swarm_sdk/models.rb +0 -161
  107. data/lib/swarm_sdk/node_context.rb +0 -245
  108. data/lib/swarm_sdk/observer/builder.rb +0 -81
  109. data/lib/swarm_sdk/observer/config.rb +0 -45
  110. data/lib/swarm_sdk/observer/manager.rb +0 -248
  111. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  112. data/lib/swarm_sdk/permissions/config.rb +0 -239
  113. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  114. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  115. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  116. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  117. data/lib/swarm_sdk/plugin.rb +0 -309
  118. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  119. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  120. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -119
  121. data/lib/swarm_sdk/restore_result.rb +0 -65
  122. data/lib/swarm_sdk/result.rb +0 -241
  123. data/lib/swarm_sdk/snapshot.rb +0 -156
  124. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  125. data/lib/swarm_sdk/state_restorer.rb +0 -476
  126. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  127. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  128. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -204
  129. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  130. data/lib/swarm_sdk/swarm/executor.rb +0 -446
  131. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -162
  132. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  133. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -361
  134. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -290
  135. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  136. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  137. data/lib/swarm_sdk/swarm.rb +0 -973
  138. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  139. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  140. data/lib/swarm_sdk/tools/base.rb +0 -63
  141. data/lib/swarm_sdk/tools/bash.rb +0 -280
  142. data/lib/swarm_sdk/tools/clock.rb +0 -46
  143. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  144. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  145. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  146. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  147. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  148. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  149. data/lib/swarm_sdk/tools/edit.rb +0 -145
  150. data/lib/swarm_sdk/tools/glob.rb +0 -166
  151. data/lib/swarm_sdk/tools/grep.rb +0 -235
  152. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  153. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  154. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  155. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  156. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  157. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  158. data/lib/swarm_sdk/tools/read.rb +0 -261
  159. data/lib/swarm_sdk/tools/registry.rb +0 -205
  160. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  161. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  163. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  164. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  165. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  166. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  167. data/lib/swarm_sdk/tools/think.rb +0 -100
  168. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  169. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  170. data/lib/swarm_sdk/tools/write.rb +0 -112
  171. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  172. data/lib/swarm_sdk/utils.rb +0 -68
  173. data/lib/swarm_sdk/validation_result.rb +0 -33
  174. data/lib/swarm_sdk/version.rb +0 -5
  175. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  176. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  177. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  178. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  179. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  180. data/lib/swarm_sdk/workflow.rb +0 -589
  181. data/lib/swarm_sdk.rb +0 -721
@@ -1,588 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Agent
5
- # Agent definition encapsulates agent configuration and builds system prompts
6
- #
7
- # This class is responsible for:
8
- # - Parsing and validating agent configuration
9
- # - Building the full system prompt (base + custom)
10
- # - Handling tool permissions
11
- # - Managing hooks (both DSL Ruby blocks and YAML shell commands)
12
- #
13
- # @example
14
- # definition = Agent::Definition.new(:backend, {
15
- # description: "Backend API developer",
16
- # model: "gpt-5",
17
- # tools: [:Read, :Write, :Bash],
18
- # system_prompt: "You build APIs"
19
- # })
20
- class Definition
21
- attr_reader :name,
22
- :description,
23
- :model,
24
- :context_window,
25
- :directory,
26
- :tools,
27
- :delegation_configs, # Full delegation config with tool names
28
- :system_prompt,
29
- :provider,
30
- :base_url,
31
- :api_version,
32
- :mcp_servers,
33
- :parameters,
34
- :headers,
35
- :request_timeout,
36
- :turn_timeout,
37
- :disable_default_tools,
38
- :coding_agent,
39
- :default_permissions,
40
- :agent_permissions,
41
- :assume_model_exists,
42
- :hooks,
43
- :plugin_configs,
44
- :shared_across_delegations,
45
- :streaming,
46
- :thinking,
47
- :disable_environment_info
48
-
49
- attr_accessor :bypass_permissions, :max_concurrent_tools
50
-
51
- def initialize(name, config = {})
52
- @name = name.to_sym
53
-
54
- # Validate name doesn't contain '@' (reserved for delegation instances)
55
- if @name.to_s.include?("@")
56
- raise ConfigurationError,
57
- "Agent names cannot contain '@' character (reserved for delegation instance naming). " \
58
- "Agent: #{@name}"
59
- end
60
-
61
- # BREAKING CHANGE: Hard error for plural form
62
- if config[:directories]
63
- raise ConfigurationError,
64
- "The 'directories' (plural) configuration is no longer supported in SwarmSDK 1.0+.\n\n" \
65
- "Change 'directories:' to 'directory:' (singular).\n\n" \
66
- "If you need access to multiple directories, use permissions:\n\n " \
67
- "directory: 'backend/'\n " \
68
- "permissions do\n " \
69
- "tool(:Read).allow_paths('../shared/**')\n " \
70
- "end"
71
- end
72
-
73
- @description = config[:description]
74
- @model = config[:model] || SwarmSDK.config.default_model
75
- @provider = config[:provider] || SwarmSDK.config.default_provider
76
- @base_url = config[:base_url]
77
- @api_version = config[:api_version]
78
- @context_window = coerce_to_integer(config[:context_window]) # Explicit context window override
79
- @parameters = config[:parameters] || {}
80
- @headers = Utils.stringify_keys(config[:headers] || {})
81
- @request_timeout = config[:request_timeout] || SwarmSDK.config.agent_request_timeout
82
- @bypass_permissions = config[:bypass_permissions] || false
83
- @max_concurrent_tools = config[:max_concurrent_tools]
84
-
85
- # Use default from config unless explicitly set (including nil to disable)
86
- @turn_timeout = if config.key?(:turn_timeout)
87
- config[:turn_timeout] # Could be a number OR nil (to disable)
88
- else
89
- SwarmSDK.config.default_turn_timeout
90
- end
91
- # Always assume model exists - SwarmSDK validates models separately using models.json
92
- # This prevents RubyLLM from trying to validate models in its registry
93
- @assume_model_exists = true
94
-
95
- # disable_default_tools can be:
96
- # - nil/not set: include all default tools (default behavior)
97
- # - true: disable ALL default tools
98
- # - Array of symbols: disable specific tools (e.g., [:Think, :TodoWrite])
99
- @disable_default_tools = config[:disable_default_tools]
100
-
101
- # coding_agent defaults to false if not specified
102
- # When true, includes the base system prompt for coding tasks
103
- # When false, uses only the custom system prompt (no base prompt)
104
- @coding_agent = config.key?(:coding_agent) ? config[:coding_agent] : false
105
-
106
- # Parse directory first so it can be used in system prompt rendering
107
- @directory = parse_directory(config[:directory])
108
-
109
- # Extract plugin configurations (generic bucket for all plugin-specific keys)
110
- # This allows plugins to store their config without SDK knowing about them
111
- @plugin_configs = extract_plugin_configs(config)
112
-
113
- # Delegation isolation mode (default: false = isolated instances per delegation)
114
- @shared_across_delegations = config[:shared_across_delegations] || false
115
-
116
- # Streaming configuration (default: true from global config)
117
- @streaming = config.fetch(:streaming, SwarmSDK.config.streaming)
118
-
119
- # Extended thinking configuration (nil = disabled)
120
- @thinking = config[:thinking]
121
-
122
- # When true, omits date/platform/OS/working directory from system prompts
123
- @disable_environment_info = config.fetch(:disable_environment_info, false)
124
-
125
- # Build system prompt after directory and memory are set
126
- @system_prompt = build_full_system_prompt(config[:system_prompt])
127
-
128
- # Parse tools with permissions support
129
- @default_permissions = config[:default_permissions] || {}
130
- @agent_permissions = config[:permissions] || {}
131
- @tools = parse_tools_with_permissions(
132
- config[:tools],
133
- @default_permissions,
134
- @agent_permissions,
135
- )
136
-
137
- # Inject default write restrictions for security
138
- @tools = inject_default_write_permissions(@tools)
139
-
140
- # Parse delegation configuration (supports both simple arrays and custom tool names)
141
- @delegation_configs = parse_delegation_config(config[:delegates_to])
142
- @mcp_servers = Array(config[:mcp_servers] || [])
143
-
144
- # Parse hooks configuration
145
- # Handles both DSL (HookDefinition objects) and YAML (raw hash) formats
146
- @hooks = parse_hooks(config[:hooks])
147
-
148
- validate!
149
- end
150
-
151
- # Get agent names that this agent delegates to (backwards compatible)
152
- #
153
- # Returns an array of agent name symbols. This maintains backwards compatibility
154
- # with existing code that expects delegates_to to be a simple array.
155
- #
156
- # @return [Array<Symbol>] Delegate agent names
157
- #
158
- # @example
159
- # agent_definition.delegates_to
160
- # # => [:frontend, :backend, :qa]
161
- def delegates_to
162
- @delegation_configs.map { |config| config[:agent] }
163
- end
164
-
165
- # Get plugin-specific configuration
166
- #
167
- # Plugins store their configuration in the generic plugin_configs hash.
168
- # This allows SDK to remain plugin-agnostic while plugins can store
169
- # arbitrary configuration.
170
- #
171
- # @param plugin_name [Symbol] Plugin name (e.g., :memory)
172
- # @return [Object, nil] Plugin configuration or nil if not present
173
- #
174
- # @example
175
- # agent_definition.plugin_config(:memory)
176
- # # => { directory: "tmp/memory", mode: :full_access }
177
- def plugin_config(plugin_name)
178
- @plugin_configs[plugin_name.to_sym] || @plugin_configs[plugin_name.to_s]
179
- end
180
-
181
- def to_h
182
- # Core SDK configuration (always serialized)
183
- base_config = {
184
- name: @name,
185
- description: @description,
186
- model: SwarmSDK::Models.resolve_alias(@model), # Resolve model aliases
187
- context_window: @context_window,
188
- directory: @directory,
189
- tools: @tools,
190
- delegates_to: @delegation_configs, # Serialize full config
191
- system_prompt: @system_prompt,
192
- provider: @provider,
193
- base_url: @base_url,
194
- api_version: @api_version,
195
- mcp_servers: @mcp_servers,
196
- parameters: @parameters,
197
- headers: @headers,
198
- request_timeout: @request_timeout,
199
- turn_timeout: @turn_timeout,
200
- bypass_permissions: @bypass_permissions,
201
- disable_default_tools: @disable_default_tools,
202
- coding_agent: @coding_agent,
203
- assume_model_exists: @assume_model_exists,
204
- max_concurrent_tools: @max_concurrent_tools,
205
- hooks: @hooks,
206
- shared_across_delegations: @shared_across_delegations,
207
- streaming: @streaming,
208
- disable_environment_info: @disable_environment_info,
209
- # Permissions are core SDK functionality (not plugin-specific)
210
- default_permissions: @default_permissions,
211
- permissions: @agent_permissions,
212
- }.compact
213
-
214
- # Allow plugins to contribute their config for serialization
215
- # This enables plugin features (memory, skills, etc.) to be preserved
216
- # when cloning agents without SwarmSDK knowing about plugin-specific fields
217
- plugin_configs = SwarmSDK::PluginRegistry.all.map do |plugin|
218
- plugin.serialize_config(agent_definition: self)
219
- end
220
-
221
- # Merge plugin configs into base config
222
- # Later plugins override earlier ones if they have conflicting keys
223
- plugin_configs.reduce(base_config) { |acc, config| acc.merge(config) }
224
- end
225
-
226
- # Validate agent configuration and return warnings (non-fatal issues)
227
- #
228
- # Unlike validate! which raises exceptions for critical errors, this method
229
- # returns an array of warning hashes for non-fatal issues like:
230
- # - Model not found in registry (informs user, suggests alternatives)
231
- # - Context tracking unavailable (useful even with assume_model_exists)
232
- #
233
- # Note: Validation ALWAYS runs, even with assume_model_exists: true or base_url set.
234
- # The purpose is to inform the user about potential issues and suggest corrections,
235
- # not to block execution.
236
- #
237
- # @return [Array<Hash>] Array of warning hashes
238
- def validate
239
- warnings = []
240
-
241
- # Always validate model (even with assume_model_exists)
242
- # Warnings inform user about typos and context tracking limitations
243
- model_warning = validate_model
244
- warnings << model_warning if model_warning
245
-
246
- # Future: could add tool validation, delegate validation, etc.
247
-
248
- warnings
249
- end
250
-
251
- private
252
-
253
- # Validate that model exists in SwarmSDK's model registry
254
- #
255
- # Uses SwarmSDK's static models.json instead of RubyLLM's dynamic registry.
256
- # This provides stable, offline model validation without network calls.
257
- #
258
- # Process:
259
- # 1. Try to find model directly in models.json
260
- # 2. If not found, try to resolve as alias and find again
261
- # 3. If still not found, return warning with suggestions
262
- #
263
- # @return [Hash, nil] Warning hash if model not found, nil otherwise
264
- def validate_model
265
- # Try direct lookup first
266
- model_data = SwarmSDK::Models.all.find { |m| (m["id"] || m[:id]) == @model }
267
-
268
- # If not found, try alias resolution
269
- unless model_data
270
- resolved_id = SwarmSDK::Models.resolve_alias(@model)
271
- # Only search again if alias was different
272
- if resolved_id != @model
273
- model_data = SwarmSDK::Models.all.find { |m| (m["id"] || m[:id]) == resolved_id }
274
- end
275
- end
276
-
277
- if model_data
278
- nil # Model exists (either directly or via alias)
279
- else
280
- # Model not found - return warning with suggestions
281
- {
282
- type: :model_not_found,
283
- agent: @name,
284
- model: @model,
285
- error_message: "Unknown model: #{@model}",
286
- suggestions: SwarmSDK::Models.suggest_similar(@model),
287
- }
288
- end
289
- rescue StandardError => e
290
- # Return warning on error
291
- {
292
- type: :model_not_found,
293
- agent: @name,
294
- model: @model,
295
- error_message: e.message,
296
- suggestions: [],
297
- }
298
- end
299
-
300
- def build_full_system_prompt(custom_prompt)
301
- # Delegate to SystemPromptBuilder for all prompt construction logic
302
- # This keeps Definition focused on data storage while extracting complex logic
303
- SystemPromptBuilder.build(
304
- custom_prompt: custom_prompt,
305
- coding_agent: @coding_agent,
306
- disable_default_tools: @disable_default_tools,
307
- directory: @directory,
308
- definition: self,
309
- disable_environment_info: @disable_environment_info,
310
- )
311
- end
312
-
313
- def parse_directory(directory_config)
314
- directory_config ||= "."
315
- File.expand_path(directory_config.to_s)
316
- end
317
-
318
- # Parse delegation configuration
319
- #
320
- # Supports multiple formats for backwards compatibility and new features:
321
- # 1. Simple array (backwards compatible): [:frontend, :backend]
322
- # 2. Hash with custom tool names: { frontend: "AskFrontend", backend: nil }
323
- # 3. Array of hashes: [{ agent: :frontend, tool_name: "AskFrontend" }]
324
- #
325
- # Returns normalized format: [{agent: :name, tool_name: "Custom" or nil}]
326
- #
327
- # @param delegation_config [nil, Array, Hash] Delegation configuration
328
- # @return [Array<Hash>] Normalized delegation config
329
- def parse_delegation_config(delegation_config)
330
- return [] if delegation_config.nil?
331
- return [] if delegation_config.respond_to?(:empty?) && delegation_config.empty?
332
-
333
- # Handle array format (could be symbols or hashes)
334
- if delegation_config.is_a?(Array)
335
- delegation_config.flat_map do |item|
336
- case item
337
- when Symbol, String
338
- # Simple format: :frontend → {agent: :frontend, tool_name: nil, preserve_context: true}
339
- [{ agent: item.to_sym, tool_name: nil, preserve_context: true }]
340
- when Hash
341
- # Could be already normalized or hash format
342
- if item.key?(:agent)
343
- # Already normalized: {agent: :frontend, tool_name: "Custom", preserve_context: false}
344
- [{
345
- agent: item[:agent].to_sym,
346
- tool_name: item[:tool_name],
347
- preserve_context: item.fetch(:preserve_context, true),
348
- }]
349
- else
350
- # Hash format in array: {frontend: "AskFrontend"}
351
- item.map { |agent, tool_name| { agent: agent.to_sym, tool_name: tool_name, preserve_context: true } }
352
- end
353
- else
354
- raise ConfigurationError, "Invalid delegation config format: #{item.inspect}"
355
- end
356
- end.uniq { |config| config[:agent] } # Remove duplicates by agent name
357
- elsif delegation_config.is_a?(Hash)
358
- # Hash format: {frontend: "AskFrontend", backend: nil}
359
- delegation_config.map do |agent, tool_name|
360
- { agent: agent.to_sym, tool_name: tool_name, preserve_context: true }
361
- end
362
- else
363
- raise ConfigurationError, "delegates_to must be an Array or Hash, got #{delegation_config.class}"
364
- end
365
- end
366
-
367
- # Extract plugin-specific configuration keys from the config hash
368
- #
369
- # Standard SDK keys are filtered out, leaving only plugin-specific keys.
370
- # This allows plugins to add their own configuration without SDK modifications.
371
- #
372
- # @param config [Hash] Full agent configuration
373
- # @return [Hash] Plugin-specific configuration (keys not recognized by SDK)
374
- def extract_plugin_configs(config)
375
- standard_keys = [
376
- :name,
377
- :description,
378
- :model,
379
- :provider,
380
- :base_url,
381
- :api_version,
382
- :context_window,
383
- :parameters,
384
- :headers,
385
- :request_timeout,
386
- :turn_timeout,
387
- :bypass_permissions,
388
- :max_concurrent_tools,
389
- :assume_model_exists,
390
- :disable_default_tools,
391
- :coding_agent,
392
- :directory,
393
- :system_prompt,
394
- :tools,
395
- :delegates_to,
396
- :mcp_servers,
397
- :hooks,
398
- :default_permissions,
399
- :permissions,
400
- :shared_across_delegations,
401
- :streaming,
402
- :directories,
403
- :disable_environment_info,
404
- ]
405
-
406
- config.reject { |k, _| standard_keys.include?(k.to_sym) }
407
- end
408
-
409
- # Parse tools configuration with permissions support
410
- #
411
- # Tools can be specified as:
412
- # - Symbol: :Write (no permissions)
413
- # - Hash: { Write: { allowed_paths: [...] } } (with permissions)
414
- #
415
- # Returns array of tool configs:
416
- # [
417
- # { name: :Read, permissions: nil },
418
- # { name: :Write, permissions: { allowed_paths: [...] } }
419
- # ]
420
- def parse_tools_with_permissions(tools_config, default_permissions, agent_permissions)
421
- tools_array = Array(tools_config || [])
422
-
423
- tools_array.map do |tool_spec|
424
- case tool_spec
425
- when Symbol, String
426
- # Simple tool: :Write or "Write"
427
- tool_name = tool_spec.to_sym
428
- permissions = resolve_permissions(tool_name, default_permissions, agent_permissions)
429
-
430
- { name: tool_name, permissions: permissions }
431
- when Hash
432
- # Check if already in parsed format: { name: :Write, permissions: {...} }
433
- if tool_spec.key?(:name)
434
- # Already parsed - pass through as-is
435
- tool_spec
436
- else
437
- # Tool with inline permissions: { Write: { allowed_paths: [...] } }
438
- tool_name = tool_spec.keys.first.to_sym
439
- inline_permissions = tool_spec.values.first
440
-
441
- # Inline permissions override defaults
442
- { name: tool_name, permissions: inline_permissions }
443
- end
444
- else
445
- raise ConfigurationError, "Invalid tool specification: #{tool_spec.inspect}"
446
- end
447
- end
448
- end
449
-
450
- # Resolve permissions for a tool from defaults and agent-level overrides
451
- def resolve_permissions(tool_name, default_permissions, agent_permissions)
452
- # Agent-level permissions override defaults
453
- agent_permissions[tool_name] || default_permissions[tool_name]
454
- end
455
-
456
- # Inject default write permissions for security
457
- #
458
- # Write, Edit, and MultiEdit tools without explicit permissions are automatically
459
- # restricted to only write within the agent's directory. This prevents accidental
460
- # writes outside the agent's working scope.
461
- #
462
- # Default permission: { allowed_paths: ["**/*"] }
463
- # This is resolved relative to the agent's directory by the permissions system.
464
- #
465
- # Users can override by explicitly setting permissions for these tools.
466
- def inject_default_write_permissions(tools)
467
- write_tools = [:Write, :Edit, :MultiEdit]
468
-
469
- tools.map do |tool_config|
470
- tool_name = tool_config[:name]
471
-
472
- # If it's a write tool and has no permissions, inject default
473
- if write_tools.include?(tool_name) && tool_config[:permissions].nil?
474
- tool_config.merge(permissions: { allowed_paths: ["**/*"] })
475
- else
476
- tool_config
477
- end
478
- end
479
- end
480
-
481
- # Parse hooks configuration
482
- #
483
- # Handles two input formats:
484
- #
485
- # 1. DSL format (from Agent::Builder): Pre-parsed HookDefinition objects
486
- # { event_type: [HookDefinition, ...] }
487
- # These are applied directly in pass_4_configure_hooks
488
- #
489
- # 2. YAML format: Raw hash with shell command specifications
490
- # hooks:
491
- # pre_tool_use:
492
- # - matcher: "Write|Edit"
493
- # type: command
494
- # command: "validate.sh"
495
- # These are kept raw and processed by Hooks::Adapter in pass_5
496
- #
497
- # Returns:
498
- # - DSL: { event_type: [HookDefinition, ...] }
499
- # - YAML: Raw hash (for Hooks::Adapter)
500
- def parse_hooks(hooks_config)
501
- return {} if hooks_config.nil? || hooks_config.empty?
502
-
503
- # If already parsed from DSL (HookDefinition objects), return as-is
504
- if hooks_config.is_a?(Hash) && hooks_config.values.all? { |v| v.is_a?(Array) && v.all? { |item| item.is_a?(Hooks::Definition) } }
505
- return hooks_config
506
- end
507
-
508
- # For YAML hooks: validate structure but keep raw for Hooks::Adapter
509
- validate_yaml_hooks(hooks_config)
510
-
511
- # Return raw YAML - Hooks::Adapter will process in pass_5
512
- hooks_config
513
- end
514
-
515
- # Validate YAML hooks structure
516
- #
517
- # @param hooks_config [Hash] YAML hooks configuration
518
- # @return [void]
519
- def validate_yaml_hooks(hooks_config)
520
- hooks_config.each do |event_name, hook_specs|
521
- event_sym = event_name.to_sym
522
-
523
- # Validate event type
524
- unless Hooks::Registry::VALID_EVENTS.include?(event_sym)
525
- raise ConfigurationError,
526
- "Invalid hook event '#{event_name}' for agent '#{@name}'. " \
527
- "Valid events: #{Hooks::Registry::VALID_EVENTS.join(", ")}"
528
- end
529
-
530
- # Validate each hook spec structure
531
- Array(hook_specs).each do |spec|
532
- hook_type = spec[:type] || spec["type"]
533
- command = spec[:command] || spec["command"]
534
-
535
- raise ConfigurationError, "Hook missing 'type' field for event #{event_name}" unless hook_type
536
- raise ConfigurationError, "Hook missing 'command' field for event #{event_name}" if hook_type.to_s == "command" && !command
537
- end
538
- end
539
- end
540
-
541
- # Coerce value to integer if it's a numeric string
542
- #
543
- # YAML sometimes parses numbers as strings (especially when quoted).
544
- # This ensures numeric values are properly converted.
545
- #
546
- # @param value [String, Integer, nil] Value to coerce
547
- # @return [Integer, nil] Coerced integer or nil
548
- def coerce_to_integer(value)
549
- return if value.nil?
550
- return value if value.is_a?(Integer)
551
- return value.to_i if value.is_a?(String) && value.match?(/\A\d+\z/)
552
-
553
- value
554
- end
555
-
556
- def validate!
557
- raise ConfigurationError, "Agent '#{@name}' missing required 'description' field" unless @description
558
-
559
- # Validate turn_timeout is positive if set
560
- if @turn_timeout && @turn_timeout <= 0
561
- raise ConfigurationError, "Agent '#{@name}' turn_timeout must be positive (got #{@turn_timeout})"
562
- end
563
-
564
- # Validate api_version can only be set for OpenAI-compatible providers
565
- if @api_version
566
- openai_compatible = ["openai", "deepseek", "perplexity", "mistral", "openrouter"]
567
- unless openai_compatible.include?(@provider.to_s)
568
- raise ConfigurationError,
569
- "Agent '#{@name}' has api_version set, but provider is '#{@provider}'. " \
570
- "api_version can only be used with OpenAI-compatible providers: #{openai_compatible.join(", ")}"
571
- end
572
-
573
- # Validate api_version value
574
- valid_versions = ["v1/chat/completions", "v1/responses"]
575
- unless valid_versions.include?(@api_version)
576
- raise ConfigurationError,
577
- "Agent '#{@name}' has invalid api_version '#{@api_version}'. " \
578
- "Valid values: #{valid_versions.join(", ")}"
579
- end
580
- end
581
-
582
- unless File.directory?(@directory)
583
- raise ConfigurationError, "Directory '#{@directory}' for agent '#{@name}' does not exist"
584
- end
585
- end
586
- end
587
- end
588
- end