lex-llm 0.1.4 → 0.1.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: acd86ca9d14268a164c9492297186706a93770c93f4e312f45d6e54cec8bd565
4
- data.tar.gz: b78f52a9e0cebfedb102b7809aaf3affc20440e1fcd68a9f815e00f4a129b440
3
+ metadata.gz: d7d400d2739542ca417b189fba9d20f468d32ca6b4c1d4864fcd884a21d31577
4
+ data.tar.gz: 1c0ffee1ed602d77d2a295d2f4e7904abcef1ac284754553c3c5f883a78fa023
5
5
  SHA512:
6
- metadata.gz: cb2b53cfc698777af6dbfbd735a8d12c0ed5eb94a3dbea88fb91a4951946ac2c3f42790b316bfa34e9f56f822ce3c1be6a07cdc8298525f8d317895269fb2348
7
- data.tar.gz: b7392fe404a9bee6f2cde122522016b43f193c4df546a69732f9d238161f61a8a6dcb93b685fc2de5a1f9703ecf2c14c433e1a790039d8489b302afbb8cce2ef
6
+ metadata.gz: 07ea1df46e8469e493b89855d983ef1416d38e6907404eae1502340f37f271a43c2de442825b48dbca538907042e520d85f95316803f8a87b08633edf849685a
7
+ data.tar.gz: 8ee001e548224a71f050c3224d140d33e652603a9308d6301e539a8f984d8aed922e7c8f8ff3313df4a42b5a693acd2dc896914a71c23e47c42eae90f4a62c9d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.6 - 2026-04-28
4
+
5
+ - Add provider-neutral registry event envelopes for future `llm.registry` offering availability, unavailability, degraded, and heartbeat publishing without persistence.
6
+ - Sanitize registry offering payloads and reject sensitive runtime, capacity, health, lane, and metadata keys before publication.
7
+
8
+ ## 0.1.5 - 2026-04-28
9
+
10
+ - Add the expanded provider-neutral model offering contract with offering IDs, provider instances, canonical model aliases, model families, and routing metadata.
11
+ - Add shared model alias normalization and an in-memory offering registry for common routing filters.
12
+
3
13
  ## 0.1.4 - 2026-04-28
4
14
 
5
15
  - Add non-live provider readiness metadata for routing without expensive health or model calls by default.
data/README.md CHANGED
@@ -48,7 +48,7 @@ gem 'lex-llm'
48
48
  Provider extensions should declare `lex-llm` as a gemspec dependency:
49
49
 
50
50
  ```ruby
51
- spec.add_dependency 'lex-llm', '>= 0.1.4'
51
+ spec.add_dependency 'lex-llm', '>= 0.1.6'
52
52
  ```
53
53
 
54
54
  For local development across LegionIO repos, prefer a local path override in the app or test `Gemfile`, not a permanent git dependency in the gemspec.
@@ -90,11 +90,14 @@ A model offering describes one concrete model made available by one provider ins
90
90
 
91
91
  ```ruby
92
92
  offering = Legion::Extensions::Llm::Routing::ModelOffering.new(
93
+ offering_id: 'ollama:macbook_m4_max:inference:qwen3-6-27b-q4-k-m',
93
94
  provider_family: :ollama,
94
- instance_id: :macbook_m4_max,
95
+ provider_instance: :macbook_m4_max,
95
96
  transport: :local,
96
97
  tier: :local,
97
98
  model: 'qwen3.6:27b-q4_K_M',
99
+ canonical_model_alias: 'qwen3.6:27b-q4_K_M',
100
+ model_family: :qwen,
98
101
  usage_type: :inference,
99
102
  capabilities: %i[chat tools vision thinking],
100
103
  limits: {
@@ -106,6 +109,10 @@ offering = Legion::Extensions::Llm::Routing::ModelOffering.new(
106
109
  latency_ms: 180
107
110
  },
108
111
  policy_tags: %i[internal_only phi_allowed],
112
+ routing_metadata: {
113
+ region: :local,
114
+ accelerator: :metal
115
+ },
109
116
  metadata: {
110
117
  enabled: true,
111
118
  eligibility: {
@@ -125,18 +132,75 @@ offering.eligible_for?(
125
132
 
126
133
  Common offering fields:
127
134
 
135
+ - `offering_id`: stable identifier for the concrete offering; generated from provider, instance, usage type, and canonical alias when omitted
128
136
  - `provider_family`: provider implementation family, such as `:ollama`, `:vllm`, `:bedrock`, `:anthropic`, or `:openai`
129
- - `instance_id`: concrete provider instance, account, node, region, or local runtime
137
+ - `provider_instance`: concrete provider instance, account, node, region, or local runtime
138
+ - `instance_id`: compatibility alias for `provider_instance`
139
+ - `model_family`: provider-neutral family such as `:openai`, `:anthropic`, `:gemini`, `:qwen`, or `:llama`
130
140
  - `transport`: `:local`, `:http`, `:rabbitmq`, `:sdk`, or another provider-supported transport
131
141
  - `tier`: `:local`, `:private`, `:fleet`, `:cloud`, `:frontier`, or deployment-specific policy tier
132
142
  - `model`: provider model name or normalized model alias
143
+ - `canonical_model_alias`: provider-neutral alias used by routers and shared fleet lane keys when a provider deployment hides the base model
133
144
  - `usage_type`: `:inference` or `:embedding`
134
145
  - `capabilities`: normalized feature flags such as `:chat`, `:tools`, `:json_schema`, `:vision`, `:thinking`, or `:embedding`
135
146
  - `limits`: context window, output token limits, rate limits, concurrency limits, and provider-specific bounds
136
147
  - `health`: readiness, latency, recent failures, and provider-specific health metadata
137
148
  - `policy_tags`: routing and compliance tags such as `:internal_only`, `:phi_allowed`, or `:hipaa`
149
+ - `routing_metadata`: provider-neutral scheduling metadata for routers; persistence is intentionally out of scope
138
150
  - `metadata`: extension-specific metadata; sensitive values are excluded from fleet eligibility fingerprints
139
151
 
152
+ Provider gems that still pass `instance_id`, or that store `model_family`, `canonical_model_alias`, or `alias` under `metadata`, remain compatible. `ModelOffering` lifts those values into first-class readers for routers.
153
+
154
+ `Legion::Extensions::Llm::Aliases.canonical_model_alias(model, provider)` provides shared alias normalization from `aliases.json`, with an explicit model string fallback.
155
+
156
+ ## Offering Registry
157
+
158
+ `Legion::Extensions::Llm::Routing::OfferingRegistry` is an in-memory index for discovered or configured offerings. It does not persist state.
159
+
160
+ ```ruby
161
+ registry = Legion::Extensions::Llm::Routing::OfferingRegistry.new
162
+ registry.register(offering)
163
+
164
+ registry.find(offering.offering_id)
165
+ registry.find_by_model_alias('qwen3.6:27b-q4_K_M')
166
+ registry.filter(
167
+ provider_family: :ollama,
168
+ provider_instance: :macbook_m4_max,
169
+ model_family: :qwen,
170
+ capability: :tools
171
+ )
172
+ ```
173
+
174
+ ## Registry Events
175
+
176
+ `Legion::Extensions::Llm::Routing::RegistryEvent` builds dependency-light envelopes for future `llm.registry` publishing. It does not persist registry state or publish messages by itself.
177
+
178
+ ```ruby
179
+ event = Legion::Extensions::Llm::Routing::RegistryEvent.available(
180
+ offering,
181
+ runtime: { host_id: 'macbook-m4-max', process: { pid: 12_345 } },
182
+ capacity: { concurrency: 4, queued: 0 },
183
+ health: { ready: true, latency_ms: 180 },
184
+ lane: offering.lane_key,
185
+ metadata: { observed_by: :lex_llm_ollama }
186
+ )
187
+
188
+ event.to_h
189
+ # => {
190
+ # event_id: "...",
191
+ # event_type: :offering_available,
192
+ # occurred_at: "2026-04-28T14:30:15.123456Z",
193
+ # offering: { ... },
194
+ # runtime: { host_id: "macbook-m4-max", process: { pid: 12345 } },
195
+ # capacity: { concurrency: 4, queued: 0 },
196
+ # health: { ready: true, latency_ms: 180 },
197
+ # lane: "llm.fleet.inference.qwen3-6-27b-q4-k-m.ctx32768",
198
+ # metadata: { observed_by: :lex_llm_ollama }
199
+ # }
200
+ ```
201
+
202
+ Supported event types are `:offering_available`, `:offering_unavailable`, `:offering_degraded`, and `:offering_heartbeat`. Event offerings are derived from `ModelOffering#to_h`, with sensitive offering fields removed. Optional `runtime`, `capacity`, `health`, `lane`, and `metadata` values are intended for non-secret operational context and reject sensitive keys such as credentials, tokens, secrets, URLs, endpoint paths, prompts, and reply queues.
203
+
140
204
  ## Fleet Lanes
141
205
 
142
206
  Fleet routing uses shared work lanes derived from model offerings. A lane describes the work required, not the worker that happens to do it.
@@ -16,6 +16,23 @@ module Legion
16
16
  end
17
17
  end
18
18
 
19
+ def normalize_model_alias(model_id)
20
+ model_id.to_s.strip
21
+ end
22
+
23
+ def canonical_model_alias(model_id, provider = nil)
24
+ normalized = normalize_model_alias(model_id)
25
+ provider_name = provider&.to_s
26
+
27
+ aliases.each do |alias_name, provider_map|
28
+ next unless alias_matches?(provider_map, normalized, provider_name)
29
+
30
+ return alias_name
31
+ end
32
+
33
+ normalized
34
+ end
35
+
19
36
  def aliases
20
37
  @aliases ||= load_aliases
21
38
  end
@@ -35,6 +52,14 @@ module Legion
35
52
  def reload!
36
53
  @aliases = load_aliases
37
54
  end
55
+
56
+ private
57
+
58
+ def alias_matches?(provider_map, model_id, provider)
59
+ return provider_map[provider] == model_id if provider
60
+
61
+ provider_map.value?(model_id)
62
+ end
38
63
  end
39
64
  end
40
65
  end
@@ -9,7 +9,7 @@ module Legion
9
9
  module_function
10
10
 
11
11
  def for(offering, prefix: 'llm.fleet', include_context: true, include_fingerprint: false)
12
- parts = [prefix, lane_kind(offering), model_slug(offering.model)]
12
+ parts = [prefix, lane_kind(offering), model_slug(lane_model(offering))]
13
13
  if include_context && offering.inference? && offering.context_window
14
14
  parts << "ctx#{offering.context_window}"
15
15
  end
@@ -17,6 +17,13 @@ module Legion
17
17
  parts.join('.')
18
18
  end
19
19
 
20
+ def lane_model(offering)
21
+ return offering.canonical_model_alias if offering.respond_to?(:canonical_model_alias) &&
22
+ offering.canonical_model_alias.to_s != ''
23
+
24
+ offering.model
25
+ end
26
+
20
27
  def lane_kind(offering)
21
28
  offering.embedding? ? 'embed' : 'inference'
22
29
  end
@@ -6,15 +6,23 @@ module Legion
6
6
  module Routing
7
7
  # Describes one concrete model made available by one provider instance.
8
8
  class ModelOffering
9
- attr_reader :provider_family, :instance_id, :transport, :tier, :model, :usage_type, :capabilities, :limits,
9
+ attr_reader :offering_id, :provider_family, :model_family, :provider_instance, :instance_id, :transport,
10
+ :tier, :model, :canonical_model_alias, :routing_metadata, :usage_type, :capabilities, :limits,
10
11
  :credentials, :health, :cost, :policy_tags, :metadata
11
12
 
12
13
  def initialize(data)
14
+ @metadata = normalize_hash(fetch_value(data, :metadata))
13
15
  @provider_family = normalize_symbol(fetch_value(data, :provider_family, fetch_value(data, :provider)))
14
- @instance_id = normalize_symbol(fetch_value(data, :instance_id, @provider_family))
16
+ @model_family = normalize_symbol(fetch_value(data, :model_family, @metadata[:model_family]))
17
+ @provider_instance = normalize_symbol(fetch_value(data, :provider_instance,
18
+ fetch_value(data, :instance_id, @provider_family)))
19
+ @instance_id = @provider_instance
15
20
  @transport = normalize_symbol(fetch_value(data, :transport, :http))
16
21
  @tier = normalize_symbol(fetch_value(data, :tier, default_tier))
17
22
  @model = fetch_value(data, :model).to_s
23
+ @canonical_model_alias = normalize_model_alias(fetch_value(data, :canonical_model_alias,
24
+ metadata_canonical_model_alias))
25
+ @routing_metadata = normalize_hash(fetch_value(data, :routing_metadata))
18
26
  @usage_type = normalize_usage_type(fetch_value(data, :usage_type,
19
27
  fetch_value(data, :type) ||
20
28
  fetch_value(data, :kind) ||
@@ -25,7 +33,7 @@ module Legion
25
33
  @health = normalize_hash(fetch_value(data, :health))
26
34
  @cost = normalize_hash(fetch_value(data, :cost))
27
35
  @policy_tags = normalize_array(fetch_value(data, :policy_tags)).map(&:to_sym)
28
- @metadata = normalize_hash(fetch_value(data, :metadata))
36
+ @offering_id = normalize_offering_id(fetch_value(data, :offering_id, default_offering_id))
29
37
  end
30
38
 
31
39
  def enabled?
@@ -70,13 +78,23 @@ module Legion
70
78
  LaneKey.eligibility_fingerprint(self)
71
79
  end
72
80
 
81
+ def model_alias?(alias_name)
82
+ normalized = normalize_model_alias(alias_name)
83
+ [canonical_model_alias, model].compact.any? { |candidate| normalize_model_alias(candidate) == normalized }
84
+ end
85
+
73
86
  def to_h
74
87
  {
88
+ offering_id: offering_id,
75
89
  provider_family: provider_family,
90
+ model_family: model_family,
91
+ provider_instance: provider_instance,
76
92
  instance_id: instance_id,
77
93
  transport: transport,
78
94
  tier: tier,
79
95
  model: model,
96
+ canonical_model_alias: canonical_model_alias,
97
+ routing_metadata: routing_metadata,
80
98
  usage_type: usage_type,
81
99
  capabilities: capabilities,
82
100
  limits: limits,
@@ -166,6 +184,28 @@ module Legion
166
184
  rescue ArgumentError, TypeError
167
185
  nil
168
186
  end
187
+
188
+ def metadata_canonical_model_alias
189
+ metadata[:canonical_model_alias] || metadata[:alias] ||
190
+ Legion::Extensions::Llm::Aliases.canonical_model_alias(@model, @provider_family)
191
+ end
192
+
193
+ def normalize_model_alias(value)
194
+ Legion::Extensions::Llm::Aliases.normalize_model_alias(value)
195
+ end
196
+
197
+ def normalize_offering_id(value)
198
+ value.to_s.strip
199
+ end
200
+
201
+ def default_offering_id
202
+ [
203
+ provider_family,
204
+ provider_instance,
205
+ usage_type,
206
+ canonical_model_alias || model
207
+ ].compact.map { |part| LaneKey.model_slug(part) }.join(':')
208
+ end
169
209
  end
170
210
  end
171
211
  end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ module Routing
7
+ # In-memory index of provider-neutral model offerings.
8
+ class OfferingRegistry
9
+ include Enumerable
10
+
11
+ def initialize(offerings = [])
12
+ @offerings = []
13
+ Array(offerings).each { |offering| register(offering) }
14
+ end
15
+
16
+ def register(offering)
17
+ normalized = normalize_offering(offering)
18
+ @offerings.reject! { |existing| existing.offering_id == normalized.offering_id }
19
+ @offerings << normalized
20
+ normalized
21
+ end
22
+
23
+ def each(&)
24
+ @offerings.each(&)
25
+ end
26
+
27
+ def all
28
+ @offerings.dup
29
+ end
30
+ alias list all
31
+
32
+ def find(offering_id)
33
+ @offerings.find { |offering| offering.offering_id == offering_id.to_s }
34
+ end
35
+
36
+ def find_by_model_alias(alias_name)
37
+ @offerings.find { |offering| offering.model_alias?(alias_name) }
38
+ end
39
+
40
+ def filter(**criteria)
41
+ @offerings.select do |offering|
42
+ matches_symbol?(offering.provider_family, criteria[:provider_family]) &&
43
+ matches_symbol?(offering.model_family, criteria[:model_family]) &&
44
+ matches_symbol?(offering.provider_instance, criteria[:provider_instance]) &&
45
+ matches_capability?(offering, criteria[:capability]) &&
46
+ matches_model_alias?(offering, criteria[:model_alias]) &&
47
+ matches_model?(offering, criteria[:model]) &&
48
+ matches_usage_type?(offering, criteria[:usage_type])
49
+ end
50
+ end
51
+
52
+ def by_provider_family(provider_family)
53
+ filter(provider_family:)
54
+ end
55
+
56
+ def by_model_family(model_family)
57
+ filter(model_family:)
58
+ end
59
+
60
+ def by_provider_instance(provider_instance)
61
+ filter(provider_instance:)
62
+ end
63
+
64
+ def by_capability(capability)
65
+ filter(capability:)
66
+ end
67
+
68
+ private
69
+
70
+ def normalize_offering(offering)
71
+ return offering if offering.is_a?(ModelOffering)
72
+
73
+ ModelOffering.new(offering)
74
+ end
75
+
76
+ def matches_symbol?(actual, expected)
77
+ expected.nil? || actual == expected.to_sym
78
+ end
79
+
80
+ def matches_capability?(offering, capability)
81
+ capability.nil? || offering.supports?(capability)
82
+ end
83
+
84
+ def matches_model_alias?(offering, model_alias)
85
+ model_alias.nil? || offering.model_alias?(model_alias)
86
+ end
87
+
88
+ def matches_model?(offering, model)
89
+ model.nil? || offering.model == model.to_s
90
+ end
91
+
92
+ def matches_usage_type?(offering, usage_type)
93
+ usage_type.nil? || offering.usage_type == usage_type.to_sym
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ module Routing
7
+ # Serializable provider-neutral envelope for future llm.registry publishing.
8
+ class RegistryEvent
9
+ EVENT_TYPES = %i[
10
+ offering_available
11
+ offering_unavailable
12
+ offering_degraded
13
+ offering_heartbeat
14
+ ].freeze
15
+ SENSITIVE_KEYS = %i[
16
+ access_key
17
+ api_key
18
+ authorization
19
+ bearer
20
+ client_secret
21
+ credential
22
+ credentials
23
+ endpoint
24
+ endpoint_url
25
+ password
26
+ path
27
+ private_key
28
+ prompt
29
+ reply_to
30
+ secret
31
+ secrets
32
+ token
33
+ url
34
+ ].freeze
35
+
36
+ attr_reader :event_id, :event_type, :occurred_at, :offering, :runtime, :capacity, :health, :lane, :metadata
37
+
38
+ class << self
39
+ def available(offering, **attributes)
40
+ new(event_type: :offering_available, offering:, **attributes)
41
+ end
42
+
43
+ def unavailable(offering, **attributes)
44
+ new(event_type: :offering_unavailable, offering:, **attributes)
45
+ end
46
+
47
+ def degraded(offering, **attributes)
48
+ new(event_type: :offering_degraded, offering:, **attributes)
49
+ end
50
+
51
+ def heartbeat(offering, **attributes)
52
+ new(event_type: :offering_heartbeat, offering:, **attributes)
53
+ end
54
+ end
55
+
56
+ def initialize(event_type:, offering:, **attributes)
57
+ @event_id = normalize_event_id(attributes.fetch(:event_id, SecureRandom.uuid))
58
+ @event_type = normalize_event_type(event_type)
59
+ @occurred_at = normalize_time(attributes.fetch(:occurred_at, Time.now.utc))
60
+ @offering = normalize_offering(offering)
61
+ @runtime = sanitize_optional_hash(attributes[:runtime], :runtime)
62
+ @capacity = sanitize_optional_hash(attributes[:capacity], :capacity)
63
+ @health = sanitize_optional_hash(attributes[:health], :health)
64
+ @lane = sanitize_optional_value(attributes[:lane], :lane)
65
+ @metadata = sanitize_optional_hash(attributes[:metadata], :metadata)
66
+ end
67
+
68
+ def to_h
69
+ {
70
+ event_id: event_id,
71
+ event_type: event_type,
72
+ occurred_at: occurred_at.utc.iso8601(6),
73
+ offering: sanitized_offering_hash,
74
+ runtime: runtime,
75
+ capacity: capacity,
76
+ health: health,
77
+ lane: lane,
78
+ metadata: metadata
79
+ }.compact
80
+ end
81
+
82
+ private
83
+
84
+ def normalize_event_id(value)
85
+ normalized = value.to_s.strip
86
+ raise ArgumentError, 'event_id is required' if normalized.empty?
87
+
88
+ normalized
89
+ end
90
+
91
+ def normalize_event_type(value)
92
+ normalized = value.to_sym
93
+ raise ArgumentError, "unsupported registry event type: #{value}" unless EVENT_TYPES.include?(normalized)
94
+
95
+ normalized
96
+ end
97
+
98
+ def normalize_time(value)
99
+ return value.utc if value.respond_to?(:utc)
100
+
101
+ Time.parse(value.to_s).utc
102
+ end
103
+
104
+ def normalize_offering(value)
105
+ return value if value.is_a?(ModelOffering)
106
+
107
+ ModelOffering.new(value)
108
+ end
109
+
110
+ def sanitized_offering_hash
111
+ sanitize_hash(offering.to_h, on_sensitive: :drop)
112
+ end
113
+
114
+ def sanitize_optional_hash(value, label)
115
+ return nil if value.nil?
116
+
117
+ sanitize_hash(value.to_h, label:)
118
+ end
119
+
120
+ def sanitize_optional_value(value, label)
121
+ return nil if value.nil?
122
+ return sanitize_hash(value.to_h, label:) if value.respond_to?(:to_h)
123
+ return value unless value.is_a?(Array)
124
+
125
+ sanitize_array(value, label:, path: [])
126
+ end
127
+
128
+ def sanitize_hash(hash, label: nil, path: [], on_sensitive: :raise)
129
+ hash.each_with_object({}) do |(key, value), sanitized|
130
+ normalized_key = key.to_sym
131
+ key_path = path + [normalized_key]
132
+ if sensitive_key?(normalized_key)
133
+ raise_sensitive_key!(label, key_path) if on_sensitive == :raise
134
+
135
+ next
136
+ end
137
+
138
+ sanitized[normalized_key] = sanitize_value(value, label:, path: key_path, on_sensitive:)
139
+ end
140
+ end
141
+
142
+ def sanitize_array(array, label:, path:, on_sensitive: :raise)
143
+ array.map { |value| sanitize_value(value, label:, path:, on_sensitive:) }
144
+ end
145
+
146
+ def sanitize_value(value, label:, path:, on_sensitive:)
147
+ return sanitize_hash(value, label:, path:, on_sensitive:) if value.is_a?(Hash)
148
+ return sanitize_array(value, label:, path:, on_sensitive:) if value.is_a?(Array)
149
+
150
+ value
151
+ end
152
+
153
+ def sensitive_key?(key)
154
+ normalized = key.to_s.downcase.gsub(/[^a-z0-9]+/, '_').to_sym
155
+ SENSITIVE_KEYS.include?(normalized) ||
156
+ normalized.to_s.end_with?('_key', '_secret', '_token', '_password')
157
+ end
158
+
159
+ def raise_sensitive_key!(label, path)
160
+ prefix = label ? "#{label} contains" : 'registry event contains'
161
+ raise ArgumentError, "#{prefix} sensitive key: #{path.join('.')}"
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Llm
6
- VERSION = '0.1.4'
6
+ VERSION = '0.1.6'
7
7
  end
8
8
  end
9
9
  end
@@ -38,11 +38,16 @@ module Legion
38
38
  # Provider-neutral value objects exposed under the Legion extension namespace.
39
39
  module Types
40
40
  ModelOffering = Routing::ModelOffering unless const_defined?(:ModelOffering, false)
41
+ OfferingRegistry = Routing::OfferingRegistry unless const_defined?(:OfferingRegistry, false)
42
+ RegistryEvent = Routing::RegistryEvent unless const_defined?(:RegistryEvent, false)
41
43
  end
42
44
 
43
45
  # Shared routing helpers exposed under the Legion extension namespace.
44
46
  module Routing
45
47
  LaneKey = ::Legion::Extensions::Llm::Routing::LaneKey unless const_defined?(:LaneKey, false)
48
+ OfferingRegistry = ::Legion::Extensions::Llm::Routing::OfferingRegistry unless const_defined?(:OfferingRegistry,
49
+ false)
50
+ RegistryEvent = ::Legion::Extensions::Llm::Routing::RegistryEvent unless const_defined?(:RegistryEvent, false)
46
51
  end
47
52
 
48
53
  class << self
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO
@@ -228,6 +228,8 @@ files:
228
228
  - lib/legion/extensions/llm/routing.rb
229
229
  - lib/legion/extensions/llm/routing/lane_key.rb
230
230
  - lib/legion/extensions/llm/routing/model_offering.rb
231
+ - lib/legion/extensions/llm/routing/offering_registry.rb
232
+ - lib/legion/extensions/llm/routing/registry_event.rb
231
233
  - lib/legion/extensions/llm/stream_accumulator.rb
232
234
  - lib/legion/extensions/llm/streaming.rb
233
235
  - lib/legion/extensions/llm/thinking.rb