lex-llm 0.1.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 (135) hide show
  1. checksums.yaml +7 -0
  2. data/.github/CODEOWNERS +7 -0
  3. data/.github/dependabot.yml +18 -0
  4. data/.github/workflows/ci.yml +16 -0
  5. data/.gitignore +19 -0
  6. data/.rubocop.yml +42 -0
  7. data/CHANGELOG.md +15 -0
  8. data/Gemfile +50 -0
  9. data/LICENSE +21 -0
  10. data/README.md +279 -0
  11. data/lex-llm.gemspec +43 -0
  12. data/lib/generators/lex_llm/agent/agent_generator.rb +36 -0
  13. data/lib/generators/lex_llm/agent/templates/agent.rb.tt +6 -0
  14. data/lib/generators/lex_llm/agent/templates/instructions.txt.erb.tt +0 -0
  15. data/lib/generators/lex_llm/chat_ui/chat_ui_generator.rb +256 -0
  16. data/lib/generators/lex_llm/chat_ui/templates/controllers/chats_controller.rb.tt +38 -0
  17. data/lib/generators/lex_llm/chat_ui/templates/controllers/messages_controller.rb.tt +21 -0
  18. data/lib/generators/lex_llm/chat_ui/templates/controllers/models_controller.rb.tt +14 -0
  19. data/lib/generators/lex_llm/chat_ui/templates/helpers/messages_helper.rb.tt +25 -0
  20. data/lib/generators/lex_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +12 -0
  21. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +16 -0
  22. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +31 -0
  23. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +31 -0
  24. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/new.html.erb.tt +9 -0
  25. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +27 -0
  26. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +14 -0
  27. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_content.html.erb.tt +1 -0
  28. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +13 -0
  29. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +23 -0
  30. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +10 -0
  31. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +2 -0
  32. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +4 -0
  33. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +14 -0
  34. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +13 -0
  35. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +21 -0
  36. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +17 -0
  37. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +40 -0
  38. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +27 -0
  39. data/lib/generators/lex_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +16 -0
  40. data/lib/generators/lex_llm/chat_ui/templates/views/chats/_form.html.erb.tt +29 -0
  41. data/lib/generators/lex_llm/chat_ui/templates/views/chats/index.html.erb.tt +28 -0
  42. data/lib/generators/lex_llm/chat_ui/templates/views/chats/new.html.erb.tt +11 -0
  43. data/lib/generators/lex_llm/chat_ui/templates/views/chats/show.html.erb.tt +25 -0
  44. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +9 -0
  45. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_content.html.erb.tt +1 -0
  46. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_error.html.erb.tt +8 -0
  47. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_form.html.erb.tt +21 -0
  48. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_system.html.erb.tt +6 -0
  49. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +2 -0
  50. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +4 -0
  51. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_user.html.erb.tt +9 -0
  52. data/lib/generators/lex_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +7 -0
  53. data/lib/generators/lex_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +8 -0
  54. data/lib/generators/lex_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +16 -0
  55. data/lib/generators/lex_llm/chat_ui/templates/views/models/_model.html.erb.tt +15 -0
  56. data/lib/generators/lex_llm/chat_ui/templates/views/models/index.html.erb.tt +38 -0
  57. data/lib/generators/lex_llm/chat_ui/templates/views/models/show.html.erb.tt +17 -0
  58. data/lib/generators/lex_llm/generator_helpers.rb +214 -0
  59. data/lib/generators/lex_llm/install/install_generator.rb +109 -0
  60. data/lib/generators/lex_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +9 -0
  61. data/lib/generators/lex_llm/install/templates/chat_model.rb.tt +3 -0
  62. data/lib/generators/lex_llm/install/templates/create_chats_migration.rb.tt +7 -0
  63. data/lib/generators/lex_llm/install/templates/create_messages_migration.rb.tt +19 -0
  64. data/lib/generators/lex_llm/install/templates/create_models_migration.rb.tt +39 -0
  65. data/lib/generators/lex_llm/install/templates/create_tool_calls_migration.rb.tt +21 -0
  66. data/lib/generators/lex_llm/install/templates/initializer.rb.tt +20 -0
  67. data/lib/generators/lex_llm/install/templates/message_model.rb.tt +4 -0
  68. data/lib/generators/lex_llm/install/templates/model_model.rb.tt +3 -0
  69. data/lib/generators/lex_llm/install/templates/tool_call_model.rb.tt +3 -0
  70. data/lib/generators/lex_llm/schema/schema_generator.rb +26 -0
  71. data/lib/generators/lex_llm/schema/templates/schema.rb.tt +2 -0
  72. data/lib/generators/lex_llm/tool/templates/tool.rb.tt +9 -0
  73. data/lib/generators/lex_llm/tool/templates/tool_call.html.erb.tt +13 -0
  74. data/lib/generators/lex_llm/tool/templates/tool_result.html.erb.tt +13 -0
  75. data/lib/generators/lex_llm/tool/tool_generator.rb +96 -0
  76. data/lib/generators/lex_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +19 -0
  77. data/lib/generators/lex_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +50 -0
  78. data/lib/generators/lex_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +7 -0
  79. data/lib/generators/lex_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +49 -0
  80. data/lib/generators/lex_llm/upgrade_to_v1_7/templates/migration.rb.tt +145 -0
  81. data/lib/generators/lex_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +122 -0
  82. data/lib/generators/lex_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +15 -0
  83. data/lib/generators/lex_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +49 -0
  84. data/lib/legion/extensions/llm/provider_settings.rb +49 -0
  85. data/lib/legion/extensions/llm/transport/fleet_lane.rb +70 -0
  86. data/lib/legion/extensions/llm.rb +50 -0
  87. data/lib/lex_llm/active_record/acts_as.rb +180 -0
  88. data/lib/lex_llm/active_record/acts_as_legacy.rb +503 -0
  89. data/lib/lex_llm/active_record/chat_methods.rb +468 -0
  90. data/lib/lex_llm/active_record/message_methods.rb +131 -0
  91. data/lib/lex_llm/active_record/model_methods.rb +76 -0
  92. data/lib/lex_llm/active_record/payload_helpers.rb +26 -0
  93. data/lib/lex_llm/active_record/tool_call_methods.rb +15 -0
  94. data/lib/lex_llm/agent.rb +365 -0
  95. data/lib/lex_llm/aliases.json +436 -0
  96. data/lib/lex_llm/aliases.rb +38 -0
  97. data/lib/lex_llm/attachment.rb +223 -0
  98. data/lib/lex_llm/chat.rb +351 -0
  99. data/lib/lex_llm/chunk.rb +6 -0
  100. data/lib/lex_llm/configuration.rb +81 -0
  101. data/lib/lex_llm/connection.rb +130 -0
  102. data/lib/lex_llm/content.rb +77 -0
  103. data/lib/lex_llm/context.rb +29 -0
  104. data/lib/lex_llm/embedding.rb +29 -0
  105. data/lib/lex_llm/error.rb +112 -0
  106. data/lib/lex_llm/image.rb +105 -0
  107. data/lib/lex_llm/message.rb +107 -0
  108. data/lib/lex_llm/mime_type.rb +71 -0
  109. data/lib/lex_llm/model/info.rb +113 -0
  110. data/lib/lex_llm/model/modalities.rb +22 -0
  111. data/lib/lex_llm/model/pricing.rb +48 -0
  112. data/lib/lex_llm/model/pricing_category.rb +46 -0
  113. data/lib/lex_llm/model/pricing_tier.rb +33 -0
  114. data/lib/lex_llm/model.rb +7 -0
  115. data/lib/lex_llm/models.json +57241 -0
  116. data/lib/lex_llm/models.rb +506 -0
  117. data/lib/lex_llm/models_schema.json +168 -0
  118. data/lib/lex_llm/moderation.rb +56 -0
  119. data/lib/lex_llm/provider.rb +278 -0
  120. data/lib/lex_llm/railtie.rb +35 -0
  121. data/lib/lex_llm/routing/lane_key.rb +51 -0
  122. data/lib/lex_llm/routing/model_offering.rb +169 -0
  123. data/lib/lex_llm/routing.rb +7 -0
  124. data/lib/lex_llm/stream_accumulator.rb +203 -0
  125. data/lib/lex_llm/streaming.rb +175 -0
  126. data/lib/lex_llm/thinking.rb +49 -0
  127. data/lib/lex_llm/tokens.rb +47 -0
  128. data/lib/lex_llm/tool.rb +254 -0
  129. data/lib/lex_llm/tool_call.rb +25 -0
  130. data/lib/lex_llm/transcription.rb +35 -0
  131. data/lib/lex_llm/utils.rb +91 -0
  132. data/lib/lex_llm/version.rb +5 -0
  133. data/lib/lex_llm.rb +95 -0
  134. data/lib/tasks/lex_llm.rake +23 -0
  135. metadata +349 -0
@@ -0,0 +1,436 @@
1
+ {
2
+ "claude-3-5-haiku": {
3
+ "anthropic": "claude-3-5-haiku-20241022",
4
+ "openrouter": "anthropic/claude-3.5-haiku",
5
+ "bedrock": "anthropic.claude-3-5-haiku-20241022-v1:0"
6
+ },
7
+ "claude-3-5-haiku-latest": {
8
+ "anthropic": "claude-3-5-haiku-latest"
9
+ },
10
+ "claude-3-5-sonnet": {
11
+ "anthropic": "claude-3-5-sonnet-20241022",
12
+ "bedrock": "anthropic.claude-3-5-sonnet-20241022-v2:0"
13
+ },
14
+ "claude-3-7-sonnet": {
15
+ "anthropic": "claude-3-7-sonnet-20250219",
16
+ "openrouter": "anthropic/claude-3.7-sonnet",
17
+ "bedrock": "anthropic.claude-3-7-sonnet-20250219-v1:0"
18
+ },
19
+ "claude-3-haiku": {
20
+ "anthropic": "claude-3-haiku-20240307",
21
+ "openrouter": "anthropic/claude-3-haiku",
22
+ "bedrock": "anthropic.claude-3-haiku-20240307-v1:0:200k"
23
+ },
24
+ "claude-3-opus": {
25
+ "anthropic": "claude-3-opus-20240229"
26
+ },
27
+ "claude-3-sonnet": {
28
+ "anthropic": "claude-3-sonnet-20240229",
29
+ "bedrock": "anthropic.claude-3-sonnet-20240229-v1:0:200k"
30
+ },
31
+ "claude-haiku-4-5": {
32
+ "anthropic": "claude-haiku-4-5-20251001",
33
+ "openrouter": "anthropic/claude-haiku-4.5",
34
+ "bedrock": "anthropic.claude-haiku-4-5-20251001-v1:0",
35
+ "azure": "claude-haiku-4-5-20251001"
36
+ },
37
+ "claude-opus-4": {
38
+ "anthropic": "claude-opus-4-20250514",
39
+ "openrouter": "anthropic/claude-opus-4",
40
+ "bedrock": "anthropic.claude-opus-4-1-20250805-v1:0"
41
+ },
42
+ "claude-opus-4-0": {
43
+ "anthropic": "claude-opus-4-0"
44
+ },
45
+ "claude-opus-4-1": {
46
+ "anthropic": "claude-opus-4-1-20250805",
47
+ "openrouter": "anthropic/claude-opus-4.1",
48
+ "bedrock": "anthropic.claude-opus-4-1-20250805-v1:0",
49
+ "azure": "claude-opus-4-1-20250805"
50
+ },
51
+ "claude-opus-4-5": {
52
+ "anthropic": "claude-opus-4-5-20251101",
53
+ "openrouter": "anthropic/claude-opus-4.5",
54
+ "bedrock": "anthropic.claude-opus-4-5-20251101-v1:0",
55
+ "azure": "claude-opus-4-5-20251101"
56
+ },
57
+ "claude-opus-4-6": {
58
+ "anthropic": "claude-opus-4-6",
59
+ "openrouter": "anthropic/claude-opus-4.6",
60
+ "bedrock": "anthropic.claude-opus-4-6-v1",
61
+ "azure": "claude-opus-4-6"
62
+ },
63
+ "claude-sonnet-4": {
64
+ "anthropic": "claude-sonnet-4-20250514",
65
+ "openrouter": "anthropic/claude-sonnet-4",
66
+ "bedrock": "anthropic.claude-sonnet-4-20250514-v1:0"
67
+ },
68
+ "claude-sonnet-4-0": {
69
+ "anthropic": "claude-sonnet-4-0"
70
+ },
71
+ "claude-sonnet-4-5": {
72
+ "anthropic": "claude-sonnet-4-5-20250929",
73
+ "openrouter": "anthropic/claude-sonnet-4.5",
74
+ "bedrock": "anthropic.claude-sonnet-4-5-20250929-v1:0",
75
+ "azure": "claude-sonnet-4-5-20250929"
76
+ },
77
+ "claude-sonnet-4-6": {
78
+ "anthropic": "claude-sonnet-4-6",
79
+ "openrouter": "anthropic/claude-sonnet-4.6",
80
+ "bedrock": "anthropic.claude-sonnet-4-6",
81
+ "azure": "claude-sonnet-4-6"
82
+ },
83
+ "deepseek-chat": {
84
+ "deepseek": "deepseek-chat",
85
+ "openrouter": "deepseek/deepseek-chat"
86
+ },
87
+ "gemini-1.5-flash": {
88
+ "gemini": "gemini-1.5-flash",
89
+ "vertexai": "gemini-1.5-flash"
90
+ },
91
+ "gemini-1.5-flash-8b": {
92
+ "gemini": "gemini-1.5-flash-8b",
93
+ "vertexai": "gemini-1.5-flash-8b"
94
+ },
95
+ "gemini-1.5-pro": {
96
+ "gemini": "gemini-1.5-pro",
97
+ "vertexai": "gemini-1.5-pro"
98
+ },
99
+ "gemini-2.0-flash": {
100
+ "gemini": "gemini-2.0-flash",
101
+ "vertexai": "gemini-2.0-flash"
102
+ },
103
+ "gemini-2.0-flash-001": {
104
+ "gemini": "gemini-2.0-flash-001",
105
+ "openrouter": "google/gemini-2.0-flash-001",
106
+ "vertexai": "gemini-2.0-flash-001"
107
+ },
108
+ "gemini-2.0-flash-lite": {
109
+ "gemini": "gemini-2.0-flash-lite",
110
+ "vertexai": "gemini-2.0-flash-lite"
111
+ },
112
+ "gemini-2.0-flash-lite-001": {
113
+ "gemini": "gemini-2.0-flash-lite-001",
114
+ "openrouter": "google/gemini-2.0-flash-lite-001",
115
+ "vertexai": "gemini-2.0-flash-lite-001"
116
+ },
117
+ "gemini-2.5-flash": {
118
+ "gemini": "gemini-2.5-flash",
119
+ "openrouter": "google/gemini-2.5-flash",
120
+ "vertexai": "gemini-2.5-flash"
121
+ },
122
+ "gemini-2.5-flash-image": {
123
+ "gemini": "gemini-2.5-flash-image",
124
+ "openrouter": "google/gemini-2.5-flash-image"
125
+ },
126
+ "gemini-2.5-flash-lite": {
127
+ "gemini": "gemini-2.5-flash-lite",
128
+ "openrouter": "google/gemini-2.5-flash-lite",
129
+ "vertexai": "gemini-2.5-flash-lite"
130
+ },
131
+ "gemini-2.5-flash-lite-preview-06-17": {
132
+ "gemini": "gemini-2.5-flash-lite-preview-06-17",
133
+ "vertexai": "gemini-2.5-flash-lite-preview-06-17"
134
+ },
135
+ "gemini-2.5-flash-lite-preview-09-2025": {
136
+ "gemini": "gemini-2.5-flash-lite-preview-09-2025",
137
+ "openrouter": "google/gemini-2.5-flash-lite-preview-09-2025",
138
+ "vertexai": "gemini-2.5-flash-lite-preview-09-2025"
139
+ },
140
+ "gemini-2.5-flash-preview-04-17": {
141
+ "gemini": "gemini-2.5-flash-preview-04-17",
142
+ "vertexai": "gemini-2.5-flash-preview-04-17"
143
+ },
144
+ "gemini-2.5-flash-preview-05-20": {
145
+ "gemini": "gemini-2.5-flash-preview-05-20",
146
+ "vertexai": "gemini-2.5-flash-preview-05-20"
147
+ },
148
+ "gemini-2.5-flash-preview-09-2025": {
149
+ "gemini": "gemini-2.5-flash-preview-09-2025",
150
+ "openrouter": "google/gemini-2.5-flash-preview-09-2025",
151
+ "vertexai": "gemini-2.5-flash-preview-09-2025"
152
+ },
153
+ "gemini-2.5-pro": {
154
+ "gemini": "gemini-2.5-pro",
155
+ "openrouter": "google/gemini-2.5-pro",
156
+ "vertexai": "gemini-2.5-pro"
157
+ },
158
+ "gemini-2.5-pro-preview-05-06": {
159
+ "gemini": "gemini-2.5-pro-preview-05-06",
160
+ "openrouter": "google/gemini-2.5-pro-preview-05-06",
161
+ "vertexai": "gemini-2.5-pro-preview-05-06"
162
+ },
163
+ "gemini-2.5-pro-preview-06-05": {
164
+ "gemini": "gemini-2.5-pro-preview-06-05",
165
+ "openrouter": "google/gemini-2.5-pro-preview-06-05",
166
+ "vertexai": "gemini-2.5-pro-preview-06-05"
167
+ },
168
+ "gemini-3-flash-preview": {
169
+ "gemini": "gemini-3-flash-preview",
170
+ "openrouter": "google/gemini-3-flash-preview",
171
+ "vertexai": "gemini-3-flash-preview"
172
+ },
173
+ "gemini-3-pro-image-preview": {
174
+ "gemini": "gemini-3-pro-image-preview",
175
+ "openrouter": "google/gemini-3-pro-image-preview"
176
+ },
177
+ "gemini-3-pro-preview": {
178
+ "gemini": "gemini-3-pro-preview",
179
+ "openrouter": "google/gemini-3-pro-preview",
180
+ "vertexai": "gemini-3-pro-preview"
181
+ },
182
+ "gemini-3.1-flash-image-preview": {
183
+ "gemini": "gemini-3.1-flash-image-preview",
184
+ "openrouter": "google/gemini-3.1-flash-image-preview",
185
+ "vertexai": "gemini-3.1-flash-image-preview"
186
+ },
187
+ "gemini-3.1-flash-lite-preview": {
188
+ "gemini": "gemini-3.1-flash-lite-preview",
189
+ "openrouter": "google/gemini-3.1-flash-lite-preview",
190
+ "vertexai": "gemini-3.1-flash-lite-preview"
191
+ },
192
+ "gemini-3.1-pro-preview": {
193
+ "gemini": "gemini-3.1-pro-preview",
194
+ "openrouter": "google/gemini-3.1-pro-preview",
195
+ "vertexai": "gemini-3.1-pro-preview"
196
+ },
197
+ "gemini-3.1-pro-preview-customtools": {
198
+ "gemini": "gemini-3.1-pro-preview-customtools",
199
+ "openrouter": "google/gemini-3.1-pro-preview-customtools",
200
+ "vertexai": "gemini-3.1-pro-preview-customtools"
201
+ },
202
+ "gemini-embedding-001": {
203
+ "gemini": "gemini-embedding-001",
204
+ "vertexai": "gemini-embedding-001"
205
+ },
206
+ "gemini-flash": {
207
+ "gemini": "gemini-flash-latest",
208
+ "vertexai": "gemini-flash-latest"
209
+ },
210
+ "gemini-flash-lite": {
211
+ "gemini": "gemini-flash-lite-latest",
212
+ "vertexai": "gemini-flash-lite-latest"
213
+ },
214
+ "gemma-3-12b-it": {
215
+ "gemini": "gemma-3-12b-it",
216
+ "openrouter": "google/gemma-3-12b-it"
217
+ },
218
+ "gemma-3-27b-it": {
219
+ "gemini": "gemma-3-27b-it",
220
+ "openrouter": "google/gemma-3-27b-it"
221
+ },
222
+ "gemma-3-4b-it": {
223
+ "gemini": "gemma-3-4b-it",
224
+ "openrouter": "google/gemma-3-4b-it"
225
+ },
226
+ "gemma-3n-e4b-it": {
227
+ "gemini": "gemma-3n-e4b-it",
228
+ "openrouter": "google/gemma-3n-e4b-it"
229
+ },
230
+ "gemma-4-26b-a4b-it": {
231
+ "gemini": "gemma-4-26b-a4b-it",
232
+ "openrouter": "google/gemma-4-26b-a4b-it"
233
+ },
234
+ "gemma-4-31b-it": {
235
+ "gemini": "gemma-4-31b-it",
236
+ "openrouter": "google/gemma-4-31b-it"
237
+ },
238
+ "gpt-3.5-turbo": {
239
+ "openai": "gpt-3.5-turbo",
240
+ "openrouter": "openai/gpt-3.5-turbo"
241
+ },
242
+ "gpt-3.5-turbo-16k": {
243
+ "openai": "gpt-3.5-turbo-16k",
244
+ "openrouter": "openai/gpt-3.5-turbo-16k"
245
+ },
246
+ "gpt-3.5-turbo-instruct": {
247
+ "openai": "gpt-3.5-turbo-instruct",
248
+ "openrouter": "openai/gpt-3.5-turbo-instruct"
249
+ },
250
+ "gpt-4": {
251
+ "openai": "gpt-4",
252
+ "openrouter": "openai/gpt-4",
253
+ "azure": "gpt-4"
254
+ },
255
+ "gpt-4-turbo": {
256
+ "openai": "gpt-4-turbo",
257
+ "openrouter": "openai/gpt-4-turbo"
258
+ },
259
+ "gpt-4.1": {
260
+ "openai": "gpt-4.1",
261
+ "openrouter": "openai/gpt-4.1",
262
+ "azure": "gpt-4.1"
263
+ },
264
+ "gpt-4.1-mini": {
265
+ "openai": "gpt-4.1-mini",
266
+ "openrouter": "openai/gpt-4.1-mini",
267
+ "azure": "gpt-4.1-mini"
268
+ },
269
+ "gpt-4.1-nano": {
270
+ "openai": "gpt-4.1-nano",
271
+ "openrouter": "openai/gpt-4.1-nano",
272
+ "azure": "gpt-4.1-nano"
273
+ },
274
+ "gpt-4o": {
275
+ "openai": "gpt-4o",
276
+ "openrouter": "openai/gpt-4o",
277
+ "azure": "gpt-4o"
278
+ },
279
+ "gpt-4o-2024-05-13": {
280
+ "openai": "gpt-4o-2024-05-13",
281
+ "openrouter": "openai/gpt-4o-2024-05-13",
282
+ "azure": "gpt-4o-2024-05-13"
283
+ },
284
+ "gpt-4o-2024-08-06": {
285
+ "openai": "gpt-4o-2024-08-06",
286
+ "openrouter": "openai/gpt-4o-2024-08-06",
287
+ "azure": "gpt-4o-2024-08-06"
288
+ },
289
+ "gpt-4o-2024-11-20": {
290
+ "openai": "gpt-4o-2024-11-20",
291
+ "openrouter": "openai/gpt-4o-2024-11-20",
292
+ "azure": "gpt-4o-2024-11-20"
293
+ },
294
+ "gpt-4o-audio-preview": {
295
+ "openai": "gpt-4o-audio-preview",
296
+ "openrouter": "openai/gpt-4o-audio-preview"
297
+ },
298
+ "gpt-4o-mini": {
299
+ "openai": "gpt-4o-mini",
300
+ "openrouter": "openai/gpt-4o-mini",
301
+ "azure": "gpt-4o-mini"
302
+ },
303
+ "gpt-4o-mini-2024-07-18": {
304
+ "openai": "gpt-4o-mini-2024-07-18",
305
+ "openrouter": "openai/gpt-4o-mini-2024-07-18",
306
+ "azure": "gpt-4o-mini-2024-07-18"
307
+ },
308
+ "gpt-4o-mini-search-preview": {
309
+ "openai": "gpt-4o-mini-search-preview",
310
+ "openrouter": "openai/gpt-4o-mini-search-preview"
311
+ },
312
+ "gpt-4o-search-preview": {
313
+ "openai": "gpt-4o-search-preview",
314
+ "openrouter": "openai/gpt-4o-search-preview"
315
+ },
316
+ "gpt-5": {
317
+ "openai": "gpt-5",
318
+ "openrouter": "openai/gpt-5"
319
+ },
320
+ "gpt-5-codex": {
321
+ "openai": "gpt-5-codex",
322
+ "openrouter": "openai/gpt-5-codex"
323
+ },
324
+ "gpt-5-mini": {
325
+ "openai": "gpt-5-mini",
326
+ "openrouter": "openai/gpt-5-mini"
327
+ },
328
+ "gpt-5-nano": {
329
+ "openai": "gpt-5-nano",
330
+ "openrouter": "openai/gpt-5-nano"
331
+ },
332
+ "gpt-5-pro": {
333
+ "openai": "gpt-5-pro",
334
+ "openrouter": "openai/gpt-5-pro"
335
+ },
336
+ "gpt-5.1": {
337
+ "openai": "gpt-5.1",
338
+ "openrouter": "openai/gpt-5.1",
339
+ "azure": "gpt-5.1"
340
+ },
341
+ "gpt-5.1-codex": {
342
+ "openai": "gpt-5.1-codex",
343
+ "openrouter": "openai/gpt-5.1-codex"
344
+ },
345
+ "gpt-5.1-codex-max": {
346
+ "openai": "gpt-5.1-codex-max",
347
+ "openrouter": "openai/gpt-5.1-codex-max"
348
+ },
349
+ "gpt-5.1-codex-mini": {
350
+ "openai": "gpt-5.1-codex-mini",
351
+ "openrouter": "openai/gpt-5.1-codex-mini"
352
+ },
353
+ "gpt-5.2": {
354
+ "openai": "gpt-5.2",
355
+ "openrouter": "openai/gpt-5.2"
356
+ },
357
+ "gpt-5.2-codex": {
358
+ "openai": "gpt-5.2-codex",
359
+ "openrouter": "openai/gpt-5.2-codex"
360
+ },
361
+ "gpt-5.2-pro": {
362
+ "openai": "gpt-5.2-pro",
363
+ "openrouter": "openai/gpt-5.2-pro"
364
+ },
365
+ "gpt-5.3-codex": {
366
+ "openai": "gpt-5.3-codex",
367
+ "openrouter": "openai/gpt-5.3-codex"
368
+ },
369
+ "gpt-5.4": {
370
+ "openai": "gpt-5.4",
371
+ "openrouter": "openai/gpt-5.4"
372
+ },
373
+ "gpt-5.4-mini": {
374
+ "openai": "gpt-5.4-mini",
375
+ "openrouter": "openai/gpt-5.4-mini"
376
+ },
377
+ "gpt-5.4-nano": {
378
+ "openai": "gpt-5.4-nano",
379
+ "openrouter": "openai/gpt-5.4-nano"
380
+ },
381
+ "gpt-5.4-pro": {
382
+ "openai": "gpt-5.4-pro",
383
+ "openrouter": "openai/gpt-5.4-pro"
384
+ },
385
+ "gpt-audio": {
386
+ "openai": "gpt-audio",
387
+ "openrouter": "openai/gpt-audio"
388
+ },
389
+ "gpt-audio-mini": {
390
+ "openai": "gpt-audio-mini",
391
+ "openrouter": "openai/gpt-audio-mini"
392
+ },
393
+ "lyria-3-clip-preview": {
394
+ "gemini": "lyria-3-clip-preview",
395
+ "openrouter": "google/lyria-3-clip-preview"
396
+ },
397
+ "lyria-3-pro-preview": {
398
+ "gemini": "lyria-3-pro-preview",
399
+ "openrouter": "google/lyria-3-pro-preview"
400
+ },
401
+ "o1": {
402
+ "openai": "o1",
403
+ "openrouter": "openai/o1"
404
+ },
405
+ "o1-pro": {
406
+ "openai": "o1-pro",
407
+ "openrouter": "openai/o1-pro",
408
+ "azure": "o1-pro"
409
+ },
410
+ "o3": {
411
+ "openai": "o3",
412
+ "openrouter": "openai/o3"
413
+ },
414
+ "o3-deep-research": {
415
+ "openai": "o3-deep-research",
416
+ "openrouter": "openai/o3-deep-research"
417
+ },
418
+ "o3-mini": {
419
+ "openai": "o3-mini",
420
+ "openrouter": "openai/o3-mini",
421
+ "azure": "o3-mini"
422
+ },
423
+ "o3-pro": {
424
+ "openai": "o3-pro",
425
+ "openrouter": "openai/o3-pro"
426
+ },
427
+ "o4-mini": {
428
+ "openai": "o4-mini",
429
+ "openrouter": "openai/o4-mini",
430
+ "azure": "o4-mini"
431
+ },
432
+ "o4-mini-deep-research": {
433
+ "openai": "o4-mini-deep-research",
434
+ "openrouter": "openai/o4-mini-deep-research"
435
+ }
436
+ }
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LexLLM
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
+ Legion::JSON.parse(File.read(aliases_file), symbolize_names: false)
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,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'uri'
5
+
6
+ module LexLLM
7
+ # A class representing a file attachment.
8
+ class Attachment
9
+ attr_reader :source, :filename, :mime_type
10
+
11
+ def initialize(source, filename: nil)
12
+ @source = source
13
+ @source = source_type_cast
14
+ @filename = filename || source_filename
15
+
16
+ determine_mime_type
17
+ end
18
+
19
+ def url?
20
+ @source.is_a?(URI) || (@source.is_a?(String) && @source.match?(%r{^https?://}))
21
+ end
22
+
23
+ def path?
24
+ @source.is_a?(Pathname) || (@source.is_a?(String) && !url?)
25
+ end
26
+
27
+ def io_like?
28
+ @source.respond_to?(:read) && !path? && !active_storage?
29
+ end
30
+
31
+ def active_storage?
32
+ return false unless defined?(ActiveStorage)
33
+
34
+ @source.is_a?(ActiveStorage::Blob) ||
35
+ @source.is_a?(ActiveStorage::Attached::One) ||
36
+ @source.is_a?(ActiveStorage::Attached::Many)
37
+ end
38
+
39
+ def content
40
+ return @content if defined?(@content) && !@content.nil?
41
+
42
+ if url?
43
+ fetch_content
44
+ elsif path?
45
+ load_content_from_path
46
+ elsif active_storage?
47
+ load_content_from_active_storage
48
+ elsif io_like?
49
+ load_content_from_io
50
+ else
51
+ LexLLM.logger.warn "Source is neither a URL, path, ActiveStorage, nor IO-like: #{@source.class}"
52
+ nil
53
+ end
54
+
55
+ @content
56
+ end
57
+
58
+ def encoded
59
+ Base64.strict_encode64(content)
60
+ end
61
+
62
+ def save(path)
63
+ return unless io_like?
64
+
65
+ File.open(path, 'w') do |f|
66
+ f.puts(@source.read)
67
+ end
68
+ end
69
+
70
+ def for_llm
71
+ case type
72
+ when :text
73
+ "<file name='#{filename}' mime_type='#{mime_type}'>#{content}</file>"
74
+ else
75
+ "data:#{mime_type};base64,#{encoded}"
76
+ end
77
+ end
78
+
79
+ def type
80
+ return :image if image?
81
+ return :video if video?
82
+ return :audio if audio?
83
+ return :pdf if pdf?
84
+ return :text if text?
85
+
86
+ :unknown
87
+ end
88
+
89
+ def image?
90
+ LexLLM::MimeType.image? mime_type
91
+ end
92
+
93
+ def video?
94
+ LexLLM::MimeType.video? mime_type
95
+ end
96
+
97
+ def audio?
98
+ LexLLM::MimeType.audio? mime_type
99
+ end
100
+
101
+ def format
102
+ case mime_type
103
+ when 'audio/mpeg'
104
+ 'mp3'
105
+ when 'audio/wav', 'audio/wave', 'audio/x-wav'
106
+ 'wav'
107
+ else
108
+ mime_type.split('/').last
109
+ end
110
+ end
111
+
112
+ def pdf?
113
+ LexLLM::MimeType.pdf? mime_type
114
+ end
115
+
116
+ def text?
117
+ LexLLM::MimeType.text? mime_type
118
+ end
119
+
120
+ def to_h
121
+ { type: type, source: @source }
122
+ end
123
+
124
+ private
125
+
126
+ def determine_mime_type
127
+ return @mime_type = active_storage_content_type if active_storage? && active_storage_content_type.present?
128
+
129
+ @mime_type = LexLLM::MimeType.for(url? ? nil : @source, name: @filename)
130
+ @mime_type = LexLLM::MimeType.for(content) if @mime_type == 'application/octet-stream'
131
+ @mime_type = 'audio/wav' if @mime_type == 'audio/x-wav' # Normalize WAV type
132
+ end
133
+
134
+ def fetch_content
135
+ response = Connection.basic.get @source.to_s
136
+ @content = response.body
137
+ end
138
+
139
+ def load_content_from_path
140
+ @content = File.binread(@source)
141
+ end
142
+
143
+ def load_content_from_io
144
+ @source.rewind if @source.respond_to? :rewind
145
+ @content = @source.read
146
+ end
147
+
148
+ def load_content_from_active_storage
149
+ return unless defined?(ActiveStorage)
150
+
151
+ @content = case @source
152
+ when ActiveStorage::Blob
153
+ @source.download
154
+ when ActiveStorage::Attached::One
155
+ @source.blob&.download
156
+ when ActiveStorage::Attached::Many
157
+ # For multiple attachments, just take the first one
158
+ # This maintains the single-attachment interface
159
+ @source.blobs.first&.download
160
+ end
161
+ end
162
+
163
+ def source_type_cast
164
+ if url?
165
+ URI(@source)
166
+ elsif path?
167
+ Pathname.new(@source)
168
+ else
169
+ @source
170
+ end
171
+ end
172
+
173
+ def source_filename
174
+ if url?
175
+ File.basename(@source.path).to_s
176
+ elsif path?
177
+ @source.basename.to_s
178
+ elsif io_like?
179
+ extract_filename_from_io
180
+ elsif active_storage?
181
+ extract_filename_from_active_storage
182
+ end
183
+ end
184
+
185
+ def extract_filename_from_io
186
+ if defined?(ActionDispatch::Http::UploadedFile) && @source.is_a?(ActionDispatch::Http::UploadedFile)
187
+ @source.original_filename.to_s
188
+ elsif @source.respond_to?(:path)
189
+ File.basename(@source.path).to_s
190
+ else
191
+ 'attachment'
192
+ end
193
+ end
194
+
195
+ def extract_filename_from_active_storage # rubocop:disable Metrics/PerceivedComplexity
196
+ return 'attachment' unless defined?(ActiveStorage)
197
+
198
+ case @source
199
+ when ActiveStorage::Blob
200
+ @source.filename.to_s
201
+ when ActiveStorage::Attached::One
202
+ @source.blob&.filename&.to_s || 'attachment'
203
+ when ActiveStorage::Attached::Many
204
+ @source.blobs.first&.filename&.to_s || 'attachment'
205
+ else
206
+ 'attachment'
207
+ end
208
+ end
209
+
210
+ def active_storage_content_type
211
+ return unless defined?(ActiveStorage)
212
+
213
+ case @source
214
+ when ActiveStorage::Blob
215
+ @source.content_type
216
+ when ActiveStorage::Attached::One
217
+ @source.blob&.content_type
218
+ when ActiveStorage::Attached::Many
219
+ @source.blobs.first&.content_type
220
+ end
221
+ end
222
+ end
223
+ end