lex-llm-openai 0.3.0 → 0.3.7

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: 3cd48c5acd857910771717afe92892f537270d62a6f6d24d0f71e2b5805abb9d
4
- data.tar.gz: c8f1a0644cb7cf1fe993f6d17b5aba3167e56d8f44f04fe0e0d6afe8d70a7146
3
+ metadata.gz: f49ee94d59da872cc2bb8095bbe619329caecf38f178f1b4c183c626f1f8168c
4
+ data.tar.gz: 674527b56d8bb6c72a0052bdad2254356e635926b37a1810bea56563f94418be
5
5
  SHA512:
6
- metadata.gz: 2f5a9d6f495bb5fac304ef891d5f2f93353ff0d6909c54c506a448030e67791f7b79fbffb546e8b7cf94b5d8d8eef1f4b8122bd5df7115e47ff537baa33e8483
7
- data.tar.gz: 7f40adfce35ab368b2db2d82a603fe48f2de269e13a093cab8adc15a946b80e2d02792fd97cf400895f55a18529f140d630e7dd4264d1f0799aa3328dd71908d
6
+ metadata.gz: 7dd8317307365087f1279f25f3c263d5dc57dc7c73487f9073d6c77d8a542f335aaf3ed2a4b176f9aee9a1ec59bd5f6269a2cf27845dff9cb73dd8ddbb898833
7
+ data.tar.gz: 88c350e3870a694eabde6c04580191bc11dcc0f48171df3d37c31f91b8b0c97800a411d78f65d0856304e7710b5420f83ee66514c230ba3a22f75f9afe0b062b
@@ -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,43 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.7 - 2026-05-08
4
+
5
+ - Accept keyword arguments in `list_models` to match the base provider contract called by `discover_offerings`.
6
+
7
+ ## 0.3.6 - 2026-05-06
8
+
9
+ - Load provider-owned fleet actors through the LegionIO subscription base and the canonical OpenAI provider root.
10
+ - Keep fleet runners anchored on the provider root namespace so provider constants and instance discovery are always loaded.
11
+ - Strip temporary generic API key, organization, and project fields from discovered OpenAI instance configs after credential deduplication.
12
+ - Gate release publishing on the shared security workflow.
13
+
14
+ ## 0.3.5 - 2026-05-06
15
+
16
+ - Advertise OpenAI moderation and audio usage in the default provider instance settings.
17
+ - Refresh README architecture and verification guidance for the shared `lex-llm` registry and fleet responder boundary.
18
+
19
+ ## 0.3.4 - 2026-05-06
20
+
21
+ - Use the shared `lex-llm` fleet provider responder helper for provider-owned fleet workers.
22
+ - Remove the runtime `legion-llm` dependency and require `lex-llm >= 0.4.3` for responder-side fleet execution.
23
+
24
+ ## 0.3.3 - 2026-05-06
25
+
26
+ - Remove require-time provider self-registration; `legion-llm` now owns adapter creation and registry writes from loaded provider discovery metadata.
27
+ - Bump dependency floors to `lex-llm >= 0.4.1` and `legion-llm >= 0.9.1`.
28
+
29
+ ## 0.3.2 - 2026-05-06
30
+
31
+ - Add provider contract specs for the shared keyword-only `lex-llm` provider API.
32
+ - Move OpenAI defaults back to `Legion::Extensions::Llm.provider_settings` with credentials and instance-level fleet responder settings.
33
+ - Remove `gateways` discovery; OpenAI-compatible targets are now named provider instances.
34
+ - Add provider-owned fleet responder actor and runner backed by `legion-llm` fleet policy execution.
35
+ - Bump the transport dependency floor to `legion-transport >= 1.4.14`.
36
+
37
+ ## 0.3.1 - 2026-05-03
38
+
39
+ - Normalize generic settings keys to OpenAI provider config keys during extension and gateway instance discovery.
40
+
3
41
  ## 0.3.0 - 2026-05-01
4
42
 
5
43
  - Add auto-discovery via CredentialSources and AutoRegistration from lex-llm 0.3.0
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.3.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
@@ -180,7 +180,7 @@ module Legion
180
180
  raise
181
181
  end
182
182
 
183
- def list_models
183
+ def list_models(**)
184
184
  log.info('Listing OpenAI models')
185
185
  raw = connection.get(models_url)
186
186
  models = build_model_infos(raw.body)
@@ -244,10 +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
- if Legion::Extensions::Llm::Configuration.respond_to?(:register_provider_options)
250
- Legion::Extensions::Llm::Configuration.register_provider_options(
251
- Legion::Extensions::Llm::Openai::Provider.configuration_options
252
- )
253
- end
@@ -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.3.0'
7
+ VERSION = '0.3.7'
8
8
  end
9
9
  end
10
10
  end
@@ -16,18 +16,36 @@ module Legion
16
16
  PROVIDER_FAMILY = :openai
17
17
 
18
18
  def self.default_settings
19
- {
20
- enabled: false,
21
- default_model: 'gpt-4o',
22
- api_key: nil,
23
- organization_id: nil,
24
- project_id: nil,
25
- model_whitelist: [],
26
- model_blacklist: [],
27
- model_cache_ttl: 3600,
28
- tls: { enabled: false, verify: :peer },
29
- instances: {}
30
- }
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
+ )
31
49
  end
32
50
 
33
51
  def self.provider_class
@@ -39,52 +57,78 @@ module Legion
39
57
 
40
58
  # 1. OPENAI_API_KEY environment variable
41
59
  env_key = CredentialSources.env('OPENAI_API_KEY')
42
- candidates[:env] = { openai_api_key: env_key, tier: :frontier } if env_key
60
+ candidates[:env] = { api_key: env_key, openai_api_key: env_key, tier: :frontier } if env_key
43
61
 
44
62
  # 2. CODEX_API_KEY environment variable
45
63
  codex_env_key = CredentialSources.env('CODEX_API_KEY')
46
- candidates[:codex_env] = { openai_api_key: codex_env_key, tier: :frontier } if codex_env_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
47
67
 
48
68
  # 3. Codex bearer token (~/.codex/auth.json chatgpt mode)
49
69
  codex_tok = CredentialSources.codex_token
50
- candidates[:codex] = { openai_api_key: codex_tok, tier: :frontier } if codex_tok
70
+ candidates[:codex] = { api_key: codex_tok, openai_api_key: codex_tok, tier: :frontier } if codex_tok
51
71
 
52
72
  # 4. Codex OPENAI_API_KEY from ~/.codex/auth.json
53
73
  codex_key = CredentialSources.codex_openai_key
54
- candidates[:codex_key] = { openai_api_key: codex_key, tier: :frontier } if codex_key
74
+ candidates[:codex_key] = { api_key: codex_key, openai_api_key: codex_key, tier: :frontier } if codex_key
55
75
 
56
76
  # 5. Claude config openaiApiKey
57
77
  claude_key = CredentialSources.claude_config_value(:openaiApiKey)
58
- candidates[:claude] = { openai_api_key: claude_key, tier: :frontier } if claude_key
78
+ candidates[:claude] = { api_key: claude_key, openai_api_key: claude_key, tier: :frontier } if claude_key
59
79
 
60
80
  # 6. Extension settings
61
81
  settings_config = CredentialSources.setting(:extensions, :llm, :openai)
62
82
  if settings_config.is_a?(Hash) && !settings_config.empty?
63
83
  settings_key = settings_config[:api_key] || settings_config['api_key']
64
84
  if settings_key
65
- candidates[:settings] = settings_config.merge(
85
+ candidates[:settings] = normalize_instance_config(settings_config).merge(
86
+ api_key: settings_key,
66
87
  openai_api_key: settings_key,
67
88
  tier: :frontier
68
89
  )
69
90
  end
70
91
  end
71
92
 
72
- # 7. Gateway instances from extension settings
73
- gateways = CredentialSources.setting(:extensions, :llm, :openai, :gateways)
74
- if gateways.is_a?(Hash)
75
- gateways.each do |name, config|
76
- next unless config.is_a?(Hash)
93
+ # 7. Named provider instances from extension settings
94
+ settings_instances(settings_config).each do |name, config|
95
+ next unless config.is_a?(Hash)
77
96
 
78
- candidates[name.to_sym] = config.merge(tier: :openai_compat)
79
- end
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)
80
101
  end
81
102
 
82
103
  # 8. Dedup
83
- CredentialSources.dedup_credentials(candidates)
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 : {}
84
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)
85
131
  end
86
132
  end
87
133
  end
88
134
  end
89
-
90
- Legion::Extensions::Llm::Openai.register_discovered_instances
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.3.0
4
+ version: 0.3.7
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.3.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.3.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: