lex-llm-openai 0.1.5 → 0.1.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: c5c4c967fa4c326ac27a3d6fb173633fa09d0764524114cbb77d6ff57c4b4572
4
- data.tar.gz: caeb6a5267ec2935d656f60e5b714c186fbfcb4bd13626ced597c690e89d4a28
3
+ metadata.gz: '0846dcde3031149be5687b648947d0ad1d450e8ff7cb20684d6cc0dd1754fc78'
4
+ data.tar.gz: ccd47540f56d1d8b7f43bcafb42dc2e0b5edc22e4083d216780d82731d94aa5d
5
5
  SHA512:
6
- metadata.gz: 458798f17517adb689c8afbe24b0c16b3eb66f60405b9af6ab2a319a1d1a3b51dafb430051e67939e49754d9457d0768c0515c05ee94ecd6d03722452c471e91
7
- data.tar.gz: 580d034e4de07aa658f50924ebb751e8ea6ebfde066bd77a1f29b309d90b62eed27d2ab9d9824e9d0761f3ca409e03dabc840374cd6f60f35e42241e44200a82
6
+ metadata.gz: ba683c96276ec71d4f420688e60613a2db35a22c0af2e726050ce1b1ff2b4f8323fc541581d67cae81317252bc561bb9922c2ac45fd7068d8e09b134c15a9e34
7
+ data.tar.gz: 7ec2d0bac937e45460281501bd74c41c08ab987f48c631c82fe41ea77d2bc59b53a2974bbff5e5d3185dcd65905b34cca7cbcf6af73ea2e6af142506560d597d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.7] - 2026-04-30
4
+ - Enable stream_usage_supported? for streaming token usage reporting
5
+
6
+ ## 0.1.6 - 2026-04-28
7
+
8
+ - Publish best-effort `llm.registry` discovered-model availability events when transport is already loaded.
9
+
3
10
  ## 0.1.5 - 2026-04-28
4
11
 
5
12
  - Require current shared Legion JSON, logging, settings, and `lex-llm >= 0.1.5` runtime dependencies.
@@ -11,6 +11,8 @@ module Legion
11
11
  include Legion::Extensions::Llm::Provider::OpenAICompatible
12
12
 
13
13
  class << self
14
+ attr_writer :registry_publisher
15
+
14
16
  def slug = 'openai'
15
17
  def configuration_requirements = %i[openai_api_key]
16
18
 
@@ -25,6 +27,10 @@ module Legion
25
27
  end
26
28
 
27
29
  def capabilities = Capabilities
30
+
31
+ def registry_publisher
32
+ @registry_publisher ||= RegistryPublisher.new
33
+ end
28
34
  end
29
35
 
30
36
  # Provider-level capability checks based on current OpenAI model families.
@@ -67,6 +73,8 @@ module Legion
67
73
  end
68
74
  end
69
75
 
76
+ def stream_usage_supported? = true
77
+
70
78
  def api_base
71
79
  config.openai_api_base || 'https://api.openai.com'
72
80
  end
@@ -89,6 +97,12 @@ module Legion
89
97
  connection.get("#{models_url}/#{model}").body
90
98
  end
91
99
 
100
+ def list_models
101
+ super.tap do |models|
102
+ self.class.registry_publisher.publish_models_async(models, readiness: readiness(live: false))
103
+ end
104
+ end
105
+
92
106
  private
93
107
 
94
108
  def maybe_normalize_temperature(temperature, model)
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ module Openai
7
+ # Builds sanitized lex-llm registry envelopes for OpenAI provider state.
8
+ class RegistryEventBuilder
9
+ def model_available(model, readiness:)
10
+ registry_event_class.available(
11
+ model_offering(model),
12
+ runtime: runtime_metadata,
13
+ health: model_health(readiness),
14
+ metadata: model_metadata(model)
15
+ )
16
+ end
17
+
18
+ private
19
+
20
+ def model_offering(model)
21
+ {
22
+ provider_family: :openai,
23
+ provider_instance: provider_instance,
24
+ transport: :http,
25
+ model: model.id,
26
+ usage_type: usage_type_for(model),
27
+ capabilities: Array(model.capabilities).map(&:to_sym),
28
+ limits: model_limits(model),
29
+ metadata: { lex: :llm_openai, model_name: model.name }.compact
30
+ }
31
+ end
32
+
33
+ def model_health(readiness)
34
+ ready = readiness.fetch(:ready, true) == true
35
+ { ready:, status: ready ? :available : :degraded }
36
+ end
37
+
38
+ def model_metadata(model)
39
+ { extension: :lex_llm_openai, provider: :openai, model_type: model.type }
40
+ end
41
+
42
+ def runtime_metadata
43
+ { node: provider_instance }
44
+ end
45
+
46
+ def model_limits(model)
47
+ {
48
+ context_window: model.context_window,
49
+ max_output_tokens: model.max_output_tokens
50
+ }.compact
51
+ end
52
+
53
+ def usage_type_for(model)
54
+ model.type == 'embedding' ? :embedding : :inference
55
+ end
56
+
57
+ def provider_instance
58
+ configured_node = (::Legion::Settings.dig(:node, :canonical_name) if defined?(::Legion::Settings))
59
+ value = configured_node.to_s.strip
60
+ value.empty? ? :openai : value.to_sym
61
+ rescue StandardError
62
+ :openai
63
+ end
64
+
65
+ def registry_event_class
66
+ ::Legion::Extensions::Llm::Routing::RegistryEvent
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ module Openai
7
+ # Best-effort publisher for OpenAI provider availability events.
8
+ class RegistryPublisher
9
+ APP_ID = 'lex-llm-openai'
10
+
11
+ def initialize(builder: RegistryEventBuilder.new)
12
+ @builder = builder
13
+ end
14
+
15
+ def publish_models_async(models, readiness:)
16
+ schedule do
17
+ Array(models).each do |model|
18
+ publish_event(@builder.model_available(model, readiness:))
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def schedule(&)
26
+ return false unless publishing_available?
27
+
28
+ Thread.new do
29
+ Thread.current.abort_on_exception = false
30
+ yield
31
+ rescue StandardError => e
32
+ log_publish_failure(e, level: :debug)
33
+ end
34
+ rescue StandardError => e
35
+ log_publish_failure(e, level: :debug)
36
+ false
37
+ end
38
+
39
+ def publish_event(event)
40
+ return false unless publishing_available?
41
+
42
+ message_class.new(event:, app_id: APP_ID).publish(spool: false)
43
+ rescue StandardError => e
44
+ log_publish_failure(e)
45
+ false
46
+ end
47
+
48
+ def publishing_available?
49
+ return false unless registry_event_available?
50
+ return false unless transport_message_available?
51
+ return true unless defined?(::Legion::Transport::Connection)
52
+ return true unless ::Legion::Transport::Connection.respond_to?(:session_open?)
53
+
54
+ ::Legion::Transport::Connection.session_open?
55
+ rescue StandardError
56
+ false
57
+ end
58
+
59
+ def registry_event_available?
60
+ defined?(::Legion::Extensions::Llm::Routing::RegistryEvent)
61
+ end
62
+
63
+ def transport_message_available?
64
+ return true if message_class_defined?
65
+ return false unless defined?(::Legion::Transport::Message) && defined?(::Legion::Transport::Exchange)
66
+
67
+ require 'legion/extensions/llm/openai/transport/messages/registry_event'
68
+ message_class_defined?
69
+ rescue LoadError
70
+ false
71
+ end
72
+
73
+ def message_class_defined?
74
+ defined?(::Legion::Extensions::Llm::Openai::Transport::Messages::RegistryEvent)
75
+ end
76
+
77
+ def message_class
78
+ ::Legion::Extensions::Llm::Openai::Transport::Messages::RegistryEvent
79
+ end
80
+
81
+ def log_publish_failure(error, level: :warn)
82
+ message = "[lex-llm-openai] llm.registry publish failed: #{error.class}: #{error.message}"
83
+ logger = ::Legion::Extensions::Llm.logger if defined?(::Legion::Extensions::Llm)
84
+ if logger.respond_to?(level)
85
+ logger.public_send(level, message)
86
+ elsif logger.respond_to?(:debug)
87
+ logger.debug(message)
88
+ end
89
+ rescue StandardError
90
+ nil
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ module Openai
7
+ module Transport
8
+ module Exchanges
9
+ # Topic exchange for OpenAI provider availability events.
10
+ class LlmRegistry < ::Legion::Transport::Exchange
11
+ def exchange_name
12
+ 'llm.registry'
13
+ end
14
+
15
+ def default_type
16
+ 'topic'
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/llm/openai/transport/exchanges/llm_registry'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Llm
8
+ module Openai
9
+ module Transport
10
+ module Messages
11
+ # Publishes lex-llm RegistryEvent envelopes to the llm.registry exchange.
12
+ class RegistryEvent < ::Legion::Transport::Message
13
+ def initialize(event:, **options)
14
+ super(**event.to_h.merge(options))
15
+ end
16
+
17
+ def exchange
18
+ Transport::Exchanges::LlmRegistry
19
+ end
20
+
21
+ def routing_key
22
+ @options[:routing_key] || "llm.registry.#{@options.fetch(:event_type)}"
23
+ end
24
+
25
+ def type
26
+ 'llm.registry.event'
27
+ end
28
+
29
+ def app_id
30
+ @options[:app_id] || RegistryPublisher::APP_ID
31
+ end
32
+
33
+ def persistent # rubocop:disable Naming/PredicateMethod
34
+ false
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Openai
7
- VERSION = '0.1.5'
7
+ VERSION = '0.1.7'
8
8
  end
9
9
  end
10
10
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/extensions/llm'
4
+ require 'legion/extensions/llm/openai/registry_event_builder'
5
+ require 'legion/extensions/llm/openai/registry_publisher'
4
6
  require 'legion/extensions/llm/openai/provider'
5
7
  require 'legion/extensions/llm/openai/version'
6
8
 
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.1.5
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO
@@ -84,6 +84,10 @@ files:
84
84
  - lex-llm-openai.gemspec
85
85
  - lib/legion/extensions/llm/openai.rb
86
86
  - lib/legion/extensions/llm/openai/provider.rb
87
+ - lib/legion/extensions/llm/openai/registry_event_builder.rb
88
+ - lib/legion/extensions/llm/openai/registry_publisher.rb
89
+ - lib/legion/extensions/llm/openai/transport/exchanges/llm_registry.rb
90
+ - lib/legion/extensions/llm/openai/transport/messages/registry_event.rb
87
91
  - lib/legion/extensions/llm/openai/version.rb
88
92
  homepage: https://github.com/LegionIO/lex-llm-openai
89
93
  licenses: