swarm_sdk 2.0.0 → 2.0.2
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 +7 -2
- data/lib/swarm_sdk/agent/definition.rb +57 -34
- data/lib/swarm_sdk/claude_code_agent_adapter.rb +205 -0
- data/lib/swarm_sdk/configuration.rb +4 -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/version.rb +1 -1
- data/lib/swarm_sdk.rb +16 -30
- metadata +5 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2cedd1197d0fea1e52b40531ecb6d6f030524436c3164c109bce37da1db883b
|
4
|
+
data.tar.gz: ac77a97bd54374a9049de888e0dfc4a1b1c99d5a4fdc2c01fabe20e54a7cd83b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d1dfdd64d1247e15a3fea9ce56b0d28657e85a5f9d706d1a2ce9c72700226cf443dcdc691596710d2e948816a3fb5d93fe1520f3bc798df78a2b6a91e9a2085
|
7
|
+
data.tar.gz: b54ae2918f3cb04eb2a49a80faecc2aac1a878cd14828cc3287705eae22ba4da09780c8e38a54c5d048cf4eb1fec75e523dc609192758f6ad60294467d68df01
|
@@ -163,13 +163,14 @@ module SwarmSDK
|
|
163
163
|
@description = text
|
164
164
|
end
|
165
165
|
|
166
|
-
#
|
166
|
+
# Set or add tools
|
167
167
|
#
|
168
168
|
# Uses Set internally to automatically deduplicate tool names across multiple calls.
|
169
169
|
# This allows calling tools() multiple times without worrying about duplicates.
|
170
170
|
#
|
171
171
|
# @param tool_names [Array<Symbol>] Tool names to add
|
172
172
|
# @param include_default [Boolean] Whether to include default tools (Read, Grep, etc.)
|
173
|
+
# @param replace [Boolean] If true, replaces existing tools instead of merging (default: false)
|
173
174
|
#
|
174
175
|
# @example Basic usage with defaults
|
175
176
|
# tools :Grep, :Read # include_default: true is implicit
|
@@ -181,7 +182,11 @@ module SwarmSDK
|
|
181
182
|
# tools :Read
|
182
183
|
# tools :Write, :Edit # @tools now contains Set[:Read, :Write, :Edit]
|
183
184
|
# tools :Read # Still Set[:Read, :Write, :Edit] - no duplicate
|
184
|
-
|
185
|
+
#
|
186
|
+
# @example Replace tools (for markdown overrides)
|
187
|
+
# tools :Read, :Write, replace: true # Replaces all existing tools
|
188
|
+
def tools(*tool_names, include_default: true, replace: false)
|
189
|
+
@tools = Set.new if replace
|
185
190
|
@tools.merge(tool_names.map(&:to_sym))
|
186
191
|
@include_default_tools = include_default
|
187
192
|
end
|
@@ -73,12 +73,9 @@ 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
80
|
# include_default_tools defaults to true if not specified
|
84
81
|
@include_default_tools = config.key?(:include_default_tools) ? config[:include_default_tools] : true
|
@@ -168,45 +165,53 @@ module SwarmSDK
|
|
168
165
|
|
169
166
|
private
|
170
167
|
|
171
|
-
# Validate that model exists in
|
168
|
+
# Validate that model exists in SwarmSDK's model registry
|
169
|
+
#
|
170
|
+
# Uses SwarmSDK's static models.json instead of RubyLLM's dynamic registry.
|
171
|
+
# This provides stable, offline model validation without network calls.
|
172
|
+
#
|
173
|
+
# Process:
|
174
|
+
# 1. Try to find model directly in models.json
|
175
|
+
# 2. If not found, try to resolve as alias and find again
|
176
|
+
# 3. If still not found, return warning with suggestions
|
172
177
|
#
|
173
178
|
# @return [Hash, nil] Warning hash if model not found, nil otherwise
|
174
179
|
def validate_model
|
175
|
-
# Try
|
176
|
-
|
177
|
-
|
180
|
+
# Try direct lookup first
|
181
|
+
model_data = SwarmSDK::Models.all.find { |m| (m["id"] || m[:id]) == @model }
|
182
|
+
|
183
|
+
# If not found, try alias resolution
|
184
|
+
unless model_data
|
185
|
+
resolved_id = SwarmSDK::Models.resolve_alias(@model)
|
186
|
+
# Only search again if alias was different
|
187
|
+
if resolved_id != @model
|
188
|
+
model_data = SwarmSDK::Models.all.find { |m| (m["id"] || m[:id]) == resolved_id }
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
if model_data
|
193
|
+
nil # Model exists (either directly or via alias)
|
194
|
+
else
|
195
|
+
# Model not found - return warning with suggestions
|
196
|
+
{
|
197
|
+
type: :model_not_found,
|
198
|
+
agent: @name,
|
199
|
+
model: @model,
|
200
|
+
error_message: "Unknown model: #{@model}",
|
201
|
+
suggestions: SwarmSDK::Models.suggest_similar(@model),
|
202
|
+
}
|
203
|
+
end
|
178
204
|
rescue StandardError => e
|
179
|
-
#
|
205
|
+
# Return warning on error
|
180
206
|
{
|
181
207
|
type: :model_not_found,
|
182
208
|
agent: @name,
|
183
209
|
model: @model,
|
184
210
|
error_message: e.message,
|
185
|
-
suggestions:
|
211
|
+
suggestions: [],
|
186
212
|
}
|
187
213
|
end
|
188
214
|
|
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
215
|
def build_full_system_prompt(custom_prompt)
|
211
216
|
# If coding_agent is false (default), return custom prompt with optional TODO/Scratchpad info
|
212
217
|
# If coding_agent is true, include full base prompt for coding tasks
|
@@ -253,9 +258,27 @@ module SwarmSDK
|
|
253
258
|
|
254
259
|
def render_non_coding_base_prompt
|
255
260
|
# Simplified base prompt for non-coding agents
|
256
|
-
#
|
261
|
+
# Includes environment info, TODO, and Scratchpad tool information
|
257
262
|
# Does not steer towards coding tasks
|
263
|
+
cwd = @directory || Dir.pwd
|
264
|
+
platform = RUBY_PLATFORM
|
265
|
+
os_version = begin
|
266
|
+
%x(uname -sr 2>/dev/null).strip
|
267
|
+
rescue
|
268
|
+
RUBY_PLATFORM
|
269
|
+
end
|
270
|
+
date = Time.now.strftime("%Y-%m-%d")
|
271
|
+
|
258
272
|
<<~PROMPT.strip
|
273
|
+
# Environment
|
274
|
+
|
275
|
+
<env>
|
276
|
+
Working directory: #{cwd}
|
277
|
+
Platform: #{platform}
|
278
|
+
OS Version: #{os_version}
|
279
|
+
Today's date: #{date}
|
280
|
+
</env>
|
281
|
+
|
259
282
|
# Task Management
|
260
283
|
|
261
284
|
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
|
@@ -238,8 +238,10 @@ module SwarmSDK
|
|
238
238
|
# Parse markdown and merge with YAML config
|
239
239
|
agent_def_from_file = MarkdownParser.parse(content, name)
|
240
240
|
|
241
|
-
# Merge:
|
242
|
-
|
241
|
+
# Merge: YAML config overrides markdown file (YAML takes precedence)
|
242
|
+
# This allows YAML to override any settings from the markdown file
|
243
|
+
final_config = agent_def_from_file.to_h.compact.merge(merged_config.compact)
|
244
|
+
|
243
245
|
Agent::Definition.new(name, final_config)
|
244
246
|
rescue StandardError => e
|
245
247
|
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.include_default_tools(config[:include_default_tools]) unless config[:include_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
|
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)
|
@@ -124,3 +94,19 @@ RubyLLM.configure do |config|
|
|
124
94
|
config.gpustack_api_base ||= ENV["GPUSTACK_API_BASE"]
|
125
95
|
config.gpustack_api_key ||= ENV["GPUSTACK_API_KEY"]
|
126
96
|
end
|
97
|
+
|
98
|
+
# monkey patch ruby_llm/mcp to add `id` when sending "notifications/initialized" message
|
99
|
+
# https://github.com/patvice/ruby_llm-mcp/issues/65
|
100
|
+
require "ruby_llm/mcp/notifications/initialize"
|
101
|
+
|
102
|
+
module RubyLLM
|
103
|
+
module MCP
|
104
|
+
module Notifications
|
105
|
+
class Initialize
|
106
|
+
def call
|
107
|
+
@coordinator.request(notification_body, add_id: true, wait_for_response: false)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
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.2
|
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
|