ruby_llm_swarm 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +175 -0
  4. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +187 -0
  5. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +39 -0
  6. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +24 -0
  7. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +14 -0
  8. data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +12 -0
  9. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +16 -0
  10. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +29 -0
  11. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +16 -0
  12. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt +11 -0
  13. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +23 -0
  14. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_content.html.erb.tt +1 -0
  15. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +21 -0
  16. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt +13 -0
  17. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +7 -0
  18. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +9 -0
  19. data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +16 -0
  20. data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +28 -0
  21. data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +18 -0
  22. data/lib/generators/ruby_llm/generator_helpers.rb +194 -0
  23. data/lib/generators/ruby_llm/install/install_generator.rb +106 -0
  24. data/lib/generators/ruby_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +9 -0
  25. data/lib/generators/ruby_llm/install/templates/chat_model.rb.tt +3 -0
  26. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +7 -0
  27. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +16 -0
  28. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +45 -0
  29. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +20 -0
  30. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +12 -0
  31. data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +4 -0
  32. data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +3 -0
  33. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +3 -0
  34. data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +145 -0
  35. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +124 -0
  36. data/lib/generators/ruby_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +15 -0
  37. data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +49 -0
  38. data/lib/ruby_llm/active_record/acts_as.rb +174 -0
  39. data/lib/ruby_llm/active_record/acts_as_legacy.rb +384 -0
  40. data/lib/ruby_llm/active_record/chat_methods.rb +350 -0
  41. data/lib/ruby_llm/active_record/message_methods.rb +81 -0
  42. data/lib/ruby_llm/active_record/model_methods.rb +84 -0
  43. data/lib/ruby_llm/aliases.json +295 -0
  44. data/lib/ruby_llm/aliases.rb +38 -0
  45. data/lib/ruby_llm/attachment.rb +220 -0
  46. data/lib/ruby_llm/chat.rb +816 -0
  47. data/lib/ruby_llm/chunk.rb +6 -0
  48. data/lib/ruby_llm/configuration.rb +78 -0
  49. data/lib/ruby_llm/connection.rb +126 -0
  50. data/lib/ruby_llm/content.rb +73 -0
  51. data/lib/ruby_llm/context.rb +29 -0
  52. data/lib/ruby_llm/embedding.rb +29 -0
  53. data/lib/ruby_llm/error.rb +84 -0
  54. data/lib/ruby_llm/image.rb +49 -0
  55. data/lib/ruby_llm/message.rb +86 -0
  56. data/lib/ruby_llm/mime_type.rb +71 -0
  57. data/lib/ruby_llm/model/info.rb +111 -0
  58. data/lib/ruby_llm/model/modalities.rb +22 -0
  59. data/lib/ruby_llm/model/pricing.rb +48 -0
  60. data/lib/ruby_llm/model/pricing_category.rb +46 -0
  61. data/lib/ruby_llm/model/pricing_tier.rb +33 -0
  62. data/lib/ruby_llm/model.rb +7 -0
  63. data/lib/ruby_llm/models.json +33198 -0
  64. data/lib/ruby_llm/models.rb +231 -0
  65. data/lib/ruby_llm/models_schema.json +168 -0
  66. data/lib/ruby_llm/moderation.rb +56 -0
  67. data/lib/ruby_llm/provider.rb +243 -0
  68. data/lib/ruby_llm/providers/anthropic/capabilities.rb +134 -0
  69. data/lib/ruby_llm/providers/anthropic/chat.rb +125 -0
  70. data/lib/ruby_llm/providers/anthropic/content.rb +44 -0
  71. data/lib/ruby_llm/providers/anthropic/embeddings.rb +20 -0
  72. data/lib/ruby_llm/providers/anthropic/media.rb +92 -0
  73. data/lib/ruby_llm/providers/anthropic/models.rb +63 -0
  74. data/lib/ruby_llm/providers/anthropic/streaming.rb +45 -0
  75. data/lib/ruby_llm/providers/anthropic/tools.rb +109 -0
  76. data/lib/ruby_llm/providers/anthropic.rb +36 -0
  77. data/lib/ruby_llm/providers/bedrock/capabilities.rb +167 -0
  78. data/lib/ruby_llm/providers/bedrock/chat.rb +63 -0
  79. data/lib/ruby_llm/providers/bedrock/media.rb +61 -0
  80. data/lib/ruby_llm/providers/bedrock/models.rb +98 -0
  81. data/lib/ruby_llm/providers/bedrock/signing.rb +831 -0
  82. data/lib/ruby_llm/providers/bedrock/streaming/base.rb +51 -0
  83. data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +71 -0
  84. data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +67 -0
  85. data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +80 -0
  86. data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +78 -0
  87. data/lib/ruby_llm/providers/bedrock/streaming.rb +18 -0
  88. data/lib/ruby_llm/providers/bedrock.rb +82 -0
  89. data/lib/ruby_llm/providers/deepseek/capabilities.rb +130 -0
  90. data/lib/ruby_llm/providers/deepseek/chat.rb +16 -0
  91. data/lib/ruby_llm/providers/deepseek.rb +30 -0
  92. data/lib/ruby_llm/providers/gemini/capabilities.rb +281 -0
  93. data/lib/ruby_llm/providers/gemini/chat.rb +454 -0
  94. data/lib/ruby_llm/providers/gemini/embeddings.rb +37 -0
  95. data/lib/ruby_llm/providers/gemini/images.rb +47 -0
  96. data/lib/ruby_llm/providers/gemini/media.rb +112 -0
  97. data/lib/ruby_llm/providers/gemini/models.rb +40 -0
  98. data/lib/ruby_llm/providers/gemini/streaming.rb +61 -0
  99. data/lib/ruby_llm/providers/gemini/tools.rb +198 -0
  100. data/lib/ruby_llm/providers/gemini/transcription.rb +116 -0
  101. data/lib/ruby_llm/providers/gemini.rb +37 -0
  102. data/lib/ruby_llm/providers/gpustack/chat.rb +27 -0
  103. data/lib/ruby_llm/providers/gpustack/media.rb +46 -0
  104. data/lib/ruby_llm/providers/gpustack/models.rb +90 -0
  105. data/lib/ruby_llm/providers/gpustack.rb +34 -0
  106. data/lib/ruby_llm/providers/mistral/capabilities.rb +155 -0
  107. data/lib/ruby_llm/providers/mistral/chat.rb +24 -0
  108. data/lib/ruby_llm/providers/mistral/embeddings.rb +33 -0
  109. data/lib/ruby_llm/providers/mistral/models.rb +48 -0
  110. data/lib/ruby_llm/providers/mistral.rb +32 -0
  111. data/lib/ruby_llm/providers/ollama/chat.rb +27 -0
  112. data/lib/ruby_llm/providers/ollama/media.rb +46 -0
  113. data/lib/ruby_llm/providers/ollama/models.rb +36 -0
  114. data/lib/ruby_llm/providers/ollama.rb +30 -0
  115. data/lib/ruby_llm/providers/openai/capabilities.rb +299 -0
  116. data/lib/ruby_llm/providers/openai/chat.rb +88 -0
  117. data/lib/ruby_llm/providers/openai/embeddings.rb +33 -0
  118. data/lib/ruby_llm/providers/openai/images.rb +38 -0
  119. data/lib/ruby_llm/providers/openai/media.rb +81 -0
  120. data/lib/ruby_llm/providers/openai/models.rb +39 -0
  121. data/lib/ruby_llm/providers/openai/moderation.rb +34 -0
  122. data/lib/ruby_llm/providers/openai/streaming.rb +46 -0
  123. data/lib/ruby_llm/providers/openai/tools.rb +98 -0
  124. data/lib/ruby_llm/providers/openai/transcription.rb +70 -0
  125. data/lib/ruby_llm/providers/openai.rb +44 -0
  126. data/lib/ruby_llm/providers/openai_responses.rb +395 -0
  127. data/lib/ruby_llm/providers/openrouter/models.rb +73 -0
  128. data/lib/ruby_llm/providers/openrouter.rb +26 -0
  129. data/lib/ruby_llm/providers/perplexity/capabilities.rb +137 -0
  130. data/lib/ruby_llm/providers/perplexity/chat.rb +16 -0
  131. data/lib/ruby_llm/providers/perplexity/models.rb +42 -0
  132. data/lib/ruby_llm/providers/perplexity.rb +48 -0
  133. data/lib/ruby_llm/providers/vertexai/chat.rb +14 -0
  134. data/lib/ruby_llm/providers/vertexai/embeddings.rb +32 -0
  135. data/lib/ruby_llm/providers/vertexai/models.rb +130 -0
  136. data/lib/ruby_llm/providers/vertexai/streaming.rb +14 -0
  137. data/lib/ruby_llm/providers/vertexai/transcription.rb +16 -0
  138. data/lib/ruby_llm/providers/vertexai.rb +55 -0
  139. data/lib/ruby_llm/railtie.rb +35 -0
  140. data/lib/ruby_llm/responses_session.rb +77 -0
  141. data/lib/ruby_llm/stream_accumulator.rb +101 -0
  142. data/lib/ruby_llm/streaming.rb +153 -0
  143. data/lib/ruby_llm/tool.rb +209 -0
  144. data/lib/ruby_llm/tool_call.rb +22 -0
  145. data/lib/ruby_llm/tool_executors.rb +125 -0
  146. data/lib/ruby_llm/transcription.rb +35 -0
  147. data/lib/ruby_llm/utils.rb +91 -0
  148. data/lib/ruby_llm/version.rb +5 -0
  149. data/lib/ruby_llm.rb +140 -0
  150. data/lib/tasks/models.rake +525 -0
  151. data/lib/tasks/release.rake +67 -0
  152. data/lib/tasks/ruby_llm.rake +15 -0
  153. data/lib/tasks/vcr.rake +92 -0
  154. metadata +346 -0
@@ -0,0 +1,295 @@
1
+ {
2
+ "chatgpt-4o": {
3
+ "openai": "chatgpt-4o-latest",
4
+ "openrouter": "openai/chatgpt-4o-latest"
5
+ },
6
+ "claude-3-5-haiku": {
7
+ "anthropic": "claude-3-5-haiku-20241022",
8
+ "openrouter": "anthropic/claude-3.5-haiku",
9
+ "bedrock": "anthropic.claude-3-5-haiku-20241022-v1:0"
10
+ },
11
+ "claude-3-5-haiku-latest": {
12
+ "anthropic": "claude-3-5-haiku-latest"
13
+ },
14
+ "claude-3-7-sonnet": {
15
+ "anthropic": "claude-3-7-sonnet-20250219",
16
+ "openrouter": "anthropic/claude-3.7-sonnet",
17
+ "bedrock": "us.anthropic.claude-3-7-sonnet-20250219-v1:0"
18
+ },
19
+ "claude-3-7-sonnet-latest": {
20
+ "anthropic": "claude-3-7-sonnet-latest"
21
+ },
22
+ "claude-3-haiku": {
23
+ "anthropic": "claude-3-haiku-20240307",
24
+ "openrouter": "anthropic/claude-3-haiku",
25
+ "bedrock": "anthropic.claude-3-haiku-20240307-v1:0:200k"
26
+ },
27
+ "claude-3-opus": {
28
+ "anthropic": "claude-3-opus-20240229",
29
+ "openrouter": "anthropic/claude-3-opus",
30
+ "bedrock": "anthropic.claude-3-opus-20240229-v1:0:200k"
31
+ },
32
+ "claude-3-sonnet": {
33
+ "bedrock": "anthropic.claude-3-sonnet-20240229-v1:0"
34
+ },
35
+ "claude-haiku-4-5": {
36
+ "anthropic": "claude-haiku-4-5-20251001",
37
+ "openrouter": "anthropic/claude-haiku-4.5",
38
+ "bedrock": "us.anthropic.claude-haiku-4-5-20251001-v1:0"
39
+ },
40
+ "claude-opus-4": {
41
+ "anthropic": "claude-opus-4-20250514",
42
+ "openrouter": "anthropic/claude-opus-4",
43
+ "bedrock": "us.anthropic.claude-opus-4-1-20250805-v1:0"
44
+ },
45
+ "claude-opus-4-0": {
46
+ "anthropic": "claude-opus-4-0"
47
+ },
48
+ "claude-opus-4-1": {
49
+ "anthropic": "claude-opus-4-1-20250805",
50
+ "openrouter": "anthropic/claude-opus-4.1",
51
+ "bedrock": "us.anthropic.claude-opus-4-1-20250805-v1:0"
52
+ },
53
+ "claude-sonnet-4": {
54
+ "anthropic": "claude-sonnet-4-20250514",
55
+ "openrouter": "anthropic/claude-sonnet-4",
56
+ "bedrock": "us.anthropic.claude-sonnet-4-20250514-v1:0"
57
+ },
58
+ "claude-sonnet-4-0": {
59
+ "anthropic": "claude-sonnet-4-0"
60
+ },
61
+ "claude-sonnet-4-5": {
62
+ "anthropic": "claude-sonnet-4-5-20250929",
63
+ "openrouter": "anthropic/claude-sonnet-4.5",
64
+ "bedrock": "us.anthropic.claude-sonnet-4-5-20250929-v1:0"
65
+ },
66
+ "deepseek-chat": {
67
+ "deepseek": "deepseek-chat",
68
+ "openrouter": "deepseek/deepseek-chat"
69
+ },
70
+ "gemini-2.0-flash": {
71
+ "gemini": "gemini-2.0-flash",
72
+ "vertexai": "gemini-2.0-flash"
73
+ },
74
+ "gemini-2.0-flash-001": {
75
+ "gemini": "gemini-2.0-flash-001",
76
+ "openrouter": "google/gemini-2.0-flash-001",
77
+ "vertexai": "gemini-2.0-flash-001"
78
+ },
79
+ "gemini-2.0-flash-exp": {
80
+ "gemini": "gemini-2.0-flash-exp",
81
+ "vertexai": "gemini-2.0-flash-exp"
82
+ },
83
+ "gemini-2.0-flash-lite-001": {
84
+ "gemini": "gemini-2.0-flash-lite-001",
85
+ "openrouter": "google/gemini-2.0-flash-lite-001",
86
+ "vertexai": "gemini-2.0-flash-lite-001"
87
+ },
88
+ "gemini-2.5-flash": {
89
+ "gemini": "gemini-2.5-flash",
90
+ "openrouter": "google/gemini-2.5-flash",
91
+ "vertexai": "gemini-2.5-flash"
92
+ },
93
+ "gemini-2.5-flash-image": {
94
+ "gemini": "gemini-2.5-flash-image",
95
+ "openrouter": "google/gemini-2.5-flash-image"
96
+ },
97
+ "gemini-2.5-flash-image-preview": {
98
+ "gemini": "gemini-2.5-flash-image-preview",
99
+ "openrouter": "google/gemini-2.5-flash-image-preview"
100
+ },
101
+ "gemini-2.5-flash-lite": {
102
+ "gemini": "gemini-2.5-flash-lite",
103
+ "openrouter": "google/gemini-2.5-flash-lite",
104
+ "vertexai": "gemini-2.5-flash-lite"
105
+ },
106
+ "gemini-2.5-flash-lite-preview-06-17": {
107
+ "gemini": "gemini-2.5-flash-lite-preview-06-17",
108
+ "openrouter": "google/gemini-2.5-flash-lite-preview-06-17"
109
+ },
110
+ "gemini-2.5-flash-lite-preview-09-2025": {
111
+ "gemini": "gemini-2.5-flash-lite-preview-09-2025",
112
+ "openrouter": "google/gemini-2.5-flash-lite-preview-09-2025"
113
+ },
114
+ "gemini-2.5-flash-preview-09-2025": {
115
+ "gemini": "gemini-2.5-flash-preview-09-2025",
116
+ "openrouter": "google/gemini-2.5-flash-preview-09-2025"
117
+ },
118
+ "gemini-2.5-pro": {
119
+ "gemini": "gemini-2.5-pro",
120
+ "openrouter": "google/gemini-2.5-pro",
121
+ "vertexai": "gemini-2.5-pro"
122
+ },
123
+ "gemini-2.5-pro-preview-05-06": {
124
+ "gemini": "gemini-2.5-pro-preview-05-06",
125
+ "openrouter": "google/gemini-2.5-pro-preview-05-06"
126
+ },
127
+ "gemini-embedding-001": {
128
+ "gemini": "gemini-embedding-001",
129
+ "vertexai": "gemini-embedding-001"
130
+ },
131
+ "gemini-exp-1206": {
132
+ "gemini": "gemini-exp-1206",
133
+ "vertexai": "gemini-exp-1206"
134
+ },
135
+ "gemma-3-12b-it": {
136
+ "gemini": "gemma-3-12b-it",
137
+ "openrouter": "google/gemma-3-12b-it"
138
+ },
139
+ "gemma-3-27b-it": {
140
+ "gemini": "gemma-3-27b-it",
141
+ "openrouter": "google/gemma-3-27b-it"
142
+ },
143
+ "gemma-3-4b-it": {
144
+ "gemini": "gemma-3-4b-it",
145
+ "openrouter": "google/gemma-3-4b-it"
146
+ },
147
+ "gemma-3n-e4b-it": {
148
+ "gemini": "gemma-3n-e4b-it",
149
+ "openrouter": "google/gemma-3n-e4b-it"
150
+ },
151
+ "gpt-3.5-turbo": {
152
+ "openai": "gpt-3.5-turbo",
153
+ "openrouter": "openai/gpt-3.5-turbo"
154
+ },
155
+ "gpt-3.5-turbo-16k": {
156
+ "openai": "gpt-3.5-turbo-16k",
157
+ "openrouter": "openai/gpt-3.5-turbo-16k"
158
+ },
159
+ "gpt-3.5-turbo-instruct": {
160
+ "openai": "gpt-3.5-turbo-instruct",
161
+ "openrouter": "openai/gpt-3.5-turbo-instruct"
162
+ },
163
+ "gpt-4": {
164
+ "openai": "gpt-4",
165
+ "openrouter": "openai/gpt-4"
166
+ },
167
+ "gpt-4-1106-preview": {
168
+ "openai": "gpt-4-1106-preview",
169
+ "openrouter": "openai/gpt-4-1106-preview"
170
+ },
171
+ "gpt-4-turbo": {
172
+ "openai": "gpt-4-turbo",
173
+ "openrouter": "openai/gpt-4-turbo"
174
+ },
175
+ "gpt-4-turbo-preview": {
176
+ "openai": "gpt-4-turbo-preview",
177
+ "openrouter": "openai/gpt-4-turbo-preview"
178
+ },
179
+ "gpt-4.1": {
180
+ "openai": "gpt-4.1",
181
+ "openrouter": "openai/gpt-4.1"
182
+ },
183
+ "gpt-4.1-mini": {
184
+ "openai": "gpt-4.1-mini",
185
+ "openrouter": "openai/gpt-4.1-mini"
186
+ },
187
+ "gpt-4.1-nano": {
188
+ "openai": "gpt-4.1-nano",
189
+ "openrouter": "openai/gpt-4.1-nano"
190
+ },
191
+ "gpt-4o": {
192
+ "openai": "gpt-4o",
193
+ "openrouter": "openai/gpt-4o"
194
+ },
195
+ "gpt-4o-2024-05-13": {
196
+ "openai": "gpt-4o-2024-05-13",
197
+ "openrouter": "openai/gpt-4o-2024-05-13"
198
+ },
199
+ "gpt-4o-2024-08-06": {
200
+ "openai": "gpt-4o-2024-08-06",
201
+ "openrouter": "openai/gpt-4o-2024-08-06"
202
+ },
203
+ "gpt-4o-2024-11-20": {
204
+ "openai": "gpt-4o-2024-11-20",
205
+ "openrouter": "openai/gpt-4o-2024-11-20"
206
+ },
207
+ "gpt-4o-audio-preview": {
208
+ "openai": "gpt-4o-audio-preview",
209
+ "openrouter": "openai/gpt-4o-audio-preview"
210
+ },
211
+ "gpt-4o-mini": {
212
+ "openai": "gpt-4o-mini",
213
+ "openrouter": "openai/gpt-4o-mini"
214
+ },
215
+ "gpt-4o-mini-2024-07-18": {
216
+ "openai": "gpt-4o-mini-2024-07-18",
217
+ "openrouter": "openai/gpt-4o-mini-2024-07-18"
218
+ },
219
+ "gpt-4o-mini-search-preview": {
220
+ "openai": "gpt-4o-mini-search-preview",
221
+ "openrouter": "openai/gpt-4o-mini-search-preview"
222
+ },
223
+ "gpt-4o-search-preview": {
224
+ "openai": "gpt-4o-search-preview",
225
+ "openrouter": "openai/gpt-4o-search-preview"
226
+ },
227
+ "gpt-5": {
228
+ "openai": "gpt-5",
229
+ "openrouter": "openai/gpt-5"
230
+ },
231
+ "gpt-5-codex": {
232
+ "openai": "gpt-5-codex",
233
+ "openrouter": "openai/gpt-5-codex"
234
+ },
235
+ "gpt-5-mini": {
236
+ "openai": "gpt-5-mini",
237
+ "openrouter": "openai/gpt-5-mini"
238
+ },
239
+ "gpt-5-nano": {
240
+ "openai": "gpt-5-nano",
241
+ "openrouter": "openai/gpt-5-nano"
242
+ },
243
+ "gpt-5-pro": {
244
+ "openai": "gpt-5-pro",
245
+ "openrouter": "openai/gpt-5-pro"
246
+ },
247
+ "gpt-oss-120b": {
248
+ "openai": "gpt-oss-120b",
249
+ "openrouter": "openai/gpt-oss-120b"
250
+ },
251
+ "gpt-oss-20b": {
252
+ "openai": "gpt-oss-20b",
253
+ "openrouter": "openai/gpt-oss-20b"
254
+ },
255
+ "imagen-4.0-generate-001": {
256
+ "gemini": "imagen-4.0-generate-001",
257
+ "vertexai": "imagen-4.0-generate-001"
258
+ },
259
+ "o1": {
260
+ "openai": "o1",
261
+ "openrouter": "openai/o1"
262
+ },
263
+ "o1-pro": {
264
+ "openai": "o1-pro",
265
+ "openrouter": "openai/o1-pro"
266
+ },
267
+ "o3": {
268
+ "openai": "o3",
269
+ "openrouter": "openai/o3"
270
+ },
271
+ "o3-deep-research": {
272
+ "openai": "o3-deep-research",
273
+ "openrouter": "openai/o3-deep-research"
274
+ },
275
+ "o3-mini": {
276
+ "openai": "o3-mini",
277
+ "openrouter": "openai/o3-mini"
278
+ },
279
+ "o3-pro": {
280
+ "openai": "o3-pro",
281
+ "openrouter": "openai/o3-pro"
282
+ },
283
+ "o4-mini": {
284
+ "openai": "o4-mini",
285
+ "openrouter": "openai/o4-mini"
286
+ },
287
+ "o4-mini-deep-research": {
288
+ "openai": "o4-mini-deep-research",
289
+ "openrouter": "openai/o4-mini-deep-research"
290
+ },
291
+ "text-embedding-004": {
292
+ "gemini": "text-embedding-004",
293
+ "vertexai": "text-embedding-004"
294
+ }
295
+ }
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ # Manages model aliases for provider-specific versions
5
+ class Aliases
6
+ class << self
7
+ def resolve(model_id, provider = nil)
8
+ return model_id unless aliases[model_id]
9
+
10
+ if provider
11
+ aliases[model_id][provider.to_s] || model_id
12
+ else
13
+ aliases[model_id].values.first || model_id
14
+ end
15
+ end
16
+
17
+ def aliases
18
+ @aliases ||= load_aliases
19
+ end
20
+
21
+ def aliases_file
22
+ File.expand_path('aliases.json', __dir__)
23
+ end
24
+
25
+ def load_aliases
26
+ if File.exist?(aliases_file)
27
+ JSON.parse(File.read(aliases_file))
28
+ else
29
+ {}
30
+ end
31
+ end
32
+
33
+ def reload!
34
+ @aliases = load_aliases
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ # A class representing a file attachment.
5
+ class Attachment
6
+ attr_reader :source, :filename, :mime_type
7
+
8
+ def initialize(source, filename: nil)
9
+ @source = source
10
+ @source = source_type_cast
11
+ @filename = filename || source_filename
12
+
13
+ determine_mime_type
14
+ end
15
+
16
+ def url?
17
+ @source.is_a?(URI) || (@source.is_a?(String) && @source.match?(%r{^https?://}))
18
+ end
19
+
20
+ def path?
21
+ @source.is_a?(Pathname) || (@source.is_a?(String) && !url?)
22
+ end
23
+
24
+ def io_like?
25
+ @source.respond_to?(:read) && !path? && !active_storage?
26
+ end
27
+
28
+ def active_storage?
29
+ return false unless defined?(ActiveStorage)
30
+
31
+ @source.is_a?(ActiveStorage::Blob) ||
32
+ @source.is_a?(ActiveStorage::Attached::One) ||
33
+ @source.is_a?(ActiveStorage::Attached::Many)
34
+ end
35
+
36
+ def content
37
+ return @content if defined?(@content) && !@content.nil?
38
+
39
+ if url?
40
+ fetch_content
41
+ elsif path?
42
+ load_content_from_path
43
+ elsif active_storage?
44
+ load_content_from_active_storage
45
+ elsif io_like?
46
+ load_content_from_io
47
+ else
48
+ RubyLLM.logger.warn "Source is neither a URL, path, ActiveStorage, nor IO-like: #{@source.class}"
49
+ nil
50
+ end
51
+
52
+ @content
53
+ end
54
+
55
+ def encoded
56
+ Base64.strict_encode64(content)
57
+ end
58
+
59
+ def save(path)
60
+ return unless io_like?
61
+
62
+ File.open(path, 'w') do |f|
63
+ f.puts(@source.read)
64
+ end
65
+ end
66
+
67
+ def for_llm
68
+ case type
69
+ when :text
70
+ "<file name='#{filename}' mime_type='#{mime_type}'>#{content}</file>"
71
+ else
72
+ "data:#{mime_type};base64,#{encoded}"
73
+ end
74
+ end
75
+
76
+ def type
77
+ return :image if image?
78
+ return :video if video?
79
+ return :audio if audio?
80
+ return :pdf if pdf?
81
+ return :text if text?
82
+
83
+ :unknown
84
+ end
85
+
86
+ def image?
87
+ RubyLLM::MimeType.image? mime_type
88
+ end
89
+
90
+ def video?
91
+ RubyLLM::MimeType.video? mime_type
92
+ end
93
+
94
+ def audio?
95
+ RubyLLM::MimeType.audio? mime_type
96
+ end
97
+
98
+ def format
99
+ case mime_type
100
+ when 'audio/mpeg'
101
+ 'mp3'
102
+ when 'audio/wav', 'audio/wave', 'audio/x-wav'
103
+ 'wav'
104
+ else
105
+ mime_type.split('/').last
106
+ end
107
+ end
108
+
109
+ def pdf?
110
+ RubyLLM::MimeType.pdf? mime_type
111
+ end
112
+
113
+ def text?
114
+ RubyLLM::MimeType.text? mime_type
115
+ end
116
+
117
+ def to_h
118
+ { type: type, source: @source }
119
+ end
120
+
121
+ private
122
+
123
+ def determine_mime_type
124
+ return @mime_type = active_storage_content_type if active_storage? && active_storage_content_type.present?
125
+
126
+ @mime_type = RubyLLM::MimeType.for(url? ? nil : @source, name: @filename)
127
+ @mime_type = RubyLLM::MimeType.for(content) if @mime_type == 'application/octet-stream'
128
+ @mime_type = 'audio/wav' if @mime_type == 'audio/x-wav' # Normalize WAV type
129
+ end
130
+
131
+ def fetch_content
132
+ response = Connection.basic.get @source.to_s
133
+ @content = response.body
134
+ end
135
+
136
+ def load_content_from_path
137
+ @content = File.read(@source)
138
+ end
139
+
140
+ def load_content_from_io
141
+ @source.rewind if @source.respond_to? :rewind
142
+ @content = @source.read
143
+ end
144
+
145
+ def load_content_from_active_storage
146
+ return unless defined?(ActiveStorage)
147
+
148
+ @content = case @source
149
+ when ActiveStorage::Blob
150
+ @source.download
151
+ when ActiveStorage::Attached::One
152
+ @source.blob&.download
153
+ when ActiveStorage::Attached::Many
154
+ # For multiple attachments, just take the first one
155
+ # This maintains the single-attachment interface
156
+ @source.blobs.first&.download
157
+ end
158
+ end
159
+
160
+ def source_type_cast
161
+ if url?
162
+ URI(@source)
163
+ elsif path?
164
+ Pathname.new(@source)
165
+ else
166
+ @source
167
+ end
168
+ end
169
+
170
+ def source_filename
171
+ if url?
172
+ File.basename(@source.path).to_s
173
+ elsif path?
174
+ @source.basename.to_s
175
+ elsif io_like?
176
+ extract_filename_from_io
177
+ elsif active_storage?
178
+ extract_filename_from_active_storage
179
+ end
180
+ end
181
+
182
+ def extract_filename_from_io
183
+ if defined?(ActionDispatch::Http::UploadedFile) && @source.is_a?(ActionDispatch::Http::UploadedFile)
184
+ @source.original_filename.to_s
185
+ elsif @source.respond_to?(:path)
186
+ File.basename(@source.path).to_s
187
+ else
188
+ 'attachment'
189
+ end
190
+ end
191
+
192
+ def extract_filename_from_active_storage # rubocop:disable Metrics/PerceivedComplexity
193
+ return 'attachment' unless defined?(ActiveStorage)
194
+
195
+ case @source
196
+ when ActiveStorage::Blob
197
+ @source.filename.to_s
198
+ when ActiveStorage::Attached::One
199
+ @source.blob&.filename&.to_s || 'attachment'
200
+ when ActiveStorage::Attached::Many
201
+ @source.blobs.first&.filename&.to_s || 'attachment'
202
+ else
203
+ 'attachment'
204
+ end
205
+ end
206
+
207
+ def active_storage_content_type
208
+ return unless defined?(ActiveStorage)
209
+
210
+ case @source
211
+ when ActiveStorage::Blob
212
+ @source.content_type
213
+ when ActiveStorage::Attached::One
214
+ @source.blob&.content_type
215
+ when ActiveStorage::Attached::Many
216
+ @source.blobs.first&.content_type
217
+ end
218
+ end
219
+ end
220
+ end