lex-llm-openai 0.2.0 → 0.3.6

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: 3adf2416815789eee25cb13dc0ceaadda14549fa6f897ea8c0216a5ebd728793
4
- data.tar.gz: 317ad6bcc34b252a9026e9aaae5a9a6fe8f850ab67c7e50e62f67a5c43d3538f
3
+ metadata.gz: 8cc102a70c5d99414bb836b6b9204a1c031a9f98072b5430e824b2ae944cd44a
4
+ data.tar.gz: 0d6cd458a0812c8767bef92785026a17ae1fa465aecd84801cbca27070570706
5
5
  SHA512:
6
- metadata.gz: 6e5b1684b8a9a328278bb0ab039940f924f43943847fe45d0b485c82702f4cbb426b142e751d407ed736778b9923bfffd8e8b323549bef335c99056f989c4250
7
- data.tar.gz: 0ff1be0c6ccd2fa4e24a3c9bb8dc816e6dc249ff6a88177240be0f5fc4aedade94cb9955d752f28570af099a64e2dea232e3d7bdef0587ab7ef8a308dbe23684
6
+ metadata.gz: db59247eafd9bd082806e464602315660dd7b3d1871c894acc85dae76db7ad9f5a58a94284c3b47b2fd5cc923dee9e76101f9c1df064bb18718d474999fa0d28
7
+ data.tar.gz: 35f02198724ba77c8b2a329c11c5aa9fe0d106d2a93a140f0a7d88041246d6c01c09d9a23a354d844638041744526280b671c186770c77ab3f2ccae7dc5d9950
@@ -8,8 +8,20 @@ jobs:
8
8
  ci:
9
9
  uses: LegionIO/.github/.github/workflows/ci.yml@main
10
10
 
11
+ excluded-files:
12
+ uses: LegionIO/.github/.github/workflows/excluded-files.yml@main
13
+
14
+ security:
15
+ uses: LegionIO/.github/.github/workflows/security-scan.yml@main
16
+
17
+ version-changelog:
18
+ uses: LegionIO/.github/.github/workflows/version-changelog.yml@main
19
+
20
+ dependency-review:
21
+ uses: LegionIO/.github/.github/workflows/dependency-review.yml@main
22
+
11
23
  release:
12
- needs: ci
24
+ needs: [ci, excluded-files, security]
13
25
  if: github.event_name == 'push' && github.ref == 'refs/heads/main'
14
26
  uses: LegionIO/.github/.github/workflows/release.yml@main
15
27
  secrets:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.6 - 2026-05-06
4
+
5
+ - Load provider-owned fleet actors through the LegionIO subscription base and the canonical OpenAI provider root.
6
+ - Keep fleet runners anchored on the provider root namespace so provider constants and instance discovery are always loaded.
7
+ - Strip temporary generic API key, organization, and project fields from discovered OpenAI instance configs after credential deduplication.
8
+ - Gate release publishing on the shared security workflow.
9
+
10
+ ## 0.3.5 - 2026-05-06
11
+
12
+ - Advertise OpenAI moderation and audio usage in the default provider instance settings.
13
+ - Refresh README architecture and verification guidance for the shared `lex-llm` registry and fleet responder boundary.
14
+
15
+ ## 0.3.4 - 2026-05-06
16
+
17
+ - Use the shared `lex-llm` fleet provider responder helper for provider-owned fleet workers.
18
+ - Remove the runtime `legion-llm` dependency and require `lex-llm >= 0.4.3` for responder-side fleet execution.
19
+
20
+ ## 0.3.3 - 2026-05-06
21
+
22
+ - Remove require-time provider self-registration; `legion-llm` now owns adapter creation and registry writes from loaded provider discovery metadata.
23
+ - Bump dependency floors to `lex-llm >= 0.4.1` and `legion-llm >= 0.9.1`.
24
+
25
+ ## 0.3.2 - 2026-05-06
26
+
27
+ - Add provider contract specs for the shared keyword-only `lex-llm` provider API.
28
+ - Move OpenAI defaults back to `Legion::Extensions::Llm.provider_settings` with credentials and instance-level fleet responder settings.
29
+ - Remove `gateways` discovery; OpenAI-compatible targets are now named provider instances.
30
+ - Add provider-owned fleet responder actor and runner backed by `legion-llm` fleet policy execution.
31
+ - Bump the transport dependency floor to `legion-transport >= 1.4.14`.
32
+
33
+ ## 0.3.1 - 2026-05-03
34
+
35
+ - Normalize generic settings keys to OpenAI provider config keys during extension and gateway instance discovery.
36
+
37
+ ## 0.3.0 - 2026-05-01
38
+
39
+ - Add auto-discovery via CredentialSources and AutoRegistration from lex-llm 0.3.0
40
+ - Self-register discovered instances into Call::Registry at require-time
41
+ - Require lex-llm >= 0.3.0
42
+
43
+
3
44
  ## [0.2.0] - 2026-04-30
4
45
  - **BREAKING**: Adopt base contract from lex-llm 0.1.9; require `lex-llm >= 0.1.9`
5
46
  - Replace `provider_settings`-based `default_settings` with flat provider defaults (enabled, default_model, api_key, etc.)
data/Gemfile CHANGED
@@ -4,6 +4,8 @@ source 'https://rubygems.org'
4
4
 
5
5
  group :test do
6
6
  llm_base_path = ENV.fetch('LEX_LLM_PATH', File.expand_path('../lex-llm', __dir__))
7
+ transport_path = ENV.fetch('LEGION_TRANSPORT_PATH', File.expand_path('../../legion-transport', __dir__))
8
+ gem 'legion-transport', path: transport_path if File.directory?(transport_path)
7
9
  gem 'lex-llm', path: llm_base_path if File.directory?(llm_base_path)
8
10
  end
9
11
 
data/README.md CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  LegionIO LLM provider extension for OpenAI.
4
4
 
5
- This gem lives under `Legion::Extensions::Llm::Openai` and depends on `lex-llm` for shared provider-neutral routing, fleet, and schema primitives.
5
+ This gem lives under `Legion::Extensions::Llm::Openai` and depends on `lex-llm >= 0.4.3` for shared provider-neutral routing, response normalization, fleet envelopes, fleet responder execution, and schema primitives.
6
6
 
7
7
  Load it with `require 'legion/extensions/llm/openai'`.
8
8
 
9
9
  ## What It Provides
10
10
 
11
- - `Legion::Extensions::Llm::Provider` registration as `:openai`
11
+ - OpenAI provider discovery under the `:openai` provider family
12
12
  - Chat completions via `POST /v1/chat/completions`
13
13
  - Streaming chat completions (same endpoint, `stream: true`)
14
14
  - Model discovery via `GET /v1/models`
@@ -24,28 +24,30 @@ Load it with `require 'legion/extensions/llm/openai'`.
24
24
  - Normalized chat, embedding, moderation, image, and audio capability mapping for discovered models
25
25
  - Shared fleet/default settings via `Legion::Extensions::Llm.provider_settings`
26
26
  - Best-effort `llm.registry` availability event publishing for discovered models
27
+ - Provider-owned fleet request handling through `Legion::Extensions::Llm::Fleet::ProviderResponder`
27
28
 
28
29
  ## Architecture
29
30
 
30
31
  ```
31
32
  Legion::Extensions::Llm::Openai
32
- ├── Provider # OpenAI provider implementation (chat, models, embeddings, etc.)
33
- └── Capabilities # Model family capability predicates
34
- ├── RegistryPublisher # Async llm.registry event publisher
35
- ├── RegistryEventBuilder # Sanitized registry event envelope builder
36
- └── Transport
37
- ├── Exchanges::LlmRegistry # Topic exchange for llm.registry
38
- └── Messages::RegistryEvent # AMQP message for registry events
33
+ |-- Provider # OpenAI provider implementation (chat, models, embeddings, etc.)
34
+ | `-- Capabilities # Model family capability predicates
35
+ |-- Actor::FleetWorker # Subscription actor gate for provider-owned fleet requests
36
+ `-- Runners::FleetWorker # Delegates request execution to lex-llm ProviderResponder
39
37
  ```
40
38
 
39
+ Registry publishing, event envelope construction, fleet protocol handling, and fleet response/error transport live in `lex-llm`. This provider intentionally does not depend on `legion-llm` at runtime.
40
+
41
41
  ## Observability
42
42
 
43
- All classes include `Legion::Logging::Helper` (when available) providing:
43
+ The provider and root extension namespace use `Legion::Logging::Helper` for:
44
44
 
45
45
  - Structured `handle_exception` calls on every rescue block
46
- - Info-level action logging for provider registration, model listing, model retrieval, and registry publishing
46
+ - Info-level action logging for model listing, model retrieval, and registry publishing
47
47
  - Automatic log segment derivation and component type tagging
48
48
 
49
+ Fleet actor and runner code stays thin and delegates execution, ack/reject handling, and response publication to the shared `lex-llm` responder helper.
50
+
49
51
  ## Defaults
50
52
 
51
53
  ```ruby
@@ -85,18 +87,38 @@ end
85
87
 
86
88
  | Gem | Purpose |
87
89
  |-----|---------|
88
- | `lex-llm` (>= 0.1.5) | Shared provider contract, fleet settings, routing |
90
+ | `lex-llm` (>= 0.4.3) | Shared provider contract, response normalization, fleet settings, routing, and fleet responder execution |
91
+ | `legion-transport` (>= 1.4.14) | AMQP subscriptions and replies |
89
92
  | `legion-json` (>= 1.2.1) | JSON serialization |
90
93
  | `legion-logging` (>= 1.3.2) | Structured logging via Helper |
91
94
  | `legion-settings` (>= 1.3.14) | Configuration management |
92
95
 
96
+ ## Fleet Responder
97
+
98
+ Provider instances can opt in to consuming Legion LLM fleet requests. The provider-owned fleet actor only starts when at least one configured instance enables `respond_to_requests`, and the runner delegates execution to `Legion::Extensions::Llm::Fleet::ProviderResponder` from `lex-llm`.
99
+
100
+ ```yaml
101
+ extensions:
102
+ llm:
103
+ openai:
104
+ instances:
105
+ local:
106
+ fleet:
107
+ enabled: true
108
+ respond_to_requests: true
109
+ capabilities:
110
+ - chat
111
+ - stream_chat
112
+ - embed
113
+ - image
114
+ ```
115
+
93
116
  ## Development
94
117
 
95
118
  ```bash
96
119
  bundle install
97
- bundle exec rspec --format progress # all pass
98
- bundle exec rubocop -A # auto-fix
99
- bundle exec rubocop # lint check (0 offenses)
120
+ bundle exec rspec --format json --out tmp/rspec_results.json --format progress --out tmp/rspec_progress.txt
121
+ bundle exec rubocop -A
100
122
  ```
101
123
 
102
124
  ## License
@@ -26,5 +26,6 @@ Gem::Specification.new do |spec|
26
26
  spec.add_dependency 'legion-json', '>= 1.2.1'
27
27
  spec.add_dependency 'legion-logging', '>= 1.3.2'
28
28
  spec.add_dependency 'legion-settings', '>= 1.3.14'
29
- spec.add_dependency 'lex-llm', '>= 0.2.0'
29
+ spec.add_dependency 'legion-transport', '>= 1.4.14'
30
+ spec.add_dependency 'lex-llm', '>= 0.4.3'
30
31
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'legion/extensions/actors/subscription'
5
+ rescue LoadError => e
6
+ warn(e.message) if $VERBOSE
7
+ end
8
+
9
+ unless defined?(Legion::Extensions::Actors::Subscription)
10
+ raise LoadError, 'LegionIO actor runtime is required for OpenAI fleet worker'
11
+ end
12
+
13
+ require 'legion/extensions/llm/openai'
14
+ require 'legion/extensions/llm/fleet/provider_responder'
15
+
16
+ module Legion
17
+ module Extensions
18
+ module Llm
19
+ module Openai
20
+ module Actor
21
+ # Subscription actor for OpenAI fleet request consumption.
22
+ class FleetWorker < Legion::Extensions::Actors::Subscription
23
+ def runner_class
24
+ 'Legion::Extensions::Llm::Openai::Runners::FleetWorker'
25
+ end
26
+
27
+ def runner_function
28
+ 'handle_fleet_request'
29
+ end
30
+
31
+ def use_runner?
32
+ false
33
+ end
34
+
35
+ def enabled?
36
+ Legion::Extensions::Llm::Fleet::ProviderResponder.enabled_for?(Openai.discover_instances)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -244,8 +244,3 @@ module Legion
244
244
  end
245
245
  end
246
246
  end
247
-
248
- # Register configuration options so Legion::Extensions::Llm::Configuration knows about them.
249
- Legion::Extensions::Llm::Configuration.register_provider_options(
250
- Legion::Extensions::Llm::Openai::Provider.configuration_options
251
- )
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/llm/fleet/provider_responder'
4
+ require 'legion/extensions/llm/openai'
5
+
6
+ module Legion
7
+ module Extensions
8
+ module Llm
9
+ module Openai
10
+ module Runners
11
+ # Runner entrypoint for OpenAI fleet request execution.
12
+ module FleetWorker
13
+ module_function
14
+
15
+ def handle_fleet_request(payload, delivery: nil, properties: nil)
16
+ Legion::Extensions::Llm::Fleet::ProviderResponder.call(
17
+ payload: payload,
18
+ provider_family: Openai::PROVIDER_FAMILY,
19
+ provider_class: Openai::Provider,
20
+ provider_instances: -> { Openai.discover_instances },
21
+ delivery: delivery,
22
+ properties: properties
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Openai
7
- VERSION = '0.2.0'
7
+ VERSION = '0.3.6'
8
8
  end
9
9
  end
10
10
  end
@@ -11,27 +11,123 @@ module Legion
11
11
  module Openai
12
12
  extend ::Legion::Extensions::Core if ::Legion::Extensions.const_defined?(:Core, false)
13
13
  extend ::Legion::Logging::Helper
14
+ extend Legion::Extensions::Llm::AutoRegistration
14
15
 
15
16
  PROVIDER_FAMILY = :openai
16
17
 
17
18
  def self.default_settings
18
- {
19
- enabled: false,
20
- default_model: 'gpt-4o',
21
- api_key: nil,
22
- organization_id: nil,
23
- project_id: nil,
24
- model_whitelist: [],
25
- model_blacklist: [],
26
- model_cache_ttl: 3600,
27
- tls: { enabled: false, verify: :peer },
28
- instances: {}
29
- }
19
+ ::Legion::Extensions::Llm.provider_settings(
20
+ family: PROVIDER_FAMILY,
21
+ instance: {
22
+ endpoint: 'https://api.openai.com',
23
+ default_model: 'gpt-4o',
24
+ tier: :frontier,
25
+ transport: :http,
26
+ credentials: {
27
+ api_key: 'env://OPENAI_API_KEY',
28
+ organization_id: nil,
29
+ project_id: nil
30
+ },
31
+ usage: {
32
+ inference: true,
33
+ embedding: true,
34
+ moderation: true,
35
+ image: true,
36
+ audio: true
37
+ },
38
+ limits: { concurrency: 4 },
39
+ fleet: {
40
+ enabled: false,
41
+ respond_to_requests: false,
42
+ capabilities: %i[chat stream_chat embed image],
43
+ lanes: [],
44
+ concurrency: 4,
45
+ queue_suffix: nil
46
+ }
47
+ }
48
+ )
30
49
  end
31
50
 
32
51
  def self.provider_class
33
52
  Provider
34
53
  end
54
+
55
+ def self.discover_instances # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
56
+ candidates = {}
57
+
58
+ # 1. OPENAI_API_KEY environment variable
59
+ env_key = CredentialSources.env('OPENAI_API_KEY')
60
+ candidates[:env] = { api_key: env_key, openai_api_key: env_key, tier: :frontier } if env_key
61
+
62
+ # 2. CODEX_API_KEY environment variable
63
+ codex_env_key = CredentialSources.env('CODEX_API_KEY')
64
+ if codex_env_key
65
+ candidates[:codex_env] = { api_key: codex_env_key, openai_api_key: codex_env_key, tier: :frontier }
66
+ end
67
+
68
+ # 3. Codex bearer token (~/.codex/auth.json chatgpt mode)
69
+ codex_tok = CredentialSources.codex_token
70
+ candidates[:codex] = { api_key: codex_tok, openai_api_key: codex_tok, tier: :frontier } if codex_tok
71
+
72
+ # 4. Codex OPENAI_API_KEY from ~/.codex/auth.json
73
+ codex_key = CredentialSources.codex_openai_key
74
+ candidates[:codex_key] = { api_key: codex_key, openai_api_key: codex_key, tier: :frontier } if codex_key
75
+
76
+ # 5. Claude config openaiApiKey
77
+ claude_key = CredentialSources.claude_config_value(:openaiApiKey)
78
+ candidates[:claude] = { api_key: claude_key, openai_api_key: claude_key, tier: :frontier } if claude_key
79
+
80
+ # 6. Extension settings
81
+ settings_config = CredentialSources.setting(:extensions, :llm, :openai)
82
+ if settings_config.is_a?(Hash) && !settings_config.empty?
83
+ settings_key = settings_config[:api_key] || settings_config['api_key']
84
+ if settings_key
85
+ candidates[:settings] = normalize_instance_config(settings_config).merge(
86
+ api_key: settings_key,
87
+ openai_api_key: settings_key,
88
+ tier: :frontier
89
+ )
90
+ end
91
+ end
92
+
93
+ # 7. Named provider instances from extension settings
94
+ settings_instances(settings_config).each do |name, config|
95
+ next unless config.is_a?(Hash)
96
+
97
+ normalized = normalize_instance_config(config)
98
+ dedup_key = normalized[:openai_api_key]
99
+ normalized[:api_key] = dedup_key if dedup_key
100
+ candidates[name.to_sym] = normalized.merge(tier: :frontier)
101
+ end
102
+
103
+ # 8. Dedup
104
+ CredentialSources.dedup_credentials(candidates).transform_values { |config| sanitize_instance_config(config) }
105
+ end
106
+
107
+ def self.settings_instances(config)
108
+ return {} unless config.is_a?(Hash)
109
+
110
+ instances = config[:instances] || config['instances']
111
+ instances.is_a?(Hash) ? instances : {}
112
+ end
113
+
114
+ def self.normalize_instance_config(config) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
115
+ normalized = config.to_h.transform_keys { |key| key.respond_to?(:to_sym) ? key.to_sym : key }
116
+ normalized[:openai_api_key] ||= normalized.delete(:api_key)
117
+ normalized[:openai_api_base] ||= normalized.delete(:base_url)
118
+ normalized[:openai_api_base] ||= normalized.delete(:api_base)
119
+ normalized[:openai_api_base] ||= normalized.delete(:endpoint)
120
+ normalized[:openai_organization_id] ||= normalized.delete(:organization_id)
121
+ normalized[:openai_project_id] ||= normalized.delete(:project_id)
122
+ normalized.compact.except(:instances)
123
+ end
124
+
125
+ def self.sanitize_instance_config(config)
126
+ config.except(:api_key, :organization_id, :project_id)
127
+ end
128
+
129
+ Legion::Extensions::Llm::Configuration.register_provider_options(Provider.configuration_options) if
130
+ Legion::Extensions::Llm::Configuration.respond_to?(:register_provider_options)
35
131
  end
36
132
  end
37
133
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm-openai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO
@@ -51,20 +51,34 @@ dependencies:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
53
  version: 1.3.14
54
+ - !ruby/object:Gem::Dependency
55
+ name: legion-transport
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.4.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.4.14
54
68
  - !ruby/object:Gem::Dependency
55
69
  name: lex-llm
56
70
  requirement: !ruby/object:Gem::Requirement
57
71
  requirements:
58
72
  - - ">="
59
73
  - !ruby/object:Gem::Version
60
- version: 0.2.0
74
+ version: 0.4.3
61
75
  type: :runtime
62
76
  prerelease: false
63
77
  version_requirements: !ruby/object:Gem::Requirement
64
78
  requirements:
65
79
  - - ">="
66
80
  - !ruby/object:Gem::Version
67
- version: 0.2.0
81
+ version: 0.4.3
68
82
  description: OpenAI provider integration for the LegionIO LLM routing framework.
69
83
  email:
70
84
  - matthewdiverson@gmail.com
@@ -83,7 +97,9 @@ files:
83
97
  - README.md
84
98
  - lex-llm-openai.gemspec
85
99
  - lib/legion/extensions/llm/openai.rb
100
+ - lib/legion/extensions/llm/openai/actors/fleet_worker.rb
86
101
  - lib/legion/extensions/llm/openai/provider.rb
102
+ - lib/legion/extensions/llm/openai/runners/fleet_worker.rb
87
103
  - lib/legion/extensions/llm/openai/version.rb
88
104
  homepage: https://github.com/LegionIO/lex-llm-openai
89
105
  licenses: