swarm_sdk 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/swarm_sdk/agent/builder.rb +24 -8
- data/lib/swarm_sdk/agent/definition.rb +73 -40
- data/lib/swarm_sdk/claude_code_agent_adapter.rb +205 -0
- data/lib/swarm_sdk/configuration.rb +12 -2
- data/lib/swarm_sdk/markdown_parser.rb +30 -1
- data/lib/swarm_sdk/model_aliases.json +5 -0
- data/lib/swarm_sdk/models.json +1 -0
- data/lib/swarm_sdk/models.rb +120 -0
- data/lib/swarm_sdk/swarm/builder.rb +77 -4
- data/lib/swarm_sdk/swarm/tool_configurator.rb +29 -3
- data/lib/swarm_sdk/tools/registry.rb +1 -0
- data/lib/swarm_sdk/tools/think.rb +95 -0
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk.rb +0 -30
- metadata +6 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9aba64a75c41f9957bb3e11afbe0dd5a5fc6422793fda5d3fb829ac0042cbe18
|
4
|
+
data.tar.gz: 4f403fb7d107fa712a8b847775fea1140a03904cf066b8e848b914f4b3c573e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 204b9d889968b2d582557b06169d02828abd4c365b00e2a6551cc7f4c44a2ec7ae452a55d1e25b59fdc8a0b10c486c365cce2b2332d02f0d54b502ec6667af75
|
7
|
+
data.tar.gz: ae46da01755ef0c98b162c7c2be3c51ba8831dbbdd10301f937e0eed0260f9694b7514658d437e9d65d9cc58e06d7a877600ec29fd48eb773eec8514d74440b1
|
@@ -44,7 +44,7 @@ module SwarmSDK
|
|
44
44
|
@headers = {}
|
45
45
|
@timeout = nil
|
46
46
|
@mcp_servers = []
|
47
|
-
@
|
47
|
+
@disable_default_tools = nil # nil = include all default tools
|
48
48
|
@bypass_permissions = false
|
49
49
|
@coding_agent = nil # nil = not set (will default to false in Definition)
|
50
50
|
@assume_model_exists = nil
|
@@ -124,9 +124,19 @@ module SwarmSDK
|
|
124
124
|
@mcp_servers << server_config
|
125
125
|
end
|
126
126
|
|
127
|
-
#
|
128
|
-
|
129
|
-
|
127
|
+
# Disable default tools
|
128
|
+
#
|
129
|
+
# @param value [Boolean, Array<Symbol>]
|
130
|
+
# - true: Disable ALL default tools
|
131
|
+
# - Array of symbols: Disable specific tools (e.g., [:Think, :TodoWrite])
|
132
|
+
#
|
133
|
+
# @example Disable all default tools
|
134
|
+
# disable_default_tools true
|
135
|
+
#
|
136
|
+
# @example Disable specific tools
|
137
|
+
# disable_default_tools [:Think, :TodoWrite]
|
138
|
+
def disable_default_tools(value)
|
139
|
+
@disable_default_tools = value
|
130
140
|
end
|
131
141
|
|
132
142
|
# Set bypass_permissions flag
|
@@ -163,13 +173,14 @@ module SwarmSDK
|
|
163
173
|
@description = text
|
164
174
|
end
|
165
175
|
|
166
|
-
#
|
176
|
+
# Set or add tools
|
167
177
|
#
|
168
178
|
# Uses Set internally to automatically deduplicate tool names across multiple calls.
|
169
179
|
# This allows calling tools() multiple times without worrying about duplicates.
|
170
180
|
#
|
171
181
|
# @param tool_names [Array<Symbol>] Tool names to add
|
172
182
|
# @param include_default [Boolean] Whether to include default tools (Read, Grep, etc.)
|
183
|
+
# @param replace [Boolean] If true, replaces existing tools instead of merging (default: false)
|
173
184
|
#
|
174
185
|
# @example Basic usage with defaults
|
175
186
|
# tools :Grep, :Read # include_default: true is implicit
|
@@ -181,9 +192,14 @@ module SwarmSDK
|
|
181
192
|
# tools :Read
|
182
193
|
# tools :Write, :Edit # @tools now contains Set[:Read, :Write, :Edit]
|
183
194
|
# tools :Read # Still Set[:Read, :Write, :Edit] - no duplicate
|
184
|
-
|
195
|
+
#
|
196
|
+
# @example Replace tools (for markdown overrides)
|
197
|
+
# tools :Read, :Write, replace: true # Replaces all existing tools
|
198
|
+
def tools(*tool_names, include_default: true, replace: false)
|
199
|
+
@tools = Set.new if replace
|
185
200
|
@tools.merge(tool_names.map(&:to_sym))
|
186
|
-
|
201
|
+
# When include_default is false, disable all default tools
|
202
|
+
@disable_default_tools = true unless include_default
|
187
203
|
end
|
188
204
|
|
189
205
|
# Add tools from all_agents configuration
|
@@ -337,7 +353,7 @@ module SwarmSDK
|
|
337
353
|
agent_config[:headers] = @headers if @headers.any?
|
338
354
|
agent_config[:timeout] = @timeout if @timeout
|
339
355
|
agent_config[:mcp_servers] = @mcp_servers if @mcp_servers.any?
|
340
|
-
agent_config[:
|
356
|
+
agent_config[:disable_default_tools] = @disable_default_tools unless @disable_default_tools.nil?
|
341
357
|
agent_config[:bypass_permissions] = @bypass_permissions
|
342
358
|
agent_config[:coding_agent] = @coding_agent
|
343
359
|
agent_config[:assume_model_exists] = @assume_model_exists unless @assume_model_exists.nil?
|
@@ -38,7 +38,7 @@ module SwarmSDK
|
|
38
38
|
:parameters,
|
39
39
|
:headers,
|
40
40
|
:timeout,
|
41
|
-
:
|
41
|
+
:disable_default_tools,
|
42
42
|
:coding_agent,
|
43
43
|
:default_permissions,
|
44
44
|
:agent_permissions,
|
@@ -73,15 +73,15 @@ module SwarmSDK
|
|
73
73
|
@timeout = config[:timeout] || DEFAULT_TIMEOUT
|
74
74
|
@bypass_permissions = config[:bypass_permissions] || false
|
75
75
|
@max_concurrent_tools = config[:max_concurrent_tools]
|
76
|
-
#
|
77
|
-
|
78
|
-
|
79
|
-
else
|
80
|
-
(base_url ? true : false)
|
81
|
-
end
|
76
|
+
# Always assume model exists - SwarmSDK validates models separately using models.json
|
77
|
+
# This prevents RubyLLM from trying to validate models in its registry
|
78
|
+
@assume_model_exists = true
|
82
79
|
|
83
|
-
#
|
84
|
-
|
80
|
+
# disable_default_tools can be:
|
81
|
+
# - nil/not set: include all default tools (default behavior)
|
82
|
+
# - true: disable ALL default tools
|
83
|
+
# - Array of symbols: disable specific tools (e.g., [:Think, :TodoWrite])
|
84
|
+
@disable_default_tools = config[:disable_default_tools]
|
85
85
|
|
86
86
|
# coding_agent defaults to false if not specified
|
87
87
|
# When true, includes the base system prompt for coding tasks
|
@@ -120,7 +120,7 @@ module SwarmSDK
|
|
120
120
|
{
|
121
121
|
name: @name,
|
122
122
|
description: @description,
|
123
|
-
model: @model,
|
123
|
+
model: SwarmSDK::Models.resolve_alias(@model), # Resolve model aliases
|
124
124
|
directory: @directory,
|
125
125
|
tools: @tools,
|
126
126
|
delegates_to: @delegates_to,
|
@@ -133,7 +133,7 @@ module SwarmSDK
|
|
133
133
|
headers: @headers,
|
134
134
|
timeout: @timeout,
|
135
135
|
bypass_permissions: @bypass_permissions,
|
136
|
-
|
136
|
+
disable_default_tools: @disable_default_tools,
|
137
137
|
coding_agent: @coding_agent,
|
138
138
|
assume_model_exists: @assume_model_exists,
|
139
139
|
max_concurrent_tools: @max_concurrent_tools,
|
@@ -168,45 +168,53 @@ module SwarmSDK
|
|
168
168
|
|
169
169
|
private
|
170
170
|
|
171
|
-
# Validate that model exists in
|
171
|
+
# Validate that model exists in SwarmSDK's model registry
|
172
|
+
#
|
173
|
+
# Uses SwarmSDK's static models.json instead of RubyLLM's dynamic registry.
|
174
|
+
# This provides stable, offline model validation without network calls.
|
175
|
+
#
|
176
|
+
# Process:
|
177
|
+
# 1. Try to find model directly in models.json
|
178
|
+
# 2. If not found, try to resolve as alias and find again
|
179
|
+
# 3. If still not found, return warning with suggestions
|
172
180
|
#
|
173
181
|
# @return [Hash, nil] Warning hash if model not found, nil otherwise
|
174
182
|
def validate_model
|
175
|
-
# Try
|
176
|
-
|
177
|
-
|
183
|
+
# Try direct lookup first
|
184
|
+
model_data = SwarmSDK::Models.all.find { |m| (m["id"] || m[:id]) == @model }
|
185
|
+
|
186
|
+
# If not found, try alias resolution
|
187
|
+
unless model_data
|
188
|
+
resolved_id = SwarmSDK::Models.resolve_alias(@model)
|
189
|
+
# Only search again if alias was different
|
190
|
+
if resolved_id != @model
|
191
|
+
model_data = SwarmSDK::Models.all.find { |m| (m["id"] || m[:id]) == resolved_id }
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
if model_data
|
196
|
+
nil # Model exists (either directly or via alias)
|
197
|
+
else
|
198
|
+
# Model not found - return warning with suggestions
|
199
|
+
{
|
200
|
+
type: :model_not_found,
|
201
|
+
agent: @name,
|
202
|
+
model: @model,
|
203
|
+
error_message: "Unknown model: #{@model}",
|
204
|
+
suggestions: SwarmSDK::Models.suggest_similar(@model),
|
205
|
+
}
|
206
|
+
end
|
178
207
|
rescue StandardError => e
|
179
|
-
#
|
208
|
+
# Return warning on error
|
180
209
|
{
|
181
210
|
type: :model_not_found,
|
182
211
|
agent: @name,
|
183
212
|
model: @model,
|
184
213
|
error_message: e.message,
|
185
|
-
suggestions:
|
214
|
+
suggestions: [],
|
186
215
|
}
|
187
216
|
end
|
188
217
|
|
189
|
-
# Suggest similar models when a model is not found
|
190
|
-
#
|
191
|
-
# @return [Array<Hash>] Up to 3 similar models with their info
|
192
|
-
def suggest_similar_models
|
193
|
-
normalized_query = @model.to_s.downcase.gsub(/[.\-_]/, "")
|
194
|
-
|
195
|
-
RubyLLM.models.all.select do |model_info|
|
196
|
-
normalized_id = model_info.id.downcase.gsub(/[.\-_]/, "")
|
197
|
-
normalized_id.include?(normalized_query) ||
|
198
|
-
model_info.name&.downcase&.gsub(/[.\-_]/, "")&.include?(normalized_query)
|
199
|
-
end.first(3).map do |model_info|
|
200
|
-
{
|
201
|
-
id: model_info.id,
|
202
|
-
name: model_info.name,
|
203
|
-
context_window: model_info.context_window,
|
204
|
-
}
|
205
|
-
end
|
206
|
-
rescue StandardError
|
207
|
-
[]
|
208
|
-
end
|
209
|
-
|
210
218
|
def build_full_system_prompt(custom_prompt)
|
211
219
|
# If coding_agent is false (default), return custom prompt with optional TODO/Scratchpad info
|
212
220
|
# If coding_agent is true, include full base prompt for coding tasks
|
@@ -219,7 +227,7 @@ module SwarmSDK
|
|
219
227
|
else
|
220
228
|
rendered_base
|
221
229
|
end
|
222
|
-
elsif
|
230
|
+
elsif default_tools_enabled?
|
223
231
|
# Non-coding agent: optionally include TODO/Scratchpad sections if default tools available
|
224
232
|
non_coding_base = render_non_coding_base_prompt
|
225
233
|
|
@@ -237,6 +245,13 @@ module SwarmSDK
|
|
237
245
|
end
|
238
246
|
end
|
239
247
|
|
248
|
+
# Check if default tools are enabled (i.e., not disabled)
|
249
|
+
#
|
250
|
+
# @return [Boolean] True if default tools should be included
|
251
|
+
def default_tools_enabled?
|
252
|
+
@disable_default_tools != true
|
253
|
+
end
|
254
|
+
|
240
255
|
def render_base_system_prompt
|
241
256
|
cwd = @directory || Dir.pwd
|
242
257
|
platform = RUBY_PLATFORM
|
@@ -253,9 +268,27 @@ module SwarmSDK
|
|
253
268
|
|
254
269
|
def render_non_coding_base_prompt
|
255
270
|
# Simplified base prompt for non-coding agents
|
256
|
-
#
|
271
|
+
# Includes environment info, TODO, and Scratchpad tool information
|
257
272
|
# Does not steer towards coding tasks
|
273
|
+
cwd = @directory || Dir.pwd
|
274
|
+
platform = RUBY_PLATFORM
|
275
|
+
os_version = begin
|
276
|
+
%x(uname -sr 2>/dev/null).strip
|
277
|
+
rescue
|
278
|
+
RUBY_PLATFORM
|
279
|
+
end
|
280
|
+
date = Time.now.strftime("%Y-%m-%d")
|
281
|
+
|
258
282
|
<<~PROMPT.strip
|
283
|
+
# Environment
|
284
|
+
|
285
|
+
<env>
|
286
|
+
Working directory: #{cwd}
|
287
|
+
Platform: #{platform}
|
288
|
+
OS Version: #{os_version}
|
289
|
+
Today's date: #{date}
|
290
|
+
</env>
|
291
|
+
|
259
292
|
# Task Management
|
260
293
|
|
261
294
|
You have access to the TodoWrite tool to help you manage and plan tasks. Use this tool to track your progress and give visibility into your work.
|
@@ -0,0 +1,205 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SwarmSDK
|
4
|
+
# Adapter for converting Claude Code agent markdown files to SwarmSDK format
|
5
|
+
#
|
6
|
+
# Claude Code agent files use a different syntax and conventions than SwarmSDK:
|
7
|
+
# - Tools are comma-separated strings instead of arrays
|
8
|
+
# - Model shortcuts like 'sonnet', 'opus', 'haiku' instead of full model IDs
|
9
|
+
# - Tool permissions like 'Write(src/**)' instead of SwarmSDK's permission system
|
10
|
+
# - Required 'name' field in frontmatter
|
11
|
+
#
|
12
|
+
# This adapter:
|
13
|
+
# - Detects Claude Code format by checking frontmatter markers
|
14
|
+
# - Converts tools from comma-separated strings to arrays
|
15
|
+
# - Maps model shortcuts to canonical model IDs
|
16
|
+
# - Strips unsupported tool permission syntax with warnings
|
17
|
+
# - Sets coding_agent: true by default
|
18
|
+
# - Warns about unsupported fields
|
19
|
+
#
|
20
|
+
# @example Parse a Claude Code agent file
|
21
|
+
# content = File.read('.claude/agents/reviewer.md')
|
22
|
+
# config = ClaudeCodeAgentAdapter.parse(content, :reviewer)
|
23
|
+
# agent = Agent::Definition.new(:reviewer, config)
|
24
|
+
#
|
25
|
+
class ClaudeCodeAgentAdapter
|
26
|
+
# Fields supported in Claude Code agent frontmatter
|
27
|
+
SUPPORTED_FIELDS = ["name", "description", "tools", "model"].freeze
|
28
|
+
|
29
|
+
# SwarmSDK documentation URL for reference
|
30
|
+
SWARM_SDK_DOCS_URL = "https://github.com/parruda/claude-swarm/blob/main/docs/v2/README.md"
|
31
|
+
|
32
|
+
# Pattern to detect tool permission syntax like Write(src/**)
|
33
|
+
TOOL_PERMISSION_PATTERN = /^([A-Za-z_]+)\([^)]+\)$/
|
34
|
+
|
35
|
+
class << self
|
36
|
+
# Detect if content appears to be in Claude Code agent format
|
37
|
+
#
|
38
|
+
# Detection is based on tools field type:
|
39
|
+
# - Claude Code: tools is a comma-separated string (e.g., "Read, Write, Bash")
|
40
|
+
# - SwarmSDK: tools is an array (e.g., [Read, Write, Bash])
|
41
|
+
#
|
42
|
+
# Note: The 'name' field alone is not sufficient since SwarmSDK also supports it
|
43
|
+
#
|
44
|
+
# @param content [String] Markdown content with YAML frontmatter
|
45
|
+
# @return [Boolean] true if content appears to be Claude Code format
|
46
|
+
def claude_code_format?(content)
|
47
|
+
return false unless content =~ /\A---\s*\n(.*?)\n---\s*\n/m
|
48
|
+
|
49
|
+
frontmatter_yaml = Regexp.last_match(1)
|
50
|
+
frontmatter = YAML.safe_load(frontmatter_yaml, permitted_classes: [Symbol], aliases: true)
|
51
|
+
|
52
|
+
return false unless frontmatter.is_a?(Hash)
|
53
|
+
|
54
|
+
# Only detect as Claude Code if tools field is a comma-separated string
|
55
|
+
# This is the most reliable indicator since SwarmSDK always uses arrays
|
56
|
+
frontmatter.key?("tools") && frontmatter["tools"].is_a?(String)
|
57
|
+
rescue Psych::SyntaxError
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
# Parse Claude Code agent markdown and convert to SwarmSDK format
|
62
|
+
#
|
63
|
+
# @param content [String] Markdown content with YAML frontmatter
|
64
|
+
# @param agent_name [Symbol, String] Name of the agent
|
65
|
+
# @param inherit_model [String, nil] Model to use when frontmatter has 'inherit'
|
66
|
+
# @return [Hash] Configuration hash suitable for Agent::Definition.new
|
67
|
+
# @raise [ConfigurationError] if content format is invalid
|
68
|
+
def parse(content, agent_name, inherit_model: nil)
|
69
|
+
new(inherit_model: inherit_model).parse(content, agent_name)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Initialize adapter with optional context
|
74
|
+
#
|
75
|
+
# @param inherit_model [String, nil] Model to use when frontmatter has 'inherit'
|
76
|
+
def initialize(inherit_model: nil)
|
77
|
+
@inherit_model = inherit_model
|
78
|
+
@warnings = []
|
79
|
+
end
|
80
|
+
|
81
|
+
# Parse Claude Code agent content
|
82
|
+
#
|
83
|
+
# @param content [String] Markdown content with YAML frontmatter
|
84
|
+
# @param agent_name [Symbol, String] Name of the agent
|
85
|
+
# @return [Hash] Configuration hash for Agent::Definition
|
86
|
+
# @raise [ConfigurationError] if format is invalid
|
87
|
+
def parse(content, agent_name)
|
88
|
+
unless content =~ /\A---\s*\n(.*?)\n---\s*\n(.*)\z/m
|
89
|
+
raise ConfigurationError, "Invalid Claude Code agent format. Expected YAML frontmatter followed by prompt content."
|
90
|
+
end
|
91
|
+
|
92
|
+
frontmatter_yaml = Regexp.last_match(1)
|
93
|
+
prompt_content = Regexp.last_match(2).strip
|
94
|
+
|
95
|
+
frontmatter = YAML.safe_load(frontmatter_yaml, permitted_classes: [Symbol], aliases: true)
|
96
|
+
|
97
|
+
unless frontmatter.is_a?(Hash)
|
98
|
+
raise ConfigurationError, "Invalid frontmatter format in Claude Code agent file"
|
99
|
+
end
|
100
|
+
|
101
|
+
config = build_config(frontmatter, prompt_content, agent_name)
|
102
|
+
emit_warnings(agent_name)
|
103
|
+
config
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
# Build SwarmSDK configuration from Claude Code frontmatter
|
109
|
+
def build_config(frontmatter, prompt_content, agent_name)
|
110
|
+
warn_unknown_fields(frontmatter)
|
111
|
+
|
112
|
+
config = {
|
113
|
+
description: frontmatter["description"],
|
114
|
+
system_prompt: prompt_content,
|
115
|
+
coding_agent: true, # Default for Claude Code agents
|
116
|
+
}
|
117
|
+
|
118
|
+
# Parse tools if present
|
119
|
+
if frontmatter["tools"]
|
120
|
+
config[:tools] = parse_tools(frontmatter["tools"])
|
121
|
+
end
|
122
|
+
|
123
|
+
# Parse model if present
|
124
|
+
if frontmatter["model"]
|
125
|
+
config[:model] = resolve_model(frontmatter["model"])
|
126
|
+
end
|
127
|
+
|
128
|
+
config
|
129
|
+
end
|
130
|
+
|
131
|
+
# Parse tools field - handles both comma-separated string and array
|
132
|
+
#
|
133
|
+
# @param tools_field [String, Array] Tools from frontmatter
|
134
|
+
# @return [Array<String>] Array of tool names
|
135
|
+
def parse_tools(tools_field)
|
136
|
+
tools_array = if tools_field.is_a?(String)
|
137
|
+
tools_field.split(",").map(&:strip)
|
138
|
+
else
|
139
|
+
Array(tools_field).map(&:to_s)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Clean tool permissions and collect warnings
|
143
|
+
tools_array.map { |tool| clean_tool_permissions(tool) }.compact
|
144
|
+
end
|
145
|
+
|
146
|
+
# Strip tool permission syntax and warn if detected
|
147
|
+
#
|
148
|
+
# @param tool_string [String] Tool name, possibly with permissions like 'Write(src/**)'
|
149
|
+
# @return [String, nil] Clean tool name, or nil if invalid
|
150
|
+
def clean_tool_permissions(tool_string)
|
151
|
+
if tool_string =~ TOOL_PERMISSION_PATTERN
|
152
|
+
tool_name = Regexp.last_match(1)
|
153
|
+
@warnings << "Tool permission syntax '#{tool_string}' detected in agent file. SwarmSDK supports permissions but uses different syntax. Using '#{tool_name}' without restrictions for now. See SwarmSDK documentation for permission configuration: #{SWARM_SDK_DOCS_URL}"
|
154
|
+
tool_name
|
155
|
+
else
|
156
|
+
tool_string
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Resolve model shortcuts to canonical model IDs
|
161
|
+
#
|
162
|
+
# Uses SwarmSDK::Models.resolve_alias to map shortcuts like 'sonnet'
|
163
|
+
# to the latest model IDs from model_aliases.json.
|
164
|
+
#
|
165
|
+
# @param model_field [String] Model from frontmatter
|
166
|
+
# @return [String, Symbol] Canonical model ID or :inherit symbol
|
167
|
+
def resolve_model(model_field)
|
168
|
+
model_str = model_field.to_s.strip
|
169
|
+
|
170
|
+
# Handle 'inherit' keyword
|
171
|
+
return :inherit if model_str == "inherit"
|
172
|
+
|
173
|
+
# Resolve using SwarmSDK model aliases
|
174
|
+
# This maps 'sonnet' → 'claude-sonnet-4-5-20250929', etc.
|
175
|
+
SwarmSDK::Models.resolve_alias(model_str)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Warn about unknown frontmatter fields
|
179
|
+
def warn_unknown_fields(frontmatter)
|
180
|
+
unknown_fields = frontmatter.keys - SUPPORTED_FIELDS
|
181
|
+
|
182
|
+
unknown_fields.each do |field|
|
183
|
+
@warnings << case field
|
184
|
+
when "hooks"
|
185
|
+
"Hooks configuration detected in agent frontmatter. SwarmSDK handles hooks at the swarm level. See: #{SWARM_SDK_DOCS_URL}"
|
186
|
+
else
|
187
|
+
"Unknown field '#{field}' in Claude Code agent file. Ignoring. Supported fields: #{SUPPORTED_FIELDS.join(", ")}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Emit all collected warnings via LogCollector
|
193
|
+
def emit_warnings(agent_name)
|
194
|
+
return if @warnings.empty?
|
195
|
+
|
196
|
+
@warnings.each do |warning|
|
197
|
+
LogCollector.emit(
|
198
|
+
type: "claude_code_conversion_warning",
|
199
|
+
agent: agent_name,
|
200
|
+
message: warning,
|
201
|
+
)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
@@ -116,6 +116,11 @@ module SwarmSDK
|
|
116
116
|
return unless @config[:swarm]
|
117
117
|
|
118
118
|
@all_agents_config = @config[:swarm][:all_agents] || {}
|
119
|
+
|
120
|
+
# Convert disable_default_tools array elements to symbols
|
121
|
+
if @all_agents_config[:disable_default_tools].is_a?(Array)
|
122
|
+
@all_agents_config[:disable_default_tools] = @all_agents_config[:disable_default_tools].map(&:to_sym)
|
123
|
+
end
|
119
124
|
end
|
120
125
|
|
121
126
|
def load_hooks_config
|
@@ -212,6 +217,9 @@ module SwarmSDK
|
|
212
217
|
# Merge headers: all_agents.headers + agent.headers
|
213
218
|
# Agent values override all_agents values for same keys
|
214
219
|
merged[:headers] = (merged[:headers] || {}).merge(value || {})
|
220
|
+
when :disable_default_tools
|
221
|
+
# Convert array elements to symbols if it's an array
|
222
|
+
merged[key] = value.is_a?(Array) ? value.map(&:to_sym) : value
|
215
223
|
else
|
216
224
|
# For everything else (model, provider, base_url, timeout, coding_agent, etc.),
|
217
225
|
# agent value overrides all_agents value
|
@@ -238,8 +246,10 @@ module SwarmSDK
|
|
238
246
|
# Parse markdown and merge with YAML config
|
239
247
|
agent_def_from_file = MarkdownParser.parse(content, name)
|
240
248
|
|
241
|
-
# Merge:
|
242
|
-
|
249
|
+
# Merge: YAML config overrides markdown file (YAML takes precedence)
|
250
|
+
# This allows YAML to override any settings from the markdown file
|
251
|
+
final_config = agent_def_from_file.to_h.compact.merge(merged_config.compact)
|
252
|
+
|
243
253
|
Agent::Definition.new(name, final_config)
|
244
254
|
rescue StandardError => e
|
245
255
|
raise ConfigurationError, "Error loading agent '#{name}' from file '#{file_path}': #{e.message}"
|
@@ -1,12 +1,41 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SwarmSDK
|
4
|
+
# Parser for agent markdown files with YAML frontmatter
|
5
|
+
#
|
6
|
+
# Supports two formats:
|
7
|
+
# 1. SwarmSDK format - YAML frontmatter with array-based tools
|
8
|
+
# 2. Claude Code format - Detected and converted via ClaudeCodeAgentAdapter
|
9
|
+
#
|
10
|
+
# Format detection is automatic based on frontmatter structure.
|
4
11
|
class MarkdownParser
|
5
12
|
FRONTMATTER_PATTERN = /\A---\s*\n(.*?)\n---\s*\n(.*)\z/m
|
6
13
|
|
7
14
|
class << self
|
15
|
+
# Parse markdown content into an Agent::Definition
|
16
|
+
#
|
17
|
+
# Automatically detects format (SwarmSDK or Claude Code) and routes
|
18
|
+
# to appropriate parser.
|
19
|
+
#
|
20
|
+
# @param content [String] Markdown content with YAML frontmatter
|
21
|
+
# @param agent_name [Symbol, String, nil] Name of the agent
|
22
|
+
# @return [Agent::Definition] Parsed agent definition
|
23
|
+
# @raise [ConfigurationError] if format is invalid
|
8
24
|
def parse(content, agent_name = nil)
|
9
|
-
|
25
|
+
# Detect Claude Code format and route to adapter
|
26
|
+
if ClaudeCodeAgentAdapter.claude_code_format?(content)
|
27
|
+
config = ClaudeCodeAgentAdapter.parse(content, agent_name)
|
28
|
+
# For Claude Code format, agent_name parameter is required since
|
29
|
+
# the 'name' field in frontmatter is Claude Code specific and not used
|
30
|
+
unless agent_name
|
31
|
+
raise ConfigurationError, "Agent name must be provided when parsing Claude Code format"
|
32
|
+
end
|
33
|
+
|
34
|
+
Agent::Definition.new(agent_name.to_sym, config)
|
35
|
+
else
|
36
|
+
# Use standard SwarmSDK format parsing
|
37
|
+
new(content, agent_name).parse
|
38
|
+
end
|
10
39
|
end
|
11
40
|
end
|
12
41
|
|
@@ -0,0 +1 @@
|
|
1
|
+
[{"name":"Claude Sonnet 4.5","id":"claude-sonnet-4-5-20250929","provider":"anthropic","family":"claude-sonnet-4-5","context_window":200000,"max_output_tokens":64000,"modalities":{"input":["image","text"],"output":["text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":3.0,"cached_input_per_million":3.75,"output_per_million":15.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"Claude Haiku 4.5","id":"claude-haiku-4-5-20251001","provider":"anthropic","family":"claude-haiku-4-5","context_window":200000,"max_output_tokens":64000,"modalities":{"input":["image","text"],"output":["text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":1.0,"cached_input_per_million":1.25,"output_per_million":5.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"Claude Opus 4.1","id":"claude-opus-4-1-20250805","provider":"anthropic","family":"claude-opus-4-1","context_window":200000,"max_output_tokens":32000,"modalities":{"input":["image","text"],"output":["text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":15.0,"cached_input_per_million":18.75,"output_per_million":75.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"Gemini 2.5 Pro","id":"gemini-2.5-pro","provider":"gemini","family":"gemini-2.5-pro","context_window":1048576,"max_output_tokens":65536,"modalities":{"input":["audio","text"],"output":["text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":1.25,"cached_input_per_million":0.125,"output_per_million":10.0},"batch":{"input_per_million":0.625,"output_per_million":5.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"Gemini 2.5 Flash","id":"gemini-2.5-flash","provider":"gemini","family":"gemini-2.5-flash","context_window":1048576,"max_output_tokens":65536,"modalities":{"input":["audio","image","text"],"output":["text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.3,"cached_input_per_million":0.03,"output_per_million":2.5},"batch":{"input_per_million":0.15,"output_per_million":1.25}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"Gemini 2.5 Flash-Lite","id":"gemini-2.5-flash-lite","provider":"gemini","family":"gemini-2.5-flash-lite","context_window":1048576,"max_output_tokens":65536,"modalities":{"input":["audio","image","text"],"output":["text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.3,"cached_input_per_million":0.03,"output_per_million":2.5},"batch":{"input_per_million":0.15,"output_per_million":1.25}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"Gemini 2.0 Flash","id":"gemini-2.0-flash","provider":"gemini","family":"gemini-2.0-flash","context_window":1048576,"max_output_tokens":8192,"modalities":{"input":["audio","image","text"],"output":["text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.1,"cached_input_per_million":0.025,"output_per_million":0.4},"batch":{"input_per_million":0.05,"output_per_million":0.2}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"Gemini 2.0 Flash","id":"gemini-2.0-flash-001","provider":"gemini","family":"gemini-2.0-flash","context_window":1048576,"max_output_tokens":8192,"modalities":{"input":["audio","image","text"],"output":["text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.1,"cached_input_per_million":0.025,"output_per_million":0.4},"batch":{"input_per_million":0.05,"output_per_million":0.2}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"Gemini 2.0 Flash","id":"gemini-2.0-flash-exp","provider":"gemini","family":"gemini-2.0-flash","context_window":1048576,"max_output_tokens":8192,"modalities":{"input":["audio","image","text"],"output":["text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.1,"cached_input_per_million":0.025,"output_per_million":0.4},"batch":{"input_per_million":0.05,"output_per_million":0.2}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"Gemini 2.0 Flash-Lite","id":"gemini-2.0-flash-lite","provider":"gemini","family":"gemini-2.0-flash-lite","context_window":1048576,"max_output_tokens":8192,"modalities":{"input":["audio","image","text"],"output":["text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.1,"cached_input_per_million":0.025,"output_per_million":0.4},"batch":{"input_per_million":0.05,"output_per_million":0.2}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"Gemini 2.0 Flash-Lite","id":"gemini-2.0-flash-lite-001","provider":"gemini","family":"gemini-2.0-flash-lite","context_window":1048576,"max_output_tokens":8192,"modalities":{"input":["audio","image","text"],"output":["text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.1,"cached_input_per_million":0.025,"output_per_million":0.4},"batch":{"input_per_million":0.05,"output_per_million":0.2}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"deepseek-chat","id":"deepseek-chat","provider":"deepseek","family":"deepseek-chat","context_window":128000,"max_output_tokens":8000,"modalities":{"input":["text"],"output":["text"]},"capabilities":["function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.28,"cached_input_per_million":0.028,"output_per_million":0.42},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"deepseek-reasoner","id":"deepseek-reasoner","provider":"deepseek","family":"deepseek-reasoner","context_window":null,"max_output_tokens":64000,"modalities":{"input":["text"],"output":["text"]},"capabilities":["function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":null,"cached_input_per_million":null,"output_per_million":null},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-5","family":"gpt-5","provider":"openai","id":"gpt-5-2025-08-07","context_window":400000,"max_output_tokens":128000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":1.25,"cached_input_per_million":0.125,"output_per_million":10.0},"batch":{"input_per_million":0.625,"output_per_million":5.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-5","family":"gpt-5","provider":"openai","id":"gpt-5","context_window":400000,"max_output_tokens":128000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":1.25,"cached_input_per_million":0.125,"output_per_million":10.0},"batch":{"input_per_million":0.625,"output_per_million":5.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-5 mini","family":"gpt-5-mini","provider":"openai","id":"gpt-5-mini-2025-08-07","context_window":400000,"max_output_tokens":128000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.25,"cached_input_per_million":0.025,"output_per_million":2.0},"batch":{"input_per_million":0.125,"output_per_million":1.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-5 mini","family":"gpt-5-mini","provider":"openai","id":"gpt-5-mini","context_window":400000,"max_output_tokens":128000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.25,"cached_input_per_million":0.025,"output_per_million":2.0},"batch":{"input_per_million":0.125,"output_per_million":1.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-5 nano","family":"gpt-5-nano","provider":"openai","id":"gpt-5-nano-2025-08-07","context_window":400000,"max_output_tokens":128000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.05,"cached_input_per_million":0.005,"output_per_million":0.4},"batch":{"input_per_million":0.025,"output_per_million":0.2}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-5 nano","family":"gpt-5-nano","provider":"openai","id":"gpt-5-nano","context_window":400000,"max_output_tokens":128000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.05,"cached_input_per_million":0.005,"output_per_million":0.4},"batch":{"input_per_million":0.025,"output_per_million":0.2}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-5 pro","family":"gpt-5-pro","provider":"openai","id":"gpt-5-pro-2025-10-06","context_window":400000,"max_output_tokens":272000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":15.0,"cached_input_per_million":null,"output_per_million":120.0},"batch":{"input_per_million":7.5,"output_per_million":60.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-5 pro","family":"gpt-5-pro","provider":"openai","id":"gpt-5-pro","context_window":400000,"max_output_tokens":272000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":15.0,"cached_input_per_million":null,"output_per_million":120.0},"batch":{"input_per_million":7.5,"output_per_million":60.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4.1","family":"gpt-4.1","provider":"openai","id":"gpt-4.1-2025-04-14","context_window":1047576,"max_output_tokens":32768,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":2.0,"cached_input_per_million":0.5,"output_per_million":8.0},"batch":{"input_per_million":1.0,"output_per_million":4.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4.1","family":"gpt-4.1","provider":"openai","id":"gpt-4.1","context_window":1047576,"max_output_tokens":32768,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":2.0,"cached_input_per_million":0.5,"output_per_million":8.0},"batch":{"input_per_million":1.0,"output_per_million":4.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"gpt-oss-120b","family":"gpt-oss-120b","provider":"openai","id":"gpt-oss-120b","context_window":131072,"max_output_tokens":131072,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":null,"cached_input_per_million":null,"output_per_million":null},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"gpt-oss-20b","family":"gpt-oss-20b","provider":"openai","id":"gpt-oss-20b","context_window":131072,"max_output_tokens":131072,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":null,"cached_input_per_million":null,"output_per_million":null},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"Sora 2","family":"sora-2","provider":"openai","id":"sora-2","context_window":null,"max_output_tokens":null,"modalities":{"input":["image","text"],"output":["audio","embeddings"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":null,"cached_input_per_million":null,"output_per_million":null},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"Sora 2 Pro","family":"sora-2-pro","provider":"openai","id":"sora-2-pro","context_window":null,"max_output_tokens":null,"modalities":{"input":["image","text"],"output":["audio","embeddings"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":null,"cached_input_per_million":null,"output_per_million":null},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o3-deep-research","family":"o3-deep-research","provider":"openai","id":"o3-deep-research-2025-06-26","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch"],"pricing":{"text_tokens":{"standard":{"input_per_million":10.0,"cached_input_per_million":2.5,"output_per_million":40.0},"batch":{"input_per_million":5.0,"output_per_million":20.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o3-deep-research","family":"o3-deep-research","provider":"openai","id":"o3-deep-research","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch"],"pricing":{"text_tokens":{"standard":{"input_per_million":10.0,"cached_input_per_million":2.5,"output_per_million":40.0},"batch":{"input_per_million":5.0,"output_per_million":20.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o4-mini-deep-research","family":"o4-mini-deep-research","provider":"openai","id":"o4-mini-deep-research-2025-06-26","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch"],"pricing":{"text_tokens":{"standard":{"input_per_million":2.0,"cached_input_per_million":0.5,"output_per_million":8.0},"batch":{"input_per_million":1.0,"output_per_million":4.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o4-mini-deep-research","family":"o4-mini-deep-research","provider":"openai","id":"o4-mini-deep-research","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch"],"pricing":{"text_tokens":{"standard":{"input_per_million":2.0,"cached_input_per_million":0.5,"output_per_million":8.0},"batch":{"input_per_million":1.0,"output_per_million":4.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT Image 1","family":"gpt-image-1","provider":"openai","id":"gpt-image-1","context_window":null,"max_output_tokens":null,"modalities":{"input":["image","text"],"output":["embeddings","image"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":5.0,"cached_input_per_million":1.25,"output_per_million":40.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"gpt-image-1-mini","family":"gpt-image-1-mini","provider":"openai","id":"gpt-image-1-mini","context_window":null,"max_output_tokens":null,"modalities":{"input":["image","text"],"output":["embeddings","image"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":2.0,"cached_input_per_million":0.2,"output_per_million":8.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"DALL·E 3","family":"dall-e-3","provider":"openai","id":"dall-e-3","context_window":null,"max_output_tokens":null,"modalities":{"input":["text"],"output":["embeddings","image"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":null,"cached_input_per_million":null,"output_per_million":null},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o mini TTS","family":"gpt-4o-mini-tts","provider":"openai","id":"gpt-4o-mini-tts","context_window":2000,"max_output_tokens":null,"modalities":{"input":["text"],"output":["audio","embeddings"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":0.6,"cached_input_per_million":null,"output_per_million":12.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o Transcribe","family":"gpt-4o-transcribe","provider":"openai","id":"gpt-4o-transcribe","context_window":16000,"max_output_tokens":2000,"modalities":{"input":["audio","text"],"output":["embeddings","text"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":2.5,"cached_input_per_million":null,"output_per_million":10.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o mini Transcribe","family":"gpt-4o-mini-transcribe","provider":"openai","id":"gpt-4o-mini-transcribe","context_window":16000,"max_output_tokens":2000,"modalities":{"input":["audio","text"],"output":["embeddings","text"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":1.25,"cached_input_per_million":null,"output_per_million":5.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"gpt-realtime","family":"gpt-realtime","provider":"openai","id":"gpt-realtime-2025-08-28","context_window":32000,"max_output_tokens":4096,"modalities":{"input":["audio","image","text"],"output":["audio","embeddings","text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":4.0,"cached_input_per_million":0.5,"output_per_million":16.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"gpt-realtime","family":"gpt-realtime","provider":"openai","id":"gpt-realtime","context_window":32000,"max_output_tokens":4096,"modalities":{"input":["audio","image","text"],"output":["audio","embeddings","text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":4.0,"cached_input_per_million":0.5,"output_per_million":16.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"gpt-audio","family":"gpt-audio","provider":"openai","id":"gpt-audio-2025-08-28","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["audio","text"],"output":["audio","embeddings","text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":2.5,"cached_input_per_million":null,"output_per_million":10.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"gpt-audio","family":"gpt-audio","provider":"openai","id":"gpt-audio","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["audio","text"],"output":["audio","embeddings","text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":2.5,"cached_input_per_million":null,"output_per_million":10.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"gpt-realtime-mini","family":"gpt-realtime-mini","provider":"openai","id":"gpt-realtime-mini-2025-10-06","context_window":32000,"max_output_tokens":4096,"modalities":{"input":["audio","image","text"],"output":["audio","embeddings","text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.6,"cached_input_per_million":0.06,"output_per_million":2.4},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"gpt-realtime-mini","family":"gpt-realtime-mini","provider":"openai","id":"gpt-realtime-mini","context_window":32000,"max_output_tokens":4096,"modalities":{"input":["audio","image","text"],"output":["audio","embeddings","text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.6,"cached_input_per_million":0.06,"output_per_million":2.4},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"gpt-audio-mini","family":"gpt-audio-mini","provider":"openai","id":"gpt-audio-mini-2025-10-06","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["audio","text"],"output":["audio","embeddings","text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.6,"cached_input_per_million":null,"output_per_million":2.4},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"gpt-audio-mini","family":"gpt-audio-mini","provider":"openai","id":"gpt-audio-mini","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["audio","text"],"output":["audio","embeddings","text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.6,"cached_input_per_million":null,"output_per_million":2.4},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-5 Chat","family":"gpt-5-chat-latest","provider":"openai","id":"gpt-5-chat-latest","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":1.25,"cached_input_per_million":0.125,"output_per_million":10.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"ChatGPT-4o","family":"chatgpt-4o-latest","provider":"openai","id":"chatgpt-4o-latest","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":5.0,"cached_input_per_million":null,"output_per_million":15.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-5-Codex","family":"gpt-5-codex","provider":"openai","id":"gpt-5-codex","context_window":400000,"max_output_tokens":128000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":1.25,"cached_input_per_million":0.125,"output_per_million":10.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o3-pro","family":"o3-pro","provider":"openai","id":"o3-pro-2025-06-10","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":20.0,"cached_input_per_million":null,"output_per_million":80.0},"batch":{"input_per_million":10.0,"output_per_million":40.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o3-pro","family":"o3-pro","provider":"openai","id":"o3-pro","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":20.0,"cached_input_per_million":null,"output_per_million":80.0},"batch":{"input_per_million":10.0,"output_per_million":40.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o3","family":"o3","provider":"openai","id":"o3-2025-04-16","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":2.0,"cached_input_per_million":0.5,"output_per_million":8.0},"batch":{"input_per_million":1.0,"output_per_million":4.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o3","family":"o3","provider":"openai","id":"o3","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":2.0,"cached_input_per_million":0.5,"output_per_million":8.0},"batch":{"input_per_million":1.0,"output_per_million":4.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o4-mini","family":"o4-mini","provider":"openai","id":"o4-mini-2025-04-16","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":1.1,"cached_input_per_million":0.275,"output_per_million":4.4},"batch":{"input_per_million":0.55,"output_per_million":2.2}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o4-mini","family":"o4-mini","provider":"openai","id":"o4-mini","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":1.1,"cached_input_per_million":0.275,"output_per_million":4.4},"batch":{"input_per_million":0.55,"output_per_million":2.2}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4.1 mini","family":"gpt-4.1-mini","provider":"openai","id":"gpt-4.1-mini-2025-04-14","context_window":1047576,"max_output_tokens":32768,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.4,"cached_input_per_million":0.1,"output_per_million":1.6},"batch":{"input_per_million":0.2,"output_per_million":0.8}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4.1 mini","family":"gpt-4.1-mini","provider":"openai","id":"gpt-4.1-mini","context_window":1047576,"max_output_tokens":32768,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.4,"cached_input_per_million":0.1,"output_per_million":1.6},"batch":{"input_per_million":0.2,"output_per_million":0.8}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4.1 nano","family":"gpt-4.1-nano","provider":"openai","id":"gpt-4.1-nano-2025-04-14","context_window":1047576,"max_output_tokens":32768,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.1,"cached_input_per_million":0.025,"output_per_million":0.4},"batch":{"input_per_million":0.05,"output_per_million":0.2}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4.1 nano","family":"gpt-4.1-nano","provider":"openai","id":"gpt-4.1-nano","context_window":1047576,"max_output_tokens":32768,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.1,"cached_input_per_million":0.025,"output_per_million":0.4},"batch":{"input_per_million":0.05,"output_per_million":0.2}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o1-pro","family":"o1-pro","provider":"openai","id":"o1-pro-2025-03-19","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":150.0,"cached_input_per_million":null,"output_per_million":600.0},"batch":{"input_per_million":75.0,"output_per_million":300.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o1-pro","family":"o1-pro","provider":"openai","id":"o1-pro","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":150.0,"cached_input_per_million":null,"output_per_million":600.0},"batch":{"input_per_million":75.0,"output_per_million":300.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"computer-use-preview","family":"computer-use-preview","provider":"openai","id":"computer-use-preview-2025-03-11","context_window":8192,"max_output_tokens":1024,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":3.0,"cached_input_per_million":null,"output_per_million":12.0},"batch":{"input_per_million":1.5,"output_per_million":6.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"computer-use-preview","family":"computer-use-preview","provider":"openai","id":"computer-use-preview","context_window":8192,"max_output_tokens":1024,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":3.0,"cached_input_per_million":null,"output_per_million":12.0},"batch":{"input_per_million":1.5,"output_per_million":6.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o mini Search Preview","family":"gpt-4o-mini-search-preview","provider":"openai","id":"gpt-4o-mini-search-preview-2025-03-11","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":["structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.15,"cached_input_per_million":null,"output_per_million":0.6},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o mini Search Preview","family":"gpt-4o-mini-search-preview","provider":"openai","id":"gpt-4o-mini-search-preview","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":["structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.15,"cached_input_per_million":null,"output_per_million":0.6},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o Search Preview","family":"gpt-4o-search-preview","provider":"openai","id":"gpt-4o-search-preview-2025-03-11","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":["structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":2.5,"cached_input_per_million":null,"output_per_million":10.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o Search Preview","family":"gpt-4o-search-preview","provider":"openai","id":"gpt-4o-search-preview","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":["structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":2.5,"cached_input_per_million":null,"output_per_million":10.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4.5 Preview (Deprecated)","family":"gpt-4.5-preview","provider":"openai","id":"gpt-4.5-preview-2025-02-27","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":75.0,"cached_input_per_million":37.5,"output_per_million":150.0},"batch":{"input_per_million":37.5,"output_per_million":75.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4.5 Preview (Deprecated)","family":"gpt-4.5-preview","provider":"openai","id":"gpt-4.5-preview","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":75.0,"cached_input_per_million":37.5,"output_per_million":150.0},"batch":{"input_per_million":37.5,"output_per_million":75.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o3-mini","family":"o3-mini","provider":"openai","id":"o3-mini-2025-01-31","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":1.1,"cached_input_per_million":0.55,"output_per_million":4.4},"batch":{"input_per_million":0.55,"output_per_million":2.2}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o3-mini","family":"o3-mini","provider":"openai","id":"o3-mini","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":1.1,"cached_input_per_million":0.55,"output_per_million":4.4},"batch":{"input_per_million":0.55,"output_per_million":2.2}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o mini Audio","family":"gpt-4o-mini-audio-preview","provider":"openai","id":"gpt-4o-mini-audio-preview-2024-12-17","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["audio","text"],"output":["audio","embeddings","text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.15,"cached_input_per_million":null,"output_per_million":0.6},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o mini Audio","family":"gpt-4o-mini-audio-preview","provider":"openai","id":"gpt-4o-mini-audio-preview","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["audio","text"],"output":["audio","embeddings","text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.15,"cached_input_per_million":null,"output_per_million":0.6},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o mini Realtime","family":"gpt-4o-mini-realtime-preview","provider":"openai","id":"gpt-4o-mini-realtime-preview-2024-12-17","context_window":16000,"max_output_tokens":4096,"modalities":{"input":["audio","text"],"output":["audio","embeddings","text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.6,"cached_input_per_million":0.3,"output_per_million":2.4},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o mini Realtime","family":"gpt-4o-mini-realtime-preview","provider":"openai","id":"gpt-4o-mini-realtime-preview","context_window":16000,"max_output_tokens":4096,"modalities":{"input":["audio","text"],"output":["audio","embeddings","text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.6,"cached_input_per_million":0.3,"output_per_million":2.4},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o1","family":"o1","provider":"openai","id":"o1-2024-12-17","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":15.0,"cached_input_per_million":7.5,"output_per_million":60.0},"batch":{"input_per_million":7.5,"output_per_million":30.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o1","family":"o1","provider":"openai","id":"o1","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":15.0,"cached_input_per_million":7.5,"output_per_million":60.0},"batch":{"input_per_million":7.5,"output_per_million":30.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"omni-moderation","family":"omni-moderation-latest","provider":"openai","id":"omni-moderation-latest","context_window":null,"max_output_tokens":null,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":0.0,"cached_input_per_million":null,"output_per_million":null},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o1-mini","family":"o1-mini","provider":"openai","id":"o1-mini-2024-09-12","context_window":128000,"max_output_tokens":65536,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":1.1,"cached_input_per_million":0.55,"output_per_million":4.4},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o1-mini","family":"o1-mini","provider":"openai","id":"o1-mini","context_window":128000,"max_output_tokens":65536,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":1.1,"cached_input_per_million":0.55,"output_per_million":4.4},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o1 Preview","family":"o1-preview","provider":"openai","id":"o1-preview-2024-09-12","context_window":128000,"max_output_tokens":32768,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":["function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":15.0,"cached_input_per_million":7.5,"output_per_million":60.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"o1 Preview","family":"o1-preview","provider":"openai","id":"o1-preview","context_window":128000,"max_output_tokens":32768,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":["function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":15.0,"cached_input_per_million":7.5,"output_per_million":60.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o","family":"gpt-4o","provider":"openai","id":"gpt-4o-2024-08-06","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":2.5,"cached_input_per_million":1.25,"output_per_million":10.0},"batch":{"input_per_million":1.25,"output_per_million":5.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o","family":"gpt-4o","provider":"openai","id":"gpt-4o","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":2.5,"cached_input_per_million":1.25,"output_per_million":10.0},"batch":{"input_per_million":1.25,"output_per_million":5.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o Audio","family":"gpt-4o-audio-preview","provider":"openai","id":"gpt-4o-audio-preview","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["audio","text"],"output":["audio","embeddings","text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":2.5,"cached_input_per_million":null,"output_per_million":10.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o mini","family":"gpt-4o-mini","provider":"openai","id":"gpt-4o-mini-2024-07-18","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.15,"cached_input_per_million":0.075,"output_per_million":0.6},"batch":{"input_per_million":0.075,"output_per_million":0.3}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o mini","family":"gpt-4o-mini","provider":"openai","id":"gpt-4o-mini","context_window":128000,"max_output_tokens":16384,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.15,"cached_input_per_million":0.075,"output_per_million":0.6},"batch":{"input_per_million":0.075,"output_per_million":0.3}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4o Realtime","family":"gpt-4o-realtime-preview","provider":"openai","id":"gpt-4o-realtime-preview","context_window":32000,"max_output_tokens":4096,"modalities":{"input":["audio","text"],"output":["audio","embeddings","text"]},"capabilities":["function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":5.0,"cached_input_per_million":2.5,"output_per_million":20.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4 Turbo","family":"gpt-4-turbo","provider":"openai","id":"gpt-4-turbo-2024-04-09","context_window":128000,"max_output_tokens":4096,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":10.0,"cached_input_per_million":null,"output_per_million":30.0},"batch":{"input_per_million":5.0,"output_per_million":15.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4 Turbo","family":"gpt-4-turbo","provider":"openai","id":"gpt-4-turbo","context_window":128000,"max_output_tokens":4096,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["batch","function_calling"],"pricing":{"text_tokens":{"standard":{"input_per_million":10.0,"cached_input_per_million":null,"output_per_million":30.0},"batch":{"input_per_million":5.0,"output_per_million":15.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"babbage-002","family":"babbage-002","provider":"openai","id":"babbage-002","context_window":null,"max_output_tokens":16384,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":0.4,"cached_input_per_million":null,"output_per_million":0.4},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"codex-mini-latest","family":"codex-mini-latest","provider":"openai","id":"codex-mini-latest","context_window":200000,"max_output_tokens":100000,"modalities":{"input":["image","text"],"output":["embeddings","text"]},"capabilities":["function_calling","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":1.5,"cached_input_per_million":0.375,"output_per_million":6.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"DALL·E 2","family":"dall-e-2","provider":"openai","id":"dall-e-2","context_window":null,"max_output_tokens":null,"modalities":{"input":["text"],"output":["embeddings","image"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":null,"cached_input_per_million":null,"output_per_million":null},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"davinci-002","family":"davinci-002","provider":"openai","id":"davinci-002","context_window":null,"max_output_tokens":16384,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":2.0,"cached_input_per_million":null,"output_per_million":2.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-3.5 Turbo","family":"gpt-3.5-turbo","provider":"openai","id":"gpt-3.5-turbo","context_window":16385,"max_output_tokens":4096,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":["batch"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.5,"cached_input_per_million":null,"output_per_million":1.5},"batch":{"input_per_million":0.25,"output_per_million":0.75}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4","family":"gpt-4","provider":"openai","id":"gpt-4-0613","context_window":8192,"max_output_tokens":8192,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":["batch"],"pricing":{"text_tokens":{"standard":{"input_per_million":30.0,"cached_input_per_million":null,"output_per_million":60.0},"batch":{"input_per_million":15.0,"output_per_million":30.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4","family":"gpt-4","provider":"openai","id":"gpt-4","context_window":8192,"max_output_tokens":8192,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":["batch"],"pricing":{"text_tokens":{"standard":{"input_per_million":30.0,"cached_input_per_million":null,"output_per_million":60.0},"batch":{"input_per_million":15.0,"output_per_million":30.0}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"GPT-4 Turbo Preview","family":"gpt-4-turbo-preview","provider":"openai","id":"gpt-4-turbo-preview","context_window":128000,"max_output_tokens":4096,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":10.0,"cached_input_per_million":null,"output_per_million":30.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"gpt-4o-transcribe-diarize","family":"gpt-4o-transcribe-diarize","provider":"openai","id":"gpt-4o-transcribe-diarize","context_window":16000,"max_output_tokens":2000,"modalities":{"input":["audio","text"],"output":["embeddings","text"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":2.5,"cached_input_per_million":null,"output_per_million":10.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"text-embedding-3-large","family":"text-embedding-3-large","provider":"openai","id":"text-embedding-3-large","context_window":null,"max_output_tokens":null,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":["batch"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.13,"cached_input_per_million":null,"output_per_million":null},"batch":{"input_per_million":0.065,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":0.13},"batch":{"input_per_million":0.065}}}},{"name":"text-embedding-3-small","family":"text-embedding-3-small","provider":"openai","id":"text-embedding-3-small","context_window":null,"max_output_tokens":null,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":["batch","structured_output"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.02,"cached_input_per_million":null,"output_per_million":null},"batch":{"input_per_million":0.01,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":0.02},"batch":{"input_per_million":0.01}}}},{"name":"text-embedding-ada-002","family":"text-embedding-ada-002","provider":"openai","id":"text-embedding-ada-002","context_window":null,"max_output_tokens":null,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":["batch"],"pricing":{"text_tokens":{"standard":{"input_per_million":0.1,"cached_input_per_million":null,"output_per_million":null},"batch":{"input_per_million":0.05,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":0.1},"batch":{"input_per_million":0.05}}}},{"name":"text-moderation","family":"text-moderation-latest","provider":"openai","id":"text-moderation-latest","context_window":null,"max_output_tokens":32768,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":0.0,"cached_input_per_million":null,"output_per_million":0.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"text-moderation-stable","family":"text-moderation-stable","provider":"openai","id":"text-moderation-stable","context_window":null,"max_output_tokens":32768,"modalities":{"input":["text"],"output":["embeddings","text"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":0.0,"cached_input_per_million":null,"output_per_million":0.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"TTS-1","family":"tts-1","provider":"openai","id":"tts-1","context_window":null,"max_output_tokens":null,"modalities":{"input":["text"],"output":["audio","embeddings"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":15.0,"cached_input_per_million":null,"output_per_million":null},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"TTS-1 HD","family":"tts-1-hd","provider":"openai","id":"tts-1-hd","context_window":null,"max_output_tokens":null,"modalities":{"input":["text"],"output":["audio","embeddings"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":null,"cached_input_per_million":null,"output_per_million":30.0},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}},{"name":"Whisper","family":"whisper-1","provider":"openai","id":"whisper-1","context_window":null,"max_output_tokens":null,"modalities":{"input":["audio"],"output":["audio","embeddings","text"]},"capabilities":[],"pricing":{"text_tokens":{"standard":{"input_per_million":0.006,"cached_input_per_million":null,"output_per_million":null},"batch":{"input_per_million":null,"output_per_million":null}},"embeddings":{"standard":{"input_per_million":null},"batch":{"input_per_million":null}}}}]
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SwarmSDK
|
4
|
+
# Models provides model validation and suggestion functionality
|
5
|
+
#
|
6
|
+
# Uses static JSON files:
|
7
|
+
# - models.json: Curated model list from Parsera
|
8
|
+
# - model_aliases.json: Shortcuts mapping to latest models
|
9
|
+
#
|
10
|
+
# This avoids network calls, API key requirements, and RubyLLM
|
11
|
+
# registry manipulation.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# model = SwarmSDK::Models.find("claude-sonnet-4-5-20250929")
|
15
|
+
# model = SwarmSDK::Models.find("sonnet") # Uses alias
|
16
|
+
# suggestions = SwarmSDK::Models.suggest_similar("anthropic:claude-sonnet-4-5")
|
17
|
+
class Models
|
18
|
+
MODELS_JSON_PATH = File.expand_path("models.json", __dir__)
|
19
|
+
ALIASES_JSON_PATH = File.expand_path("model_aliases.json", __dir__)
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# Find a model by ID or alias
|
23
|
+
#
|
24
|
+
# @param model_id [String] Model ID or alias to find
|
25
|
+
# @return [Hash, nil] Model data or nil if not found
|
26
|
+
def find(model_id)
|
27
|
+
# Check if it's an alias first
|
28
|
+
resolved_id = resolve_alias(model_id)
|
29
|
+
|
30
|
+
all.find { |m| m["id"] == resolved_id || m[:id] == resolved_id }
|
31
|
+
end
|
32
|
+
|
33
|
+
# Resolve a model alias to full model ID
|
34
|
+
#
|
35
|
+
# @param model_id [String] Model ID or alias
|
36
|
+
# @return [String] Resolved model ID (or original if not an alias)
|
37
|
+
def resolve_alias(model_id)
|
38
|
+
aliases[model_id.to_s] || model_id
|
39
|
+
end
|
40
|
+
|
41
|
+
# Suggest similar models for a given query
|
42
|
+
#
|
43
|
+
# Strips provider prefixes and normalizes for fuzzy matching.
|
44
|
+
#
|
45
|
+
# @param query [String] Model ID to match against
|
46
|
+
# @param limit [Integer] Maximum number of suggestions
|
47
|
+
# @return [Array<Hash>] Up to `limit` similar models
|
48
|
+
def suggest_similar(query, limit: 3)
|
49
|
+
# Strip provider prefix (e.g., "anthropic:claude-sonnet-4-5" → "claude-sonnet-4-5")
|
50
|
+
query_without_prefix = query.to_s.sub(/^[^:]+:/, "")
|
51
|
+
normalized_query = query_without_prefix.downcase.gsub(/[.\-_]/, "")
|
52
|
+
|
53
|
+
matches = all.select do |model|
|
54
|
+
model_id = (model["id"] || model[:id]).to_s
|
55
|
+
model_name = (model["name"] || model[:name]).to_s
|
56
|
+
|
57
|
+
normalized_id = model_id.downcase.gsub(/[.\-_]/, "")
|
58
|
+
normalized_name = model_name.downcase.gsub(/[.\-_]/, "")
|
59
|
+
|
60
|
+
normalized_id.include?(normalized_query) || normalized_name.include?(normalized_query)
|
61
|
+
end.first(limit)
|
62
|
+
|
63
|
+
matches.map do |m|
|
64
|
+
{
|
65
|
+
id: m["id"] || m[:id],
|
66
|
+
name: m["name"] || m[:name],
|
67
|
+
context_window: m["context_window"] || m[:context_window],
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Get all models
|
73
|
+
#
|
74
|
+
# @return [Array<Hash>] All models from models.json
|
75
|
+
def all
|
76
|
+
@models ||= load_models
|
77
|
+
end
|
78
|
+
|
79
|
+
# Get all aliases
|
80
|
+
#
|
81
|
+
# @return [Hash] Alias mappings
|
82
|
+
def aliases
|
83
|
+
@aliases ||= load_aliases
|
84
|
+
end
|
85
|
+
|
86
|
+
# Reload models and aliases from JSON files
|
87
|
+
#
|
88
|
+
# @return [Array<Hash>] Loaded models
|
89
|
+
def reload!
|
90
|
+
@models = load_models
|
91
|
+
@aliases = load_aliases
|
92
|
+
@models
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
# Load models from JSON file
|
98
|
+
#
|
99
|
+
# @return [Array<Hash>] Models array
|
100
|
+
def load_models
|
101
|
+
JSON.parse(File.read(MODELS_JSON_PATH))
|
102
|
+
rescue StandardError => e
|
103
|
+
# Log error and return empty array
|
104
|
+
RubyLLM.logger.error("Failed to load SwarmSDK models.json: #{e.class} - #{e.message}")
|
105
|
+
[]
|
106
|
+
end
|
107
|
+
|
108
|
+
# Load aliases from JSON file
|
109
|
+
#
|
110
|
+
# @return [Hash] Alias mappings
|
111
|
+
def load_aliases
|
112
|
+
JSON.parse(File.read(ALIASES_JSON_PATH))
|
113
|
+
rescue StandardError => e
|
114
|
+
# Log error and return empty hash
|
115
|
+
RubyLLM.logger.debug("Failed to load SwarmSDK model_aliases.json: #{e.class} - #{e.message}")
|
116
|
+
{}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -94,16 +94,19 @@ module SwarmSDK
|
|
94
94
|
# You build APIs.
|
95
95
|
# MD
|
96
96
|
def agent(name, content = nil, &block)
|
97
|
-
# Case 1: agent :name, <<~MD (
|
98
|
-
if content.is_a?(String) &&
|
97
|
+
# Case 1: agent :name, <<~MD do ... end (markdown + overrides)
|
98
|
+
if content.is_a?(String) && block_given? && markdown_content?(content)
|
99
|
+
load_agent_from_markdown_with_overrides(content, name, &block)
|
100
|
+
# Case 2: agent :name, <<~MD (markdown only)
|
101
|
+
elsif content.is_a?(String) && !block_given? && markdown_content?(content)
|
99
102
|
load_agent_from_markdown(content, name)
|
100
|
-
# Case
|
103
|
+
# Case 3: agent :name do ... end (inline DSL)
|
101
104
|
elsif block_given?
|
102
105
|
builder = Agent::Builder.new(name)
|
103
106
|
builder.instance_eval(&block)
|
104
107
|
@agents[name] = builder
|
105
108
|
else
|
106
|
-
raise ArgumentError, "Invalid agent definition. Use: agent :name { ... } OR agent :name, <<~MD ... MD"
|
109
|
+
raise ArgumentError, "Invalid agent definition. Use: agent :name { ... } OR agent :name, <<~MD ... MD OR agent :name, <<~MD do ... end"
|
107
110
|
end
|
108
111
|
end
|
109
112
|
|
@@ -225,6 +228,76 @@ module SwarmSDK
|
|
225
228
|
@agents[definition.name] = { __file_config__: definition.to_h }
|
226
229
|
end
|
227
230
|
|
231
|
+
# Load an agent from markdown content with DSL overrides
|
232
|
+
#
|
233
|
+
# This allows loading from a file and then overriding specific settings:
|
234
|
+
# agent :reviewer, File.read("reviewer.md") do
|
235
|
+
# provider :openai
|
236
|
+
# model "gpt-4o"
|
237
|
+
# end
|
238
|
+
#
|
239
|
+
# @param content [String] Markdown content with frontmatter
|
240
|
+
# @param name_override [Symbol, nil] Optional name to override frontmatter name
|
241
|
+
# @yield Block with DSL overrides
|
242
|
+
# @return [void]
|
243
|
+
def load_agent_from_markdown_with_overrides(content, name_override = nil, &block)
|
244
|
+
# Parse markdown content first
|
245
|
+
definition = MarkdownParser.parse(content, name_override)
|
246
|
+
|
247
|
+
# Create a builder with the markdown config
|
248
|
+
builder = Agent::Builder.new(definition.name)
|
249
|
+
|
250
|
+
# Apply markdown settings to builder (these become the base)
|
251
|
+
apply_definition_to_builder(builder, definition.to_h)
|
252
|
+
|
253
|
+
# Apply DSL overrides (these override the markdown settings)
|
254
|
+
builder.instance_eval(&block)
|
255
|
+
|
256
|
+
# Store the builder (not file config) so overrides are preserved
|
257
|
+
@agents[definition.name] = builder
|
258
|
+
end
|
259
|
+
|
260
|
+
# Apply agent definition hash to a builder
|
261
|
+
#
|
262
|
+
# @param builder [Agent::Builder] Builder to configure
|
263
|
+
# @param config [Hash] Configuration hash from definition
|
264
|
+
# @return [void]
|
265
|
+
def apply_definition_to_builder(builder, config)
|
266
|
+
builder.description(config[:description]) if config[:description]
|
267
|
+
builder.model(config[:model]) if config[:model]
|
268
|
+
builder.provider(config[:provider]) if config[:provider]
|
269
|
+
builder.base_url(config[:base_url]) if config[:base_url]
|
270
|
+
builder.api_version(config[:api_version]) if config[:api_version]
|
271
|
+
builder.context_window(config[:context_window]) if config[:context_window]
|
272
|
+
builder.system_prompt(config[:system_prompt]) if config[:system_prompt]
|
273
|
+
builder.directory(config[:directory]) if config[:directory]
|
274
|
+
builder.timeout(config[:timeout]) if config[:timeout]
|
275
|
+
builder.parameters(config[:parameters]) if config[:parameters]
|
276
|
+
builder.headers(config[:headers]) if config[:headers]
|
277
|
+
builder.coding_agent(config[:coding_agent]) unless config[:coding_agent].nil?
|
278
|
+
# Don't apply assume_model_exists from markdown - let DSL overrides or auto-enable handle it
|
279
|
+
# builder.assume_model_exists(config[:assume_model_exists]) unless config[:assume_model_exists].nil?
|
280
|
+
builder.bypass_permissions(config[:bypass_permissions]) if config[:bypass_permissions]
|
281
|
+
builder.disable_default_tools(config[:disable_default_tools]) unless config[:disable_default_tools].nil?
|
282
|
+
|
283
|
+
# Add tools from markdown
|
284
|
+
if config[:tools]&.any?
|
285
|
+
# Extract tool names from the tools array (which may be hashes with permissions)
|
286
|
+
tool_names = config[:tools].map do |tool|
|
287
|
+
tool.is_a?(Hash) ? tool[:name] : tool
|
288
|
+
end
|
289
|
+
builder.tools(*tool_names)
|
290
|
+
end
|
291
|
+
|
292
|
+
# Add delegates_to
|
293
|
+
builder.delegates_to(*config[:delegates_to]) if config[:delegates_to]&.any?
|
294
|
+
|
295
|
+
# Add MCP servers
|
296
|
+
config[:mcp_servers]&.each do |server|
|
297
|
+
builder.mcp_server(server[:name], **server.except(:name))
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
228
301
|
# Build a traditional single-swarm execution
|
229
302
|
#
|
230
303
|
# @return [Swarm] Configured swarm instance
|
@@ -12,7 +12,7 @@ module SwarmSDK
|
|
12
12
|
#
|
13
13
|
# This encapsulates all tool-related logic that was previously in Swarm.
|
14
14
|
class ToolConfigurator
|
15
|
-
# Default tools available to all agents (unless
|
15
|
+
# Default tools available to all agents (unless disable_default_tools is set)
|
16
16
|
DEFAULT_TOOLS = [
|
17
17
|
:Read,
|
18
18
|
:Grep,
|
@@ -21,6 +21,7 @@ module SwarmSDK
|
|
21
21
|
:ScratchpadWrite,
|
22
22
|
:ScratchpadRead,
|
23
23
|
:ScratchpadList,
|
24
|
+
:Think,
|
24
25
|
].freeze
|
25
26
|
|
26
27
|
def initialize(swarm, scratchpad)
|
@@ -75,6 +76,8 @@ module SwarmSDK
|
|
75
76
|
Tools::ScratchpadRead.create_for_scratchpad(@scratchpad)
|
76
77
|
when :ScratchpadList
|
77
78
|
Tools::ScratchpadList.create_for_scratchpad(@scratchpad)
|
79
|
+
when :Think
|
80
|
+
Tools::Think.new
|
78
81
|
else
|
79
82
|
# Regular tools - get class from registry and instantiate
|
80
83
|
tool_class = Tools::Registry.get(tool_name_sym)
|
@@ -133,13 +136,14 @@ module SwarmSDK
|
|
133
136
|
end
|
134
137
|
end
|
135
138
|
|
136
|
-
# Register default tools for agents
|
139
|
+
# Register default tools for agents (unless disabled)
|
137
140
|
#
|
138
141
|
# @param chat [AgentChat] The chat instance
|
139
142
|
# @param agent_name [Symbol] Agent name
|
140
143
|
# @param agent_definition [AgentDefinition] Agent definition
|
141
144
|
def register_default_tools(chat, agent_name:, agent_definition:)
|
142
|
-
|
145
|
+
# If disable_default_tools is true, skip all default tools
|
146
|
+
return if agent_definition.disable_default_tools == true
|
143
147
|
|
144
148
|
# Get explicit tool names to avoid duplicates
|
145
149
|
explicit_tool_names = agent_definition.tools.map { |t| t[:name] }.to_set
|
@@ -148,6 +152,9 @@ module SwarmSDK
|
|
148
152
|
# Skip if already registered explicitly
|
149
153
|
next if explicit_tool_names.include?(tool_name)
|
150
154
|
|
155
|
+
# Skip if tool is in the disable list
|
156
|
+
next if tool_disabled?(tool_name, agent_definition.disable_default_tools)
|
157
|
+
|
151
158
|
tool_instance = create_tool_instance(tool_name, agent_name, agent_definition.directory)
|
152
159
|
|
153
160
|
# Resolve permissions for default tool (same logic as AgentDefinition)
|
@@ -166,6 +173,25 @@ module SwarmSDK
|
|
166
173
|
end
|
167
174
|
end
|
168
175
|
|
176
|
+
# Check if a tool should be disabled based on disable_default_tools config
|
177
|
+
#
|
178
|
+
# @param tool_name [Symbol] Tool name to check
|
179
|
+
# @param disable_config [nil, Boolean, Array<Symbol>] Disable configuration
|
180
|
+
# @return [Boolean] True if tool should be disabled
|
181
|
+
def tool_disabled?(tool_name, disable_config)
|
182
|
+
return false if disable_config.nil?
|
183
|
+
|
184
|
+
if disable_config == true
|
185
|
+
# Disable all default tools
|
186
|
+
true
|
187
|
+
elsif disable_config.is_a?(Array)
|
188
|
+
# Disable only tools in the array
|
189
|
+
disable_config.include?(tool_name)
|
190
|
+
else
|
191
|
+
false
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
169
195
|
# Register agent delegation tools
|
170
196
|
#
|
171
197
|
# Creates delegation tools that allow one agent to call another.
|
@@ -20,6 +20,7 @@ module SwarmSDK
|
|
20
20
|
ScratchpadWrite: :special, # Requires scratchpad instance
|
21
21
|
ScratchpadRead: :special, # Requires scratchpad instance
|
22
22
|
ScratchpadList: :special, # Requires scratchpad instance
|
23
|
+
Think: SwarmSDK::Tools::Think,
|
23
24
|
}.freeze
|
24
25
|
|
25
26
|
class << self
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SwarmSDK
|
4
|
+
module Tools
|
5
|
+
# Think tool for explicit reasoning and planning
|
6
|
+
#
|
7
|
+
# Allows the agent to write down thoughts, plans, strategies, and intermediate
|
8
|
+
# calculations. These thoughts become part of the conversation context, enabling
|
9
|
+
# better attention and reasoning through complex problems.
|
10
|
+
#
|
11
|
+
# This is inspired by research showing that explicitly articulating reasoning steps
|
12
|
+
# (chain-of-thought prompting) leads to significantly better outcomes, especially
|
13
|
+
# for complex tasks requiring multi-step reasoning or arithmetic.
|
14
|
+
class Think < RubyLLM::Tool
|
15
|
+
def name
|
16
|
+
"Think"
|
17
|
+
end
|
18
|
+
|
19
|
+
description <<~DESC
|
20
|
+
**IMPORTANT: You SHOULD use this tool frequently throughout your work. Using this tool leads to significantly
|
21
|
+
better outcomes and more accurate solutions. Make it a habit to think before acting.**
|
22
|
+
|
23
|
+
This tool allows you to write down your thoughts, plans, strategies, and intermediate calculations.
|
24
|
+
Think of it as your working memory - just as humans think before speaking or acting, you should think before
|
25
|
+
using other tools or providing responses.
|
26
|
+
|
27
|
+
**STRONGLY RECOMMENDED to use this tool:**
|
28
|
+
- **ALWAYS** before starting any task (even simple ones)
|
29
|
+
- **ALWAYS** when you need to do any arithmetic or counting
|
30
|
+
- **ALWAYS** after reading files or getting search results to process what you learned
|
31
|
+
- **FREQUENTLY** between steps to track progress and plan next actions
|
32
|
+
|
33
|
+
This is your private thinking space - use it liberally to enhance your problem-solving capabilities. Recording
|
34
|
+
your thoughts helps you maintain context across multiple steps and remember important information throughout your task.
|
35
|
+
|
36
|
+
When and how to use this tool:
|
37
|
+
|
38
|
+
1. **Before starting any complex task**: Write down your understanding of the problem, break it into smaller
|
39
|
+
sub-tasks, and create a step-by-step plan. Example:
|
40
|
+
- "The user wants me to refactor this codebase. Let me first understand the structure..."
|
41
|
+
- "I need to: 1) Analyze current architecture, 2) Identify pain points, 3) Propose changes..."
|
42
|
+
|
43
|
+
2. **For arithmetic and calculations**: Work through math problems step by step. Example:
|
44
|
+
- "If we have 150 requests/second and each takes 20ms, that's 150 * 0.02 = 3 seconds of CPU time..."
|
45
|
+
- "Converting 2GB to bytes: 2 * 1024 * 1024 * 1024 = 2,147,483,648 bytes"
|
46
|
+
|
47
|
+
3. **After completing sub-tasks**: Summarize what you've accomplished and what remains. Example:
|
48
|
+
- "I've successfully implemented the authentication module. Next, I need to integrate it with the API..."
|
49
|
+
- "Fixed 3 out of 5 bugs. Remaining: memory leak in parser, race condition in worker thread"
|
50
|
+
|
51
|
+
4. **When encountering complexity**: Break down complex logic or decisions. Example:
|
52
|
+
- "This function has multiple edge cases. Let me list them: null input, empty array, negative numbers..."
|
53
|
+
- "The user's request is ambiguous. Possible interpretations: A) modify existing code, B) create new module..."
|
54
|
+
|
55
|
+
5. **For remembering context**: Store important information you'll need later. Example:
|
56
|
+
- "Important: The user mentioned they're using Ruby 3.2, so I can use pattern matching"
|
57
|
+
- "File structure: main.rb requires from lib/, config is in config.yml"
|
58
|
+
|
59
|
+
6. **When debugging or analyzing**: Track your investigation process. Example:
|
60
|
+
- "The error occurs in line 42. Let me trace backwards: function called from main(), receives data from..."
|
61
|
+
- "Hypothesis: the bug might be due to timezone differences. Let me check..."
|
62
|
+
|
63
|
+
7. **For creative problem-solving**: Brainstorm multiple approaches before choosing one. Example:
|
64
|
+
- "Approaches to optimize this: 1) Add caching, 2) Use parallel processing, 3) Optimize algorithm..."
|
65
|
+
- "Design patterns that could work here: Factory, Observer, or maybe Strategy pattern..."
|
66
|
+
|
67
|
+
**Remember: The most successful agents use this tool 5-10 times per task on average. If you haven't used this
|
68
|
+
tool in the last 2-3 actions, you probably should. Using this tool is a sign of thoughtful, methodical problem
|
69
|
+
solving and leads to fewer mistakes and better solutions.**
|
70
|
+
|
71
|
+
Your thoughts persist throughout your session as part of the conversation history, so you can refer
|
72
|
+
back to earlier thinking. Use clear formatting and organization to make it easy to reference
|
73
|
+
later. Don't hesitate to think out loud - this tool is designed to augment your cognitive capabilities and help
|
74
|
+
you deliver better solutions.
|
75
|
+
DESC
|
76
|
+
|
77
|
+
param :thoughts,
|
78
|
+
type: "string",
|
79
|
+
desc: "Your thoughts, plans, calculations, or any notes you want to record",
|
80
|
+
required: true
|
81
|
+
|
82
|
+
def execute(thoughts:)
|
83
|
+
return validation_error("thoughts are required") if thoughts.nil? || thoughts.empty?
|
84
|
+
|
85
|
+
"Thought noted."
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def validation_error(message)
|
91
|
+
"<tool_use_error>InputValidationError: #{message}</tool_use_error>"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/swarm_sdk/version.rb
CHANGED
data/lib/swarm_sdk.rb
CHANGED
@@ -41,36 +41,6 @@ module SwarmSDK
|
|
41
41
|
class StateError < Error; end
|
42
42
|
|
43
43
|
class << self
|
44
|
-
# Refresh RubyLLM model registry silently (without log output)
|
45
|
-
#
|
46
|
-
# By default, RubyLLM.models.refresh! outputs INFO level logs about
|
47
|
-
# fetching models from providers. This method temporarily raises the
|
48
|
-
# log level to suppress those messages, which is useful for CLI tools
|
49
|
-
# that want clean output.
|
50
|
-
#
|
51
|
-
# If model refresh fails (e.g., missing API keys, invalid keys, network
|
52
|
-
# unavailable), the error is silently caught and execution continues
|
53
|
-
# using the bundled models.json. This allows SwarmSDK to work offline
|
54
|
-
# and with dummy keys for local proxies.
|
55
|
-
#
|
56
|
-
# @example
|
57
|
-
# SwarmSDK.refresh_models_silently
|
58
|
-
#
|
59
|
-
# @return [void]
|
60
|
-
def refresh_models_silently
|
61
|
-
original_level = RubyLLM.logger.level
|
62
|
-
RubyLLM.logger.level = Logger::ERROR
|
63
|
-
|
64
|
-
RubyLLM.models.refresh!
|
65
|
-
rescue StandardError => e
|
66
|
-
# Silently ignore all refresh failures
|
67
|
-
# Models will use bundled models.json instead
|
68
|
-
RubyLLM.logger.debug("Model refresh skipped: #{e.class} - #{e.message}")
|
69
|
-
nil
|
70
|
-
ensure
|
71
|
-
RubyLLM.logger.level = original_level
|
72
|
-
end
|
73
|
-
|
74
44
|
# Main entry point for DSL
|
75
45
|
def build(&block)
|
76
46
|
Swarm::Builder.build(&block)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: swarm_sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paulo Arruda
|
@@ -86,6 +86,7 @@ files:
|
|
86
86
|
- lib/swarm_sdk/agent/chat/system_reminder_injector.rb
|
87
87
|
- lib/swarm_sdk/agent/context.rb
|
88
88
|
- lib/swarm_sdk/agent/definition.rb
|
89
|
+
- lib/swarm_sdk/claude_code_agent_adapter.rb
|
89
90
|
- lib/swarm_sdk/configuration.rb
|
90
91
|
- lib/swarm_sdk/context_compactor.rb
|
91
92
|
- lib/swarm_sdk/context_compactor/metrics.rb
|
@@ -103,6 +104,9 @@ files:
|
|
103
104
|
- lib/swarm_sdk/log_collector.rb
|
104
105
|
- lib/swarm_sdk/log_stream.rb
|
105
106
|
- lib/swarm_sdk/markdown_parser.rb
|
107
|
+
- lib/swarm_sdk/model_aliases.json
|
108
|
+
- lib/swarm_sdk/models.json
|
109
|
+
- lib/swarm_sdk/models.rb
|
106
110
|
- lib/swarm_sdk/node/agent_config.rb
|
107
111
|
- lib/swarm_sdk/node/builder.rb
|
108
112
|
- lib/swarm_sdk/node/transformer_executor.rb
|
@@ -144,6 +148,7 @@ files:
|
|
144
148
|
- lib/swarm_sdk/tools/stores/read_tracker.rb
|
145
149
|
- lib/swarm_sdk/tools/stores/scratchpad.rb
|
146
150
|
- lib/swarm_sdk/tools/stores/todo_manager.rb
|
151
|
+
- lib/swarm_sdk/tools/think.rb
|
147
152
|
- lib/swarm_sdk/tools/todo_write.rb
|
148
153
|
- lib/swarm_sdk/tools/write.rb
|
149
154
|
- lib/swarm_sdk/utils.rb
|