lex-llm-gemini 0.1.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c368de9aa6ea5d2a0d755285b3e275d4b0db5ab42b654f68fdcc376e5aa331e
4
- data.tar.gz: afd033d49220b131dcf79fa248605c381c3d5c16fd6ac78bfd7e2fd53d402629
3
+ metadata.gz: b03f32f8b23f0f05443b2e0a32d2a6860c287628b1f2bd9f5ee0ad38b458d311
4
+ data.tar.gz: 1eb0389046f1c40f8b8e74c1ed306d871bdc6030492ead96aeec07293167bf10
5
5
  SHA512:
6
- metadata.gz: 1c5ac8a118acc877ac61623403c293de40915ab220fe51e0798aeb6055560ceff27977a8e213f28bf0fe2527ace3ff81305416671973691a5d67159d4d63e86c
7
- data.tar.gz: 14816624a72f3fca4d3827ebb81b20b8513b5beadaadf0b20fdc1680ef76756e0ee2091303be9486e70b8827fe988e81ed9e880b4db7ef1a4c70b5015dac91b9
6
+ metadata.gz: 9647f4d3d4bfa69924576f8cab5ff4b1523ace82b33d8ec60fcf9c57e7877f184f2f9920b6a7a5d46c5a3822dadddd0982f7bc215c7bd970d3c58744282d2773
7
+ data.tar.gz: 905d32ea8573ee0903794c19547d7068c1c27aab9ad5f23b12fb57780908cf55a0f7e639604421a14558b64671557ad30dc8e08033ad57bbd4f1f762795b8ed9
data/.gitignore CHANGED
@@ -2,6 +2,7 @@
2
2
  /coverage/
3
3
  /pkg/
4
4
  /tmp/
5
+ Gemfile.lock
5
6
 
6
7
  .rspec_status*
7
8
  .env
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1 - 2026-04-27
4
+
5
+ - Add the Gemini LexLLM provider class with generateContent, streaming, model listing, and embedContent helpers.
6
+ - Use shared `Legion::Extensions::Llm.provider_settings` defaults from `lex-llm`.
7
+ - Remove the committed `Gemfile.lock`.
8
+
3
9
  ## 0.1.0 - 2026-04-26
4
10
 
5
11
  - Initial Legion LLM Gemini provider extension scaffold.
data/Gemfile CHANGED
@@ -2,11 +2,13 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
- if ENV['LEX_LLM_PATH'] && File.directory?(ENV.fetch('LEX_LLM_PATH'))
6
- gem 'lex-llm', path: ENV.fetch('LEX_LLM_PATH')
7
- else
8
- gem 'lex-llm', git: 'https://github.com/LegionIO/lex-llm',
9
- branch: ENV.fetch('LEX_LLM_BRANCH', 'lex-llm-routing-base-20260425')
5
+ group :test do
6
+ if ENV['LEX_LLM_PATH'] && File.directory?(ENV.fetch('LEX_LLM_PATH'))
7
+ gem 'lex-llm', path: ENV.fetch('LEX_LLM_PATH')
8
+ else
9
+ gem 'lex-llm', git: 'https://github.com/LegionIO/lex-llm',
10
+ branch: ENV.fetch('LEX_LLM_BRANCH', 'main')
11
+ end
10
12
  end
11
13
 
12
14
  gemspec
data/README.md CHANGED
@@ -3,3 +3,42 @@
3
3
  LegionIO LLM provider extension for Gemini.
4
4
 
5
5
  This gem lives under `Legion::Extensions::Llm::Gemini` and depends on `lex-llm` for shared provider-neutral routing, fleet, and schema primitives.
6
+
7
+ ## What It Provides
8
+
9
+ - `LexLLM::Provider` registration as `:gemini`
10
+ - Gemini-native chat requests through `POST /v1beta/{model=models/*}:generateContent`
11
+ - streaming chat support through `POST /v1beta/{model=models/*}:streamGenerateContent?alt=sse`
12
+ - model discovery through `GET /v1beta/models`
13
+ - embeddings through `POST /v1beta/{model=models/*}:embedContent`
14
+ - shared fleet/default settings via `Legion::Extensions::Llm.provider_settings`
15
+
16
+ ## Defaults
17
+
18
+ ```ruby
19
+ Legion::Extensions::Llm::Gemini.default_settings
20
+ # {
21
+ # provider_family: :gemini,
22
+ # instances: {
23
+ # default: {
24
+ # endpoint: "https://generativelanguage.googleapis.com/v1beta",
25
+ # tier: :frontier,
26
+ # transport: :http,
27
+ # credentials: { api_key: "env://GEMINI_API_KEY" },
28
+ # usage: { inference: true, embedding: true },
29
+ # limits: { concurrency: 4 }
30
+ # }
31
+ # }
32
+ # }
33
+ ```
34
+
35
+ ## Configuration
36
+
37
+ ```ruby
38
+ LexLLM.configure do |config|
39
+ config.gemini_api_key = ENV.fetch("GEMINI_API_KEY")
40
+ config.gemini_api_base = "https://generativelanguage.googleapis.com/v1beta"
41
+ config.default_model = "gemini-2.0-flash"
42
+ config.default_embedding_model = "gemini-embedding-001"
43
+ end
44
+ ```
@@ -24,5 +24,8 @@ Gem::Specification.new do |spec|
24
24
  spec.require_paths = ['lib']
25
25
 
26
26
  spec.add_dependency 'faraday', '>= 2.0'
27
- spec.add_dependency 'lex-llm', '>= 0.1.0'
27
+ spec.add_dependency 'legion-json', '>= 1.0'
28
+ spec.add_dependency 'legion-logging', '>= 1.0'
29
+ spec.add_dependency 'legion-settings', '>= 1.3.14'
30
+ spec.add_dependency 'lex-llm', '>= 0.1.1'
28
31
  end
@@ -0,0 +1,313 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lex_llm'
4
+ require 'legion/json'
5
+
6
+ module Legion
7
+ module Extensions
8
+ module Llm
9
+ module Gemini
10
+ # Gemini provider implementation for the LexLLM base provider contract.
11
+ class Provider < LexLLM::Provider # rubocop:disable Metrics/ClassLength
12
+ class << self
13
+ def slug = 'gemini'
14
+ def configuration_options = %i[gemini_api_key gemini_api_base]
15
+ def configuration_requirements = %i[gemini_api_key]
16
+ def capabilities = Capabilities
17
+ end
18
+
19
+ # Capability predicates for Gemini API models.
20
+ module Capabilities
21
+ module_function
22
+
23
+ def chat?(model) = supported?(model, 'generateContent')
24
+ def streaming?(model) = supported?(model, 'streamGenerateContent')
25
+ def embeddings?(model) = supported?(model, 'embedContent')
26
+ def vision?(model) = model_id(model).match?(/gemini|flash|pro/)
27
+ def functions?(model) = chat?(model)
28
+
29
+ def critical_capabilities_for(model)
30
+ [
31
+ ('streaming' if streaming?(model)),
32
+ ('embeddings' if embeddings?(model)),
33
+ ('function_calling' if functions?(model)),
34
+ ('vision' if vision?(model))
35
+ ].compact
36
+ end
37
+
38
+ def supported?(model, action)
39
+ methods = generation_methods(model)
40
+ return model_id(model).include?('embedding') if action == 'embedContent' && methods.empty?
41
+ return true if methods.empty? && action != 'embedContent'
42
+
43
+ methods.include?(action)
44
+ end
45
+
46
+ def generation_methods(model)
47
+ metadata = metadata_for(model)
48
+ Array(metadata[:supported_generation_methods] || metadata['supported_generation_methods'])
49
+ end
50
+
51
+ def model_id(model)
52
+ return model.fetch('name', '').delete_prefix('models/') if model.is_a?(Hash)
53
+
54
+ model.respond_to?(:id) ? model.id.to_s : model.to_s
55
+ end
56
+
57
+ def metadata_for(model)
58
+ return model if model.is_a?(Hash)
59
+ return model.metadata if model.respond_to?(:metadata)
60
+
61
+ {}
62
+ end
63
+ end
64
+
65
+ def api_base
66
+ config.gemini_api_base || 'https://generativelanguage.googleapis.com/v1beta'
67
+ end
68
+
69
+ def headers
70
+ { 'x-goog-api-key' => config.gemini_api_key }
71
+ end
72
+
73
+ def completion_url = generate_content_url(model: @model)
74
+ def stream_url = stream_generate_content_url(model: @model)
75
+ def models_url = 'models'
76
+ def embedding_url(model:) = embed_content_url(model:)
77
+
78
+ def generate_content_url(model:)
79
+ "#{model_path(model)}:generateContent"
80
+ end
81
+
82
+ def stream_generate_content_url(model:)
83
+ "#{model_path(model)}:streamGenerateContent?alt=sse"
84
+ end
85
+
86
+ def embed_content_url(model:)
87
+ "#{model_path(model)}:embedContent"
88
+ end
89
+
90
+ private
91
+
92
+ def model_path(model)
93
+ value = model.to_s
94
+ value.start_with?('models/') ? value : "models/#{value}"
95
+ end
96
+
97
+ # rubocop:disable Metrics/ParameterLists,Lint/UnusedMethodArgument
98
+ def render_payload(messages, tools:, temperature:, model:, stream:, schema:, thinking:, tool_prefs:)
99
+ @model = model.id
100
+ payload = { contents: format_messages(messages), generationConfig: generation_config(temperature, schema) }
101
+ payload[:systemInstruction] = system_instruction(messages)
102
+ payload[:tools] = format_tools(tools) unless tools.empty?
103
+ payload[:toolConfig] = tool_config(tool_prefs) if tool_prefs
104
+ payload.compact
105
+ end
106
+ # rubocop:enable Metrics/ParameterLists,Lint/UnusedMethodArgument
107
+
108
+ def generation_config(temperature, schema)
109
+ {
110
+ temperature: temperature,
111
+ responseMimeType: ('application/json' if schema),
112
+ responseSchema: schema_hash(schema)
113
+ }.compact
114
+ end
115
+
116
+ def schema_hash(schema)
117
+ return unless schema
118
+
119
+ schema.respond_to?(:to_h) ? schema.to_h.fetch(:schema, schema.to_h) : schema
120
+ end
121
+
122
+ def system_instruction(messages)
123
+ system_messages = messages.select { |message| message.role == :system }
124
+ parts = system_messages.flat_map { |message| content_parts(message.content) }
125
+ return nil if parts.empty?
126
+
127
+ { parts: parts }
128
+ end
129
+
130
+ def format_messages(messages)
131
+ messages.reject { |message| message.role == :system }.map do |message|
132
+ { role: gemini_role(message), parts: message_parts(message) }
133
+ end
134
+ end
135
+
136
+ def gemini_role(message)
137
+ return 'model' if message.role == :assistant
138
+ return 'function' if message.tool_result?
139
+
140
+ message.role.to_s
141
+ end
142
+
143
+ def message_parts(message)
144
+ return tool_call_parts(message) if message.tool_call?
145
+ return tool_result_parts(message) if message.tool_result?
146
+
147
+ content_parts(message.content)
148
+ end
149
+
150
+ def content_parts(content)
151
+ return Array(content.value) if content.is_a?(LexLLM::Content::Raw)
152
+ return [{ text: Legion::JSON.generate(content) }] if content.is_a?(Hash) || content.is_a?(Array)
153
+ return [{ text: content.to_s }] unless content.is_a?(LexLLM::Content)
154
+
155
+ parts = []
156
+ parts << { text: content.text } if content.text
157
+ content.attachments.each { |attachment| parts << attachment_part(attachment) }
158
+ parts
159
+ end
160
+
161
+ def attachment_part(attachment)
162
+ if attachment.text?
163
+ { text: attachment.for_llm }
164
+ else
165
+ { inline_data: { mime_type: attachment.mime_type, data: attachment.encoded } }
166
+ end
167
+ end
168
+
169
+ def tool_call_parts(message)
170
+ message.tool_calls.values.map do |tool_call|
171
+ { functionCall: { name: tool_call.name, args: tool_call.arguments } }
172
+ end
173
+ end
174
+
175
+ def tool_result_parts(message)
176
+ [{
177
+ functionResponse: {
178
+ name: message.tool_call_id,
179
+ response: { name: message.tool_call_id, content: content_parts(message.content) }
180
+ }
181
+ }]
182
+ end
183
+
184
+ def format_tools(tools)
185
+ [{
186
+ functionDeclarations: tools.values.map do |tool|
187
+ declaration = { name: tool.name, description: tool.description }
188
+ declaration[:parameters] = tool.params_schema if tool.params_schema
189
+ declaration
190
+ end
191
+ }]
192
+ end
193
+
194
+ def tool_config(tool_prefs)
195
+ choice = tool_prefs[:choice] || tool_prefs['choice']
196
+ return unless choice
197
+
198
+ { functionCallingConfig: { mode: choice.to_s } }
199
+ end
200
+
201
+ def parse_completion_response(response)
202
+ body = response.body
203
+ parts = response_parts(body)
204
+
205
+ LexLLM::Message.new(
206
+ role: :assistant,
207
+ content: text_content(parts),
208
+ tool_calls: parse_tool_calls(parts),
209
+ input_tokens: body.dig('usageMetadata', 'promptTokenCount'),
210
+ output_tokens: output_tokens(body),
211
+ cached_tokens: body.dig('usageMetadata', 'cachedContentTokenCount'),
212
+ thinking_tokens: body.dig('usageMetadata', 'thoughtsTokenCount'),
213
+ model_id: body['modelVersion'] || @model,
214
+ raw: body
215
+ )
216
+ end
217
+
218
+ def build_chunk(data)
219
+ parts = response_parts(data)
220
+
221
+ LexLLM::Chunk.new(
222
+ role: :assistant,
223
+ content: text_content(parts),
224
+ tool_calls: parse_tool_calls(parts),
225
+ input_tokens: data.dig('usageMetadata', 'promptTokenCount'),
226
+ output_tokens: output_tokens(data),
227
+ cached_tokens: data.dig('usageMetadata', 'cachedContentTokenCount'),
228
+ thinking_tokens: data.dig('usageMetadata', 'thoughtsTokenCount'),
229
+ model_id: data['modelVersion'] || @model,
230
+ raw: data
231
+ )
232
+ end
233
+
234
+ def response_parts(body)
235
+ body.dig('candidates', 0, 'content', 'parts') || []
236
+ end
237
+
238
+ def text_content(parts)
239
+ text = parts.reject { |part| part['thought'] }.filter_map { |part| part['text'] }.join
240
+ text.empty? ? nil : text
241
+ end
242
+
243
+ def output_tokens(body)
244
+ candidates = body.dig('usageMetadata', 'candidatesTokenCount') || 0
245
+ thoughts = body.dig('usageMetadata', 'thoughtsTokenCount') || 0
246
+ total = candidates + thoughts
247
+ total.positive? ? total : nil
248
+ end
249
+
250
+ def parse_tool_calls(parts)
251
+ tool_calls = parts.each_with_object({}) do |part, result|
252
+ function_call = part['functionCall']
253
+ next unless function_call
254
+
255
+ id = SecureRandom.uuid
256
+ result[id] = LexLLM::ToolCall.new(
257
+ id: id,
258
+ name: function_call['name'],
259
+ arguments: function_call['args'] || {}
260
+ )
261
+ end
262
+
263
+ tool_calls.empty? ? nil : tool_calls
264
+ end
265
+
266
+ def parse_list_models_response(response, provider, capabilities)
267
+ Array(response.body['models']).map do |model_data|
268
+ model_id = model_data.fetch('name').delete_prefix('models/')
269
+ methods = Array(model_data['supportedGenerationMethods'])
270
+
271
+ LexLLM::Model::Info.new(
272
+ id: model_id,
273
+ name: model_data['displayName'] || model_id,
274
+ provider: provider,
275
+ context_window: model_data['inputTokenLimit'],
276
+ max_output_tokens: model_data['outputTokenLimit'],
277
+ capabilities: capabilities.critical_capabilities_for(model_data),
278
+ modalities: modalities_for(methods),
279
+ metadata: {
280
+ version: model_data['version'],
281
+ description: model_data['description'],
282
+ supported_generation_methods: methods
283
+ }
284
+ )
285
+ end
286
+ end
287
+
288
+ def modalities_for(methods)
289
+ return { input: %w[text], output: %w[embeddings] } if methods.include?('embedContent')
290
+
291
+ { input: %w[text image audio video], output: %w[text] }
292
+ end
293
+
294
+ def render_embedding_payload(text, model:, dimensions:)
295
+ {
296
+ model: model_path(model),
297
+ content: { parts: [{ text: text.to_s }] },
298
+ outputDimensionality: dimensions
299
+ }.compact
300
+ end
301
+
302
+ def parse_embedding_response(response, model:, **)
303
+ LexLLM::Embedding.new(
304
+ vectors: response.body.dig('embedding', 'values'),
305
+ model: model,
306
+ input_tokens: response.body.dig('usageMetadata', 'promptTokenCount').to_i
307
+ )
308
+ end
309
+ end
310
+ end
311
+ end
312
+ end
313
+ end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Gemini
7
- VERSION = '0.1.0'
7
+ VERSION = '0.1.1'
8
8
  end
9
9
  end
10
10
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/extensions/llm'
4
- require 'legion/extensions/llm/gemini/provider_settings'
4
+ require 'legion/extensions/llm/gemini/provider'
5
5
  require 'legion/extensions/llm/gemini/version'
6
6
 
7
7
  module Legion
@@ -14,10 +14,10 @@ module Legion
14
14
  PROVIDER_FAMILY = :gemini
15
15
 
16
16
  def self.default_settings
17
- ProviderSettings.build(
17
+ ::Legion::Extensions::Llm.provider_settings(
18
18
  family: PROVIDER_FAMILY,
19
19
  instance: {
20
- endpoint: 'https://generativelanguage.googleapis.com',
20
+ endpoint: 'https://generativelanguage.googleapis.com/v1beta',
21
21
  tier: :frontier,
22
22
  transport: :http,
23
23
  credentials: { api_key: 'env://GEMINI_API_KEY' },
@@ -26,7 +26,14 @@ module Legion
26
26
  }
27
27
  )
28
28
  end
29
+
30
+ def self.provider_class
31
+ Provider
32
+ end
29
33
  end
30
34
  end
31
35
  end
32
36
  end
37
+
38
+ LexLLM::Provider.register(Legion::Extensions::Llm::Gemini::PROVIDER_FAMILY,
39
+ Legion::Extensions::Llm::Gemini::Provider)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm-gemini
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO
@@ -23,20 +23,62 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '2.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: legion-json
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '1.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '1.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: legion-logging
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: legion-settings
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.3.14
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 1.3.14
26
68
  - !ruby/object:Gem::Dependency
27
69
  name: lex-llm
28
70
  requirement: !ruby/object:Gem::Requirement
29
71
  requirements:
30
72
  - - ">="
31
73
  - !ruby/object:Gem::Version
32
- version: 0.1.0
74
+ version: 0.1.1
33
75
  type: :runtime
34
76
  prerelease: false
35
77
  version_requirements: !ruby/object:Gem::Requirement
36
78
  requirements:
37
79
  - - ">="
38
80
  - !ruby/object:Gem::Version
39
- version: 0.1.0
81
+ version: 0.1.1
40
82
  description: Gemini provider integration for the LegionIO LLM routing framework.
41
83
  email:
42
84
  - matthewdiverson@gmail.com
@@ -51,12 +93,11 @@ files:
51
93
  - ".rubocop.yml"
52
94
  - CHANGELOG.md
53
95
  - Gemfile
54
- - Gemfile.lock
55
96
  - LICENSE
56
97
  - README.md
57
98
  - lex-llm-gemini.gemspec
58
99
  - lib/legion/extensions/llm/gemini.rb
59
- - lib/legion/extensions/llm/gemini/provider_settings.rb
100
+ - lib/legion/extensions/llm/gemini/provider.rb
60
101
  - lib/legion/extensions/llm/gemini/version.rb
61
102
  - lib/lex_llm_gemini.rb
62
103
  homepage: https://github.com/LegionIO/lex-llm-gemini
data/Gemfile.lock DELETED
@@ -1,161 +0,0 @@
1
- GIT
2
- remote: https://github.com/LegionIO/lex-llm
3
- revision: 6b2901bf332806ca369399ad08f09da82825b121
4
- branch: lex-llm-routing-base-20260425
5
- specs:
6
- lex-llm (0.1.0)
7
- base64
8
- event_stream_parser (~> 1)
9
- faraday (>= 1.10.0)
10
- faraday-multipart (>= 1)
11
- faraday-net_http (>= 1)
12
- faraday-retry (>= 1)
13
- marcel (~> 1)
14
- ruby_llm-schema (~> 0)
15
- zeitwerk (~> 2)
16
-
17
- PATH
18
- remote: .
19
- specs:
20
- lex-llm-gemini (0.1.0)
21
- faraday (>= 2.0)
22
- lex-llm (>= 0.1.0)
23
-
24
- GEM
25
- remote: https://rubygems.org/
26
- specs:
27
- ast (2.4.3)
28
- base64 (0.3.0)
29
- diff-lcs (1.6.2)
30
- event_stream_parser (1.0.0)
31
- faraday (2.14.1)
32
- faraday-net_http (>= 2.0, < 3.5)
33
- json
34
- logger
35
- faraday-multipart (1.2.0)
36
- multipart-post (~> 2.0)
37
- faraday-net_http (3.4.2)
38
- net-http (~> 0.5)
39
- faraday-retry (2.4.0)
40
- faraday (~> 2.0)
41
- json (2.19.4)
42
- language_server-protocol (3.17.0.5)
43
- lint_roller (1.1.0)
44
- logger (1.7.0)
45
- marcel (1.1.0)
46
- multipart-post (2.4.1)
47
- net-http (0.9.1)
48
- uri (>= 0.11.1)
49
- parallel (2.1.0)
50
- parser (3.3.11.1)
51
- ast (~> 2.4.1)
52
- racc
53
- prism (1.9.0)
54
- racc (1.8.1)
55
- rainbow (3.1.1)
56
- rake (13.4.2)
57
- regexp_parser (2.12.0)
58
- rspec (3.13.2)
59
- rspec-core (~> 3.13.0)
60
- rspec-expectations (~> 3.13.0)
61
- rspec-mocks (~> 3.13.0)
62
- rspec-core (3.13.6)
63
- rspec-support (~> 3.13.0)
64
- rspec-expectations (3.13.5)
65
- diff-lcs (>= 1.2.0, < 2.0)
66
- rspec-support (~> 3.13.0)
67
- rspec-mocks (3.13.8)
68
- diff-lcs (>= 1.2.0, < 2.0)
69
- rspec-support (~> 3.13.0)
70
- rspec-support (3.13.7)
71
- rubocop (1.86.1)
72
- json (~> 2.3)
73
- language_server-protocol (~> 3.17.0.2)
74
- lint_roller (~> 1.1.0)
75
- parallel (>= 1.10)
76
- parser (>= 3.3.0.2)
77
- rainbow (>= 2.2.2, < 4.0)
78
- regexp_parser (>= 2.9.3, < 3.0)
79
- rubocop-ast (>= 1.49.0, < 2.0)
80
- ruby-progressbar (~> 1.7)
81
- unicode-display_width (>= 2.4.0, < 4.0)
82
- rubocop-ast (1.49.1)
83
- parser (>= 3.3.7.2)
84
- prism (~> 1.7)
85
- rubocop-performance (1.26.1)
86
- lint_roller (~> 1.1)
87
- rubocop (>= 1.75.0, < 2.0)
88
- rubocop-ast (>= 1.47.1, < 2.0)
89
- rubocop-rake (0.7.1)
90
- lint_roller (~> 1.1)
91
- rubocop (>= 1.72.1)
92
- rubocop-rspec (3.9.0)
93
- lint_roller (~> 1.1)
94
- rubocop (~> 1.81)
95
- ruby-progressbar (1.13.0)
96
- ruby_llm-schema (0.3.0)
97
- unicode-display_width (3.2.0)
98
- unicode-emoji (~> 4.1)
99
- unicode-emoji (4.2.0)
100
- uri (1.1.1)
101
- zeitwerk (2.7.5)
102
-
103
- PLATFORMS
104
- arm64-darwin-25
105
- ruby
106
-
107
- DEPENDENCIES
108
- bundler (>= 2.0)
109
- lex-llm!
110
- lex-llm-gemini!
111
- rake (>= 13.0)
112
- rspec (~> 3.12)
113
- rubocop (>= 1.0)
114
- rubocop-performance
115
- rubocop-rake (>= 0.6)
116
- rubocop-rspec
117
-
118
- CHECKSUMS
119
- ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
120
- base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
121
- diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
122
- event_stream_parser (1.0.0) sha256=a2683bab70126286f8184dc88f7968ffc4028f813161fb073ec90d171f7de3c8
123
- faraday (2.14.1) sha256=a43cceedc1e39d188f4d2cdd360a8aaa6a11da0c407052e426ba8d3fb42ef61c
124
- faraday-multipart (1.2.0) sha256=7d89a949693714176f612323ca13746a2ded204031a6ba528adee788694ef757
125
- faraday-net_http (3.4.2) sha256=f147758260d3526939bf57ecf911682f94926a3666502e24c69992765875906c
126
- faraday-retry (2.4.0) sha256=7b79c48fb7e56526faf247b12d94a680071ff40c9fda7cf1ec1549439ad11ebe
127
- json (2.19.4) sha256=670a7d333fb3b18ca5b29cb255eb7bef099e40d88c02c80bd42a3f30fe5239ac
128
- language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
129
- lex-llm (0.1.0)
130
- lex-llm-gemini (0.1.0)
131
- lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
132
- logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
133
- marcel (1.1.0) sha256=fdcfcfa33cc52e93c4308d40e4090a5d4ea279e160a7f6af988260fa970e0bee
134
- multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8
135
- net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996
136
- parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
137
- parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54
138
- prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
139
- racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
140
- rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
141
- rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
142
- regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
143
- rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
144
- rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
145
- rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
146
- rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
147
- rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
148
- rubocop (1.86.1) sha256=44415f3f01d01a21e01132248d2fd0867572475b566ca188a0a42133a08d4531
149
- rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
150
- rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834
151
- rubocop-rake (0.7.1) sha256=3797f2b6810c3e9df7376c26d5f44f3475eda59eb1adc38e6f62ecf027cbae4d
152
- rubocop-rspec (3.9.0) sha256=8fa70a3619408237d789aeecfb9beef40576acc855173e60939d63332fdb55e2
153
- ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
154
- ruby_llm-schema (0.3.0) sha256=a591edc5ca1b7f0304f0e2261de61ba4b3bea17be09f5cf7558153adfda3dec6
155
- unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
156
- unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
157
- uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
158
- zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd
159
-
160
- BUNDLED WITH
161
- 4.0.6
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Llm
6
- module Gemini
7
- # Builds provider defaults while inheriting shared lex-llm defaults.
8
- module ProviderSettings
9
- module_function
10
-
11
- def build(family:, instance:)
12
- deep_merge(
13
- base_settings,
14
- {
15
- enabled: true,
16
- provider_family: family,
17
- discovery: { enabled: true, interval_seconds: 300 },
18
- instances: {
19
- default: {
20
- enabled: true,
21
- credentials: nil,
22
- fleet: { enabled: false, consumer_priority: 0, prefetch: 1 }
23
- }.merge(instance)
24
- }
25
- }
26
- )
27
- end
28
-
29
- def base_settings
30
- return {} unless ::Legion::Extensions::Llm.respond_to?(:default_settings)
31
-
32
- deep_dup(::Legion::Extensions::Llm.default_settings)
33
- end
34
-
35
- def deep_dup(value)
36
- case value
37
- when Hash
38
- value.to_h { |key, inner_value| [key, deep_dup(inner_value)] }
39
- when Array
40
- value.map { |inner_value| deep_dup(inner_value) }
41
- else
42
- value
43
- end
44
- end
45
-
46
- def deep_merge(left, right)
47
- left.merge(right) do |_key, left_value, right_value|
48
- left_value.is_a?(Hash) && right_value.is_a?(Hash) ? deep_merge(left_value, right_value) : right_value
49
- end
50
- end
51
- end
52
- end
53
- end
54
- end
55
- end