lex-gemini 0.1.3 → 0.1.5

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: f14c38ec6ddccebe1155a80eeb18a6c94425ceff69892f030fc5decb58f2a0dd
4
- data.tar.gz: 036d065ce267fa459982774f41ca10c62b7a4f9878c03882c5059c65a47db55b
3
+ metadata.gz: ae34d00ad4e874b1b0b71979f4d7fe34632681f7b422626da992fa0ebe42b944
4
+ data.tar.gz: 2943e187a68e5759f67ae512b28e3a8b151307f5e53991d8cef54d67269d3791
5
5
  SHA512:
6
- metadata.gz: 8c6350fd770335e2dc4bacd86bb554bbf52bf881525653a06d6261709d8c54fdc28e4a9f6cab5177843ec7c0dd9a88cb8a39bd13274266c9814cb5c4035359fd
7
- data.tar.gz: 0fbfb97ccd6fb92824bf1e278b7e34f1946a0603ba4e3f838d8b47e06d5d80531b3b5fda16d074332ef0434d07f7432eff06355a79694d9b25cd6713bebbf78a
6
+ metadata.gz: 417aa550be8ad9fa013f66046a264fd52fd9ea60d8ea2f2be6bc9eaae2f8881fd24936c087d0573059b686c9f34f5c6fab1a2bf73868f44f66c55f566f4abd00
7
+ data.tar.gz: 2b6fdc4f9ad52e63bcb2eac57c4fcac09b0a1e92eeab822636b7e667d80919f9c0a813c8d1de5dae22aa9ff424128ded13218d9fe956ee726ef2790b45bc4610
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.5] - 2026-04-06
4
+
5
+ ### Added
6
+ - Credential-only identity module for Phase 8 Broker integration (`Identity` module with `provide_token`)
7
+
8
+ ## [0.1.4] - 2026-03-31
9
+
10
+ ### Added
11
+ - `Helpers::Usage` module with `extract_usage` helper that parses `usageMetadata` from Gemini API responses
12
+ - All runner methods now return a `usage:` key with `input_tokens`, `output_tokens`, `cache_read_tokens`, and `cache_write_tokens`
13
+ - Usage specs for all runners and the new `Helpers::Usage` module (80 examples total, up from 36)
14
+
3
15
  ## [0.1.3] - 2026-03-30
4
16
 
5
17
  ### Changed
data/CLAUDE.md CHANGED
@@ -10,8 +10,8 @@ Legion Extension that connects LegionIO to Google Gemini API. Generate content,
10
10
 
11
11
  **GitHub**: https://github.com/LegionIO/lex-gemini
12
12
  **License**: MIT
13
- **Version**: 0.1.2
14
- **Specs**: 36 examples
13
+ **Version**: 0.1.5
14
+ **Specs**: 88 examples (9 spec files)
15
15
 
16
16
  ## Architecture
17
17
 
@@ -25,19 +25,20 @@ Legion::Extensions::Gemini
25
25
  │ ├── Files # upload(api_key:, file_path:, mime_type:, ...), list, get, delete
26
26
  │ └── CachedContents # create(api_key:, model:, contents:, ...), list, get, update, delete
27
27
  └── Helpers/
28
- └── Client # Faraday-based HTTP client (class, instantiated per-request)
28
+ ├── Client # Faraday-based HTTP client (class, instantiated per-request)
29
+ └── Usage # usage normalization helpers
29
30
  ```
30
31
 
31
- Unlike lex-claude and lex-openai, `Helpers::Client` is a **class** instantiated per-request. Each runner creates `Helpers::Client.new(api_key:, model:)` inline. This means no module-level `extend` is used in runners; instead, runners call `Helpers::Client.new(...)` directly.
32
+ Unlike other extensions in this category, `Helpers::Client` is a **class** instantiated per-request. Each runner creates `Helpers::Client.new(api_key:, model:)` inline. No module-level `extend` is used in runners runners call `Helpers::Client.new(...)` directly.
32
33
 
33
- `include Legion::Extensions::Helpers::Lex` guard: uses `if defined?(Legion::Extensions::Helpers::Lex)` (note: slightly different guard pattern from lex-claude/lex-openai which use `const_defined?`).
34
+ `include Legion::Extensions::Helpers::Lex` guard: uses `if defined?(Legion::Extensions::Helpers::Lex)` (note: slightly different guard pattern from other extensions which use `const_defined?`).
34
35
 
35
36
  ## Key Design Decisions
36
37
 
37
38
  - `Helpers::Client` is a class to allow per-request model selection without global state.
38
39
  - Authentication uses query parameter `?key=<api_key>` set on the Faraday connection params, so all requests automatically include it.
39
40
  - File upload falls back to raw binary upload (`X-Goog-Upload-Protocol: raw`) when `faraday-multipart` is not loaded.
40
- - `Helpers::Client#handle_response` returns the raw body on success and `{ error: body, status: code }` on failure. All runners wrap the `Helpers::Client` return value in `{ result: ... }`, so the actual runner-level return shape is `{ result: <body> }` on success and `{ result: { error: ..., status: ... } }` on failure.
41
+ - `Helpers::Client#handle_response` returns the raw body on success and `{ error: body, status: code }` on failure. All runners wrap the return value in `{ result: ... }`.
41
42
  - `gemini-2.0-flash` is the default model for Content, Tokens, and `Helpers::Client` initialization.
42
43
  - `gemini-embedding-exp` is the default model for Embeddings runners.
43
44
 
@@ -51,15 +52,17 @@ Unlike lex-claude and lex-openai, `Helpers::Client` is a **class** instantiated
51
52
  |-----|---------|
52
53
  | `faraday` >= 2.0 | HTTP client for Gemini REST API |
53
54
  | `faraday-multipart` | File uploads (optional — falls back to raw binary upload if not available) |
55
+ | `legion-cache`, `legion-crypt`, `legion-data`, `legion-json`, `legion-logging`, `legion-settings`, `legion-transport` | LegionIO core |
54
56
 
55
57
  ## Testing
56
58
 
57
59
  ```bash
58
60
  bundle install
59
- bundle exec rspec # 36 examples
61
+ bundle exec rspec # 88 examples
60
62
  bundle exec rubocop
61
63
  ```
62
64
 
63
65
  ---
64
66
 
65
67
  **Maintained By**: Matthew Iverson (@Esity)
68
+ **Last Updated**: 2026-04-06
data/README.md CHANGED
@@ -109,6 +109,10 @@ puts tokens['totalTokens']
109
109
  - `legion-llm` — High-level LLM interface including Gemini via ruby_llm
110
110
  - `extensions-ai/CLAUDE.md` — Architecture patterns shared across all AI extensions
111
111
 
112
+ ## Version
113
+
114
+ 0.1.5
115
+
112
116
  ## License
113
117
 
114
118
  MIT
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Gemini
6
+ module Helpers
7
+ module Usage
8
+ def extract_usage(body)
9
+ return zero_usage unless body.is_a?(Hash)
10
+
11
+ {
12
+ input_tokens: body.dig('usageMetadata', 'promptTokenCount') || 0,
13
+ output_tokens: body.dig('usageMetadata', 'candidatesTokenCount') || 0,
14
+ cache_read_tokens: body.dig('usageMetadata', 'cachedContentTokenCount') || 0,
15
+ cache_write_tokens: 0
16
+ }
17
+ end
18
+
19
+ private
20
+
21
+ def zero_usage
22
+ { input_tokens: 0, output_tokens: 0, cache_read_tokens: 0, cache_write_tokens: 0 }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Gemini
6
+ module Identity
7
+ module_function
8
+
9
+ def provider_name = :gemini
10
+ def provider_type = :credential
11
+ def facing = nil
12
+ def capabilities = %i[credentials]
13
+
14
+ def resolve(canonical_name: nil) # rubocop:disable Lint/UnusedMethodArgument
15
+ nil
16
+ end
17
+
18
+ def provide_token
19
+ api_key = resolve_api_key
20
+ return nil unless api_key
21
+
22
+ Legion::Identity::Lease.new(
23
+ provider: :gemini,
24
+ credential: api_key,
25
+ expires_at: nil,
26
+ renewable: false,
27
+ issued_at: Time.now,
28
+ metadata: { credential_type: :api_key }
29
+ )
30
+ end
31
+
32
+ def resolve_api_key
33
+ return nil unless defined?(Legion::Settings)
34
+
35
+ value = Legion::Settings.dig(:llm, :providers, :gemini, :api_key)
36
+ value = value.find { |v| v && !v.empty? } if value.is_a?(Array)
37
+ value unless value.nil? || (value.is_a?(String) && (value.empty? || value.start_with?('env://')))
38
+ end
39
+
40
+ private_class_method :resolve_api_key
41
+ end
42
+ end
43
+ end
44
+ end
@@ -6,31 +6,37 @@ module Legion
6
6
  module Runners
7
7
  module CachedContents
8
8
  include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
9
+ include Helpers::Usage
9
10
 
10
11
  def create(api_key:, model:, contents:, ttl: nil, expire_time: nil, display_name: nil, system_instruction: nil, **)
11
12
  client = Helpers::Client.new(api_key: api_key)
12
- { result: client.create_cached_content(model: model, contents: contents, ttl: ttl, expire_time: expire_time,
13
- display_name: display_name, system_instruction: system_instruction) }
13
+ body = client.create_cached_content(model: model, contents: contents, ttl: ttl, expire_time: expire_time,
14
+ display_name: display_name, system_instruction: system_instruction)
15
+ { result: body, usage: extract_usage(body) }
14
16
  end
15
17
 
16
18
  def list(api_key:, page_size: nil, page_token: nil, **)
17
19
  client = Helpers::Client.new(api_key: api_key)
18
- { result: client.list_cached_contents(page_size: page_size, page_token: page_token) }
20
+ body = client.list_cached_contents(page_size: page_size, page_token: page_token)
21
+ { result: body, usage: extract_usage(body) }
19
22
  end
20
23
 
21
24
  def get(api_key:, name:, **)
22
25
  client = Helpers::Client.new(api_key: api_key)
23
- { result: client.get_cached_content(name: name) }
26
+ body = client.get_cached_content(name: name)
27
+ { result: body, usage: extract_usage(body) }
24
28
  end
25
29
 
26
30
  def update(api_key:, name:, ttl: nil, expire_time: nil, **)
27
31
  client = Helpers::Client.new(api_key: api_key)
28
- { result: client.update_cached_content(name: name, ttl: ttl, expire_time: expire_time) }
32
+ body = client.update_cached_content(name: name, ttl: ttl, expire_time: expire_time)
33
+ { result: body, usage: extract_usage(body) }
29
34
  end
30
35
 
31
36
  def delete(api_key:, name:, **)
32
37
  client = Helpers::Client.new(api_key: api_key)
33
- { result: client.delete_cached_content(name: name) }
38
+ body = client.delete_cached_content(name: name)
39
+ { result: body, usage: extract_usage(body) }
34
40
  end
35
41
  end
36
42
  end
@@ -6,20 +6,23 @@ module Legion
6
6
  module Runners
7
7
  module Content
8
8
  include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
9
+ include Helpers::Usage
9
10
 
10
11
  def generate(api_key:, contents:, model: 'gemini-2.0-flash', generation_config: nil, safety_settings: nil,
11
12
  system_instruction: nil, **)
12
13
  client = Helpers::Client.new(api_key: api_key, model: model)
13
- { result: client.generate_content(contents: contents, generation_config: generation_config,
14
- safety_settings: safety_settings, system_instruction: system_instruction) }
14
+ body = client.generate_content(contents: contents, generation_config: generation_config,
15
+ safety_settings: safety_settings, system_instruction: system_instruction)
16
+ { result: body, usage: extract_usage(body) }
15
17
  end
16
18
 
17
19
  def stream_generate(api_key:, contents:, model: 'gemini-2.0-flash', generation_config: nil, safety_settings: nil,
18
20
  system_instruction: nil, **)
19
21
  client = Helpers::Client.new(api_key: api_key, model: model)
20
- { result: client.stream_generate_content(contents: contents, generation_config: generation_config,
21
- safety_settings: safety_settings,
22
- system_instruction: system_instruction) }
22
+ body = client.stream_generate_content(contents: contents, generation_config: generation_config,
23
+ safety_settings: safety_settings,
24
+ system_instruction: system_instruction)
25
+ { result: body, usage: extract_usage(body) }
23
26
  end
24
27
  end
25
28
  end
@@ -6,15 +6,18 @@ module Legion
6
6
  module Runners
7
7
  module Embeddings
8
8
  include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
9
+ include Helpers::Usage
9
10
 
10
11
  def embed(api_key:, content:, model: 'gemini-embedding-exp', task_type: nil, title: nil, **)
11
12
  client = Helpers::Client.new(api_key: api_key, model: model)
12
- { result: client.embed_content(content: content, task_type: task_type, title: title) }
13
+ body = client.embed_content(content: content, task_type: task_type, title: title)
14
+ { result: body, usage: extract_usage(body) }
13
15
  end
14
16
 
15
17
  def batch_embed(api_key:, requests:, model: 'gemini-embedding-exp', **)
16
18
  client = Helpers::Client.new(api_key: api_key, model: model)
17
- { result: client.batch_embed_contents(requests: requests) }
19
+ body = client.batch_embed_contents(requests: requests)
20
+ { result: body, usage: extract_usage(body) }
18
21
  end
19
22
  end
20
23
  end
@@ -6,25 +6,30 @@ module Legion
6
6
  module Runners
7
7
  module Files
8
8
  include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
9
+ include Helpers::Usage
9
10
 
10
11
  def upload(api_key:, file_path:, mime_type:, display_name: nil, **)
11
12
  client = Helpers::Client.new(api_key: api_key)
12
- { result: client.upload_file(file_path: file_path, mime_type: mime_type, display_name: display_name) }
13
+ body = client.upload_file(file_path: file_path, mime_type: mime_type, display_name: display_name)
14
+ { result: body, usage: extract_usage(body) }
13
15
  end
14
16
 
15
17
  def list(api_key:, page_size: nil, page_token: nil, **)
16
18
  client = Helpers::Client.new(api_key: api_key)
17
- { result: client.list_files(page_size: page_size, page_token: page_token) }
19
+ body = client.list_files(page_size: page_size, page_token: page_token)
20
+ { result: body, usage: extract_usage(body) }
18
21
  end
19
22
 
20
23
  def get(api_key:, name:, **)
21
24
  client = Helpers::Client.new(api_key: api_key)
22
- { result: client.get_file(name: name) }
25
+ body = client.get_file(name: name)
26
+ { result: body, usage: extract_usage(body) }
23
27
  end
24
28
 
25
29
  def delete(api_key:, name:, **)
26
30
  client = Helpers::Client.new(api_key: api_key)
27
- { result: client.delete_file(name: name) }
31
+ body = client.delete_file(name: name)
32
+ { result: body, usage: extract_usage(body) }
28
33
  end
29
34
  end
30
35
  end
@@ -6,15 +6,18 @@ module Legion
6
6
  module Runners
7
7
  module Models
8
8
  include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
9
+ include Helpers::Usage
9
10
 
10
11
  def list(api_key:, **)
11
12
  client = Helpers::Client.new(api_key: api_key)
12
- { result: client.list_models }
13
+ body = client.list_models
14
+ { result: body, usage: extract_usage(body) }
13
15
  end
14
16
 
15
17
  def get(api_key:, name:, **)
16
18
  client = Helpers::Client.new(api_key: api_key)
17
- { result: client.get_model(name: name) }
19
+ body = client.get_model(name: name)
20
+ { result: body, usage: extract_usage(body) }
18
21
  end
19
22
  end
20
23
  end
@@ -6,10 +6,12 @@ module Legion
6
6
  module Runners
7
7
  module Tokens
8
8
  include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
9
+ include Helpers::Usage
9
10
 
10
11
  def count(api_key:, contents:, model: 'gemini-2.0-flash', **)
11
12
  client = Helpers::Client.new(api_key: api_key, model: model)
12
- { result: client.count_tokens(contents: contents) }
13
+ body = client.count_tokens(contents: contents)
14
+ { result: body, usage: extract_usage(body) }
13
15
  end
14
16
  end
15
17
  end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Gemini
6
- VERSION = '0.1.3'
6
+ VERSION = '0.1.5'
7
7
  end
8
8
  end
9
9
  end
@@ -2,12 +2,14 @@
2
2
 
3
3
  require 'legion/extensions/gemini/version'
4
4
  require 'legion/extensions/gemini/helpers/client'
5
+ require 'legion/extensions/gemini/helpers/usage'
5
6
  require 'legion/extensions/gemini/runners/content'
6
7
  require 'legion/extensions/gemini/runners/embeddings'
7
8
  require 'legion/extensions/gemini/runners/models'
8
9
  require 'legion/extensions/gemini/runners/tokens'
9
10
  require 'legion/extensions/gemini/runners/files'
10
11
  require 'legion/extensions/gemini/runners/cached_contents'
12
+ require 'legion/extensions/gemini/identity'
11
13
 
12
14
  module Legion
13
15
  module Extensions
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-gemini
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -143,6 +143,8 @@ files:
143
143
  - lex-gemini.gemspec
144
144
  - lib/legion/extensions/gemini.rb
145
145
  - lib/legion/extensions/gemini/helpers/client.rb
146
+ - lib/legion/extensions/gemini/helpers/usage.rb
147
+ - lib/legion/extensions/gemini/identity.rb
146
148
  - lib/legion/extensions/gemini/runners/cached_contents.rb
147
149
  - lib/legion/extensions/gemini/runners/content.rb
148
150
  - lib/legion/extensions/gemini/runners/embeddings.rb