lex-llm 0.7.6 → 0.8.4
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 +4 -4
- data/CHANGELOG.md +139 -0
- data/RULES.md +97 -0
- data/lib/legion/extensions/llm/auto_registration.rb +4 -13
- data/lib/legion/extensions/llm/canonical/chunk.rb +107 -94
- data/lib/legion/extensions/llm/canonical/content_block.rb +65 -75
- data/lib/legion/extensions/llm/canonical/message.rb +53 -69
- data/lib/legion/extensions/llm/canonical/params.rb +58 -34
- data/lib/legion/extensions/llm/canonical/request.rb +68 -56
- data/lib/legion/extensions/llm/canonical/response.rb +62 -78
- data/lib/legion/extensions/llm/canonical/strict.rb +105 -0
- data/lib/legion/extensions/llm/canonical/thinking.rb +36 -84
- data/lib/legion/extensions/llm/canonical/thinking_config.rb +149 -0
- data/lib/legion/extensions/llm/canonical/tool_call.rb +43 -53
- data/lib/legion/extensions/llm/canonical/tool_definition.rb +56 -46
- data/lib/legion/extensions/llm/canonical/tool_schema.rb +15 -22
- data/lib/legion/extensions/llm/canonical/usage.rb +47 -40
- data/lib/legion/extensions/llm/canonical.rb +7 -5
- data/lib/legion/extensions/llm/configuration.rb +40 -10
- data/lib/legion/extensions/llm/connection.rb +8 -30
- data/lib/legion/extensions/llm/credential_sources.rb +32 -49
- data/lib/legion/extensions/llm/discovery/actor.rb +92 -0
- data/lib/legion/extensions/llm/discovery/pipeline.rb +604 -0
- data/lib/legion/extensions/llm/error.rb +0 -14
- data/lib/legion/extensions/llm/fleet/contract_error.rb +15 -0
- data/lib/legion/extensions/llm/fleet/envelope_validation.rb +7 -6
- data/lib/legion/extensions/llm/fleet/fleet_envelope.rb +66 -0
- data/lib/legion/extensions/llm/fleet/protocol.rb +18 -5
- data/lib/legion/extensions/llm/fleet/provider_responder.rb +58 -156
- data/lib/legion/extensions/llm/fleet/token_validator.rb +15 -21
- data/lib/legion/extensions/llm/fleet/worker_execution.rb +107 -154
- data/lib/legion/extensions/llm/inventory/errors.rb +0 -1
- data/lib/legion/extensions/llm/inventory/evidence.rb +1 -1
- data/lib/legion/extensions/llm/inventory/identity.rb +55 -39
- data/lib/legion/extensions/llm/inventory/probe_token.rb +9 -12
- data/lib/legion/extensions/llm/inventory/publisher.rb +15 -53
- data/lib/legion/extensions/llm/inventory/records.rb +36 -100
- data/lib/legion/extensions/llm/inventory/registry.rb +54 -48
- data/lib/legion/extensions/llm/inventory/snapshot.rb +7 -21
- data/lib/legion/extensions/llm/inventory/weight_reconciler.rb +13 -6
- data/lib/legion/extensions/llm/inventory/weight_schema.rb +47 -8
- data/lib/legion/extensions/llm/provider/open_ai_compatible.rb +178 -182
- data/lib/legion/extensions/llm/provider.rb +192 -315
- data/lib/legion/extensions/llm/provider_contract.rb +25 -8
- data/lib/legion/extensions/llm/provider_settings.rb +5 -26
- data/lib/legion/extensions/llm/responses/thinking_extractor.rb +8 -1
- data/lib/legion/extensions/llm/responses/tool_arguments.rb +48 -0
- data/lib/legion/extensions/llm/routing/provider_outcome.rb +25 -0
- data/lib/legion/extensions/llm/routing/records.rb +34 -17
- data/lib/legion/extensions/llm/stream_accumulator.rb +186 -270
- data/lib/legion/extensions/llm/streaming.rb +50 -35
- data/lib/legion/extensions/llm/taxonomies.rb +14 -26
- data/lib/legion/extensions/llm/transport/fleet_lane.rb +8 -10
- data/lib/legion/extensions/llm/transport/messages/fleet_error.rb +3 -2
- data/lib/legion/extensions/llm/transport/messages/fleet_request.rb +6 -8
- data/lib/legion/extensions/llm/transport/messages/fleet_response.rb +10 -9
- data/lib/legion/extensions/llm/utils.rb +23 -5
- data/lib/legion/extensions/llm/version.rb +1 -1
- data/lib/legion/extensions/llm.rb +8 -98
- data/spec/legion/extensions/llm/auto_registration_spec.rb +4 -9
- data/spec/legion/extensions/llm/canonical/chunk_spec.rb +66 -252
- data/spec/legion/extensions/llm/canonical/content_block_spec.rb +52 -197
- data/spec/legion/extensions/llm/canonical/message_spec.rb +89 -204
- data/spec/legion/extensions/llm/canonical/params_spec.rb +55 -136
- data/spec/legion/extensions/llm/canonical/request_spec.rb +81 -143
- data/spec/legion/extensions/llm/canonical/response_spec.rb +68 -204
- data/spec/legion/extensions/llm/canonical/thinking/config_spec.rb +222 -0
- data/spec/legion/extensions/llm/canonical/thinking_spec.rb +23 -171
- data/spec/legion/extensions/llm/canonical/tool_call_spec.rb +59 -162
- data/spec/legion/extensions/llm/canonical/tool_definition_spec.rb +55 -191
- data/spec/legion/extensions/llm/canonical/tool_schema_spec.rb +26 -67
- data/spec/legion/extensions/llm/canonical/usage_spec.rb +46 -155
- data/spec/legion/extensions/llm/configuration_spec.rb +31 -5
- data/spec/legion/extensions/llm/conformance/canonical_type_examples.rb +106 -0
- data/spec/legion/extensions/llm/conformance/client_translator_examples.rb +1 -2
- data/spec/legion/extensions/llm/conformance/conformance.rb +10 -2
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_fleet_round_trip.json +1 -1
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_thinking_request.json +2 -2
- data/spec/legion/extensions/llm/conformance/provider_translator_examples.rb +1 -1
- data/spec/legion/extensions/llm/conformance/ssot_contract_conformance_spec.rb +128 -0
- data/spec/legion/extensions/llm/conformance/ssot_contract_examples.rb +505 -0
- data/spec/legion/extensions/llm/conformance/ssot_provider_examples.rb +11 -10
- data/spec/legion/extensions/llm/credential_sources_spec.rb +12 -13
- data/spec/legion/extensions/llm/error_spec.rb +2 -12
- data/spec/legion/extensions/llm/fleet/exact_offering_spec.rb +59 -45
- data/spec/legion/extensions/llm/fleet/provider_responder_spec.rb +173 -95
- data/spec/legion/extensions/llm/fleet/token_validator_spec.rb +7 -2
- data/spec/legion/extensions/llm/fleet/worker_execution_spec.rb +93 -74
- data/spec/legion/extensions/llm/fleet_messages_spec.rb +119 -125
- data/spec/legion/extensions/llm/gemspec_spec.rb +1 -2
- data/spec/legion/extensions/llm/inventory/boot_spec.rb +4 -4
- data/spec/legion/extensions/llm/inventory/identity_spec.rb +127 -110
- data/spec/legion/extensions/llm/inventory/probe_token_spec.rb +4 -4
- data/spec/legion/extensions/llm/inventory/publisher_spec.rb +8 -61
- data/spec/legion/extensions/llm/inventory/records_spec.rb +82 -47
- data/spec/legion/extensions/llm/inventory/registry_activation_spec.rb +49 -16
- data/spec/legion/extensions/llm/inventory/registry_replacement_spec.rb +5 -4
- data/spec/legion/extensions/llm/inventory/snapshot_spec.rb +11 -5
- data/spec/legion/extensions/llm/inventory/weight_reconciler_spec.rb +7 -3
- data/spec/legion/extensions/llm/inventory/weight_schema_spec.rb +60 -10
- data/spec/legion/extensions/llm/provider/open_ai_compatible_spec.rb +128 -68
- data/spec/legion/extensions/llm/provider/open_ai_compatible_tool_calls_array_spec.rb +7 -31
- data/spec/legion/extensions/llm/provider_contract_spec.rb +10 -15
- data/spec/legion/extensions/llm/provider_spec.rb +97 -78
- data/spec/legion/extensions/llm/routing/records_spec.rb +38 -5
- data/spec/legion/extensions/llm/stream_accumulator_spec.rb +174 -144
- data/spec/legion/extensions/llm/streaming_spec.rb +27 -0
- data/spec/legion/extensions/llm/taxonomies_spec.rb +43 -43
- data/spec/legion/extensions/llm/transport/fleet_lane_spec.rb +1 -1
- data/spec/legion/extensions/llm/utils_spec.rb +26 -7
- data/spec/legion/extensions/llm_base_contract_spec.rb +55 -90
- data/spec/legion/extensions/llm_extension_spec.rb +5 -5
- data/spec/support/fake_llm_provider.rb +45 -39
- data/spec/support/fake_ssot_harness.rb +7 -2
- metadata +13 -54
- data/lib/legion/extensions/llm/agent.rb +0 -366
- data/lib/legion/extensions/llm/aliases.json +0 -436
- data/lib/legion/extensions/llm/aliases.rb +0 -67
- data/lib/legion/extensions/llm/attachment.rb +0 -229
- data/lib/legion/extensions/llm/chat.rb +0 -354
- data/lib/legion/extensions/llm/chunk.rb +0 -10
- data/lib/legion/extensions/llm/content.rb +0 -81
- data/lib/legion/extensions/llm/context.rb +0 -33
- data/lib/legion/extensions/llm/embedding.rb +0 -33
- data/lib/legion/extensions/llm/image.rb +0 -109
- data/lib/legion/extensions/llm/inventory/capabilities.rb +0 -40
- data/lib/legion/extensions/llm/inventory/scoped_refresher.rb +0 -310
- data/lib/legion/extensions/llm/message.rb +0 -118
- data/lib/legion/extensions/llm/mime_type.rb +0 -75
- data/lib/legion/extensions/llm/model/info.rb +0 -286
- data/lib/legion/extensions/llm/model/modalities.rb +0 -26
- data/lib/legion/extensions/llm/model/pricing.rb +0 -52
- data/lib/legion/extensions/llm/model/pricing_category.rb +0 -50
- data/lib/legion/extensions/llm/model/pricing_tier.rb +0 -37
- data/lib/legion/extensions/llm/model.rb +0 -11
- data/lib/legion/extensions/llm/models.json +0 -57313
- data/lib/legion/extensions/llm/models.rb +0 -530
- data/lib/legion/extensions/llm/models_schema.json +0 -168
- data/lib/legion/extensions/llm/moderation.rb +0 -60
- data/lib/legion/extensions/llm/registry_event_builder.rb +0 -141
- data/lib/legion/extensions/llm/registry_publisher.rb +0 -107
- data/lib/legion/extensions/llm/responses/chat_response.rb +0 -43
- data/lib/legion/extensions/llm/responses/embedding_response.rb +0 -38
- data/lib/legion/extensions/llm/responses/stream_chunk.rb +0 -43
- data/lib/legion/extensions/llm/routing/lane_key.rb +0 -66
- data/lib/legion/extensions/llm/routing/model_offering.rb +0 -241
- data/lib/legion/extensions/llm/routing/offering_registry.rb +0 -101
- data/lib/legion/extensions/llm/routing/registry_event.rb +0 -167
- data/lib/legion/extensions/llm/thinking.rb +0 -53
- data/lib/legion/extensions/llm/tokens.rb +0 -51
- data/lib/legion/extensions/llm/tool_call.rb +0 -34
- data/lib/legion/extensions/llm/transcription.rb +0 -39
- data/lib/legion/extensions/llm/transport/messages/registry_event.rb +0 -44
- data/spec/legion/extensions/llm/agent_spec.rb +0 -179
- data/spec/legion/extensions/llm/attachment_spec.rb +0 -25
- data/spec/legion/extensions/llm/conformance/fixtures/ssot_identity_vectors.json +0 -84
- data/spec/legion/extensions/llm/context_spec.rb +0 -127
- data/spec/legion/extensions/llm/inventory/capabilities_spec.rb +0 -43
- data/spec/legion/extensions/llm/inventory/scoped_refresher_spec.rb +0 -340
- data/spec/legion/extensions/llm/message_spec.rb +0 -64
- data/spec/legion/extensions/llm/model/info_spec.rb +0 -222
- data/spec/legion/extensions/llm/models_spec.rb +0 -104
- data/spec/legion/extensions/llm/registry_event_builder_spec.rb +0 -68
- data/spec/legion/extensions/llm/registry_publisher_spec.rb +0 -22
- data/spec/legion/extensions/llm/responses/response_objects_spec.rb +0 -75
- data/spec/legion/extensions/llm/routing/model_offering_spec.rb +0 -281
- data/spec/legion/extensions/llm/routing/offering_registry_spec.rb +0 -50
- data/spec/legion/extensions/llm/routing/registry_event_spec.rb +0 -120
|
@@ -13,7 +13,7 @@ module Legion
|
|
|
13
13
|
include Legion::Logging::Helper
|
|
14
14
|
|
|
15
15
|
def basic(&)
|
|
16
|
-
logger =
|
|
16
|
+
logger = faraday_logger_for(Legion::Extensions::Llm.config, log)
|
|
17
17
|
Faraday.new do |f|
|
|
18
18
|
f.response :logger,
|
|
19
19
|
logger,
|
|
@@ -25,22 +25,20 @@ module Legion
|
|
|
25
25
|
yield f if block_given?
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
|
+
end
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return config.logger if config.respond_to?(:logger) && config.logger
|
|
30
|
+
# One Faraday logger resolution (10 U9): the configured logger when
|
|
31
|
+
# present, else the caller's fallback log.
|
|
32
|
+
def self.faraday_logger_for(config_source, fallback_log)
|
|
33
|
+
return config_source.logger if config_source.respond_to?(:logger) && config_source.logger
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
end
|
|
35
|
+
fallback_log
|
|
37
36
|
end
|
|
38
37
|
|
|
39
38
|
def initialize(provider, config)
|
|
40
39
|
@provider = provider
|
|
41
40
|
@config = config
|
|
42
41
|
|
|
43
|
-
ensure_configured!
|
|
44
42
|
@connection ||= Faraday.new(provider.api_base) do |faraday|
|
|
45
43
|
faraday.ssl.verify = false
|
|
46
44
|
setup_timeout(faraday)
|
|
@@ -83,7 +81,7 @@ module Legion
|
|
|
83
81
|
end
|
|
84
82
|
|
|
85
83
|
def setup_logging(faraday)
|
|
86
|
-
logger =
|
|
84
|
+
logger = self.class.faraday_logger_for(config, log)
|
|
87
85
|
# Enable request body logging when the logger is at DEBUG level,
|
|
88
86
|
# or when explicitly enabled via fleet request_payload setting.
|
|
89
87
|
request_payload = Legion::Extensions::Llm.default_settings.dig(:fleet, :request, :logger, :request_payload)
|
|
@@ -100,12 +98,6 @@ module Legion
|
|
|
100
98
|
end
|
|
101
99
|
end
|
|
102
100
|
|
|
103
|
-
def faraday_logger
|
|
104
|
-
return config.logger if config.respond_to?(:logger) && config.logger
|
|
105
|
-
|
|
106
|
-
log
|
|
107
|
-
end
|
|
108
|
-
|
|
109
101
|
def debug_logger?(logger)
|
|
110
102
|
return logger.debug? if logger.respond_to?(:debug?)
|
|
111
103
|
return logger.level.to_i <= Logger::DEBUG if logger.respond_to?(:level)
|
|
@@ -164,20 +156,6 @@ module Legion
|
|
|
164
156
|
Legion::Extensions::Llm::OverloadedError
|
|
165
157
|
]
|
|
166
158
|
end
|
|
167
|
-
|
|
168
|
-
def ensure_configured!
|
|
169
|
-
return if @provider.configured?
|
|
170
|
-
|
|
171
|
-
missing = @provider.configuration_requirements.reject { |req| @config.send(req) }
|
|
172
|
-
config_block = <<~RUBY
|
|
173
|
-
Legion::Extensions::Llm.configure do |config|
|
|
174
|
-
#{missing.map { |key| "config.#{key} = ENV['#{key.to_s.upcase}']" }.join("\n ")}
|
|
175
|
-
end
|
|
176
|
-
RUBY
|
|
177
|
-
|
|
178
|
-
raise ConfigurationError,
|
|
179
|
-
"#{@provider.name} provider is not configured. Add this to your initialization:\n\n#{config_block}"
|
|
180
|
-
end
|
|
181
159
|
end
|
|
182
160
|
end
|
|
183
161
|
end
|
|
@@ -58,8 +58,12 @@ module Legion
|
|
|
58
58
|
env_hash[key.to_sym] || env_hash[key.to_s]
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
# The codex auth file is an optional source: absent means "no codex
|
|
62
|
+
# credential" (probe semantics); present-but-unreadable raises from
|
|
63
|
+
# read_json (O11 fail-closed).
|
|
61
64
|
def codex_token
|
|
62
65
|
return nil unless credential_source_probing_enabled?
|
|
66
|
+
return nil unless File.exist?(CODEX_AUTH)
|
|
63
67
|
|
|
64
68
|
data = read_json(CODEX_AUTH)
|
|
65
69
|
mode = data[:auth_mode] || data['auth_mode']
|
|
@@ -74,6 +78,7 @@ module Legion
|
|
|
74
78
|
|
|
75
79
|
def codex_openai_key
|
|
76
80
|
return nil unless credential_source_probing_enabled?
|
|
81
|
+
return nil unless File.exist?(CODEX_AUTH)
|
|
77
82
|
|
|
78
83
|
data = read_json(CODEX_AUTH)
|
|
79
84
|
val = data[:OPENAI_API_KEY] || data['OPENAI_API_KEY']
|
|
@@ -198,19 +203,12 @@ module Legion
|
|
|
198
203
|
credential_fingerprint(val)
|
|
199
204
|
end
|
|
200
205
|
|
|
201
|
-
# Returns true when the URL points to localhost / 127.0.0.1 / ::1
|
|
206
|
+
# Returns true when the URL points to localhost / 127.0.0.1 / ::1
|
|
207
|
+
# (the one shared host-locality detector — Utils.localhost_url?).
|
|
202
208
|
def localhost?(url)
|
|
203
209
|
return false if url.nil?
|
|
204
210
|
|
|
205
|
-
|
|
206
|
-
host = uri.host
|
|
207
|
-
return false if host.nil?
|
|
208
|
-
|
|
209
|
-
normalized = host.delete_prefix('[').delete_suffix(']')
|
|
210
|
-
%w[localhost 127.0.0.1 ::1].include?(normalized)
|
|
211
|
-
rescue URI::InvalidURIError => e
|
|
212
|
-
handle_exception(e, level: :warn, handled: true, operation: 'llm.credential_sources.localhost')
|
|
213
|
-
false
|
|
211
|
+
Utils.localhost_url?(url)
|
|
214
212
|
end
|
|
215
213
|
|
|
216
214
|
module_function :env, :credential_source_probing_enabled?,
|
|
@@ -224,69 +222,54 @@ module Legion
|
|
|
224
222
|
# --- private helpers -----------------------------------------------
|
|
225
223
|
|
|
226
224
|
# Merge user-level (~/.claude/settings.json) and project-level
|
|
227
|
-
# (.claude/settings.json) Claude configs.
|
|
225
|
+
# (.claude/settings.json) Claude configs. Both files are optional
|
|
226
|
+
# sources: absent means "no config from that level". Project overrides
|
|
227
|
+
# user.
|
|
228
228
|
def merge_claude_configs
|
|
229
|
-
user = read_json(CLAUDE_SETTINGS)
|
|
230
|
-
project = read_json(CLAUDE_PROJECT)
|
|
231
|
-
deep_merge(user, project)
|
|
229
|
+
user = File.exist?(CLAUDE_SETTINGS) ? read_json(CLAUDE_SETTINGS) : {}
|
|
230
|
+
project = File.exist?(CLAUDE_PROJECT) ? read_json(CLAUDE_PROJECT) : {}
|
|
231
|
+
Utils.deep_merge(user, project)
|
|
232
232
|
end
|
|
233
233
|
|
|
234
|
-
# Read and parse a JSON file.
|
|
234
|
+
# Read and parse a JSON file. O11 fail-closed: a missing, empty, or
|
|
235
|
+
# unreadable/unparseable credential file raises — it is a
|
|
236
|
+
# configuration error, never a fabricated empty credential.
|
|
235
237
|
def read_json(path)
|
|
236
|
-
|
|
238
|
+
raise ConfigurationError, "credential file is missing: #{path}" unless File.exist?(path)
|
|
237
239
|
|
|
238
240
|
raw = File.read(path)
|
|
239
|
-
|
|
241
|
+
raise ConfigurationError, "credential file is empty: #{path}" if raw.strip.empty?
|
|
240
242
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
::JSON.parse(raw, symbolize_names: true)
|
|
245
|
-
end
|
|
246
|
-
rescue StandardError => e
|
|
247
|
-
handle_exception(e, level: :warn, handled: true, operation: 'llm.credential_sources.read_json',
|
|
248
|
-
path:)
|
|
249
|
-
{}
|
|
243
|
+
# L1: Legion::JSON only (house rule) — the bare ::JSON fallback is
|
|
244
|
+
# deleted; Legion::JSON is a hard dependency of this gem.
|
|
245
|
+
::Legion::JSON.parse(raw, symbolize_names: true)
|
|
250
246
|
end
|
|
251
247
|
|
|
252
248
|
# JWT expiry check. Decodes the base64 payload segment and checks
|
|
253
|
-
# that exp > now.
|
|
254
|
-
# doubt).
|
|
249
|
+
# that exp > now. O11 fail-closed: unreadable or invalid token data
|
|
250
|
+
# is INVALID (the old benefit-of-the-doubt true is deleted). A
|
|
251
|
+
# parseable token with no exp claim has nothing to violate.
|
|
255
252
|
def token_valid?(token)
|
|
256
|
-
return true if token.nil?
|
|
257
|
-
|
|
258
253
|
require 'base64'
|
|
259
|
-
require 'json'
|
|
260
254
|
|
|
261
255
|
parts = token.to_s.split('.')
|
|
262
|
-
return
|
|
256
|
+
return false if parts.length < 2
|
|
263
257
|
|
|
264
|
-
|
|
258
|
+
# L1: Legion::JSON only (string keys — the JWT payload is read by
|
|
259
|
+
# string key).
|
|
260
|
+
payload = ::Legion::JSON.parse(Base64.urlsafe_decode64(parts[1]), symbolize_names: false)
|
|
265
261
|
exp = payload['exp']
|
|
266
262
|
return true if exp.nil?
|
|
267
263
|
|
|
268
264
|
exp.to_i > Time.now.to_i
|
|
269
265
|
rescue StandardError => e
|
|
270
266
|
handle_exception(e, level: :warn, handled: true, operation: 'llm.credential_sources.token_valid')
|
|
271
|
-
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
# Simple recursive hash merge (project values override user values).
|
|
275
|
-
def deep_merge(base, override)
|
|
276
|
-
base.merge(override) do |_key, old_val, new_val|
|
|
277
|
-
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
|
|
278
|
-
deep_merge(old_val, new_val)
|
|
279
|
-
else
|
|
280
|
-
new_val
|
|
281
|
-
end
|
|
282
|
-
end
|
|
267
|
+
false
|
|
283
268
|
end
|
|
284
269
|
|
|
285
|
-
module_function :merge_claude_configs, :read_json,
|
|
286
|
-
:token_valid?, :deep_merge
|
|
270
|
+
module_function :merge_claude_configs, :read_json, :token_valid?
|
|
287
271
|
|
|
288
|
-
private_class_method :merge_claude_configs, :read_json,
|
|
289
|
-
:token_valid?, :deep_merge
|
|
272
|
+
private_class_method :merge_claude_configs, :read_json, :token_valid?
|
|
290
273
|
end
|
|
291
274
|
end
|
|
292
275
|
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'legion/extensions/actors/every'
|
|
5
|
+
rescue LoadError => e
|
|
6
|
+
warn(e.message) if $VERBOSE
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# The base actor inherits the LegionIO time-based Every actor, which only
|
|
10
|
+
# exists inside the daemon. In a standalone lex-llm load (specs, gem require)
|
|
11
|
+
# Every is absent — define nothing rather than crash. Provider actor files
|
|
12
|
+
# carry the same guard.
|
|
13
|
+
#
|
|
14
|
+
# Like the pipeline, this base lives in `discovery/`, NOT the scanned
|
|
15
|
+
# `actors/` dir: a file there would be claimed by the builder as a live
|
|
16
|
+
# per-extension actor (and auto-wrapped in a meta actor). A provider's live
|
|
17
|
+
# actor is its own `<Provider>::Actor::Discovery` (scanned), which subclasses
|
|
18
|
+
# this base with an EMPTY body.
|
|
19
|
+
return unless defined?(Legion::Extensions::Actors::Every)
|
|
20
|
+
|
|
21
|
+
module Legion
|
|
22
|
+
module Extensions
|
|
23
|
+
module Llm
|
|
24
|
+
module Discovery
|
|
25
|
+
# Base periodic discovery actor for EVERY lex-llm-* provider. This is
|
|
26
|
+
# the interface: a provider subclasses it with an EMPTY body and gets
|
|
27
|
+
# discovery for free —
|
|
28
|
+
#
|
|
29
|
+
# module Vllm
|
|
30
|
+
# module Actor
|
|
31
|
+
# class Discovery < Legion::Extensions::Llm::Discovery::Actor; end
|
|
32
|
+
# end
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# It fires on the configured discovery interval and dispatches each tick
|
|
36
|
+
# to the provider's `Runners::Discovery` module (resolved by convention
|
|
37
|
+
# from this actor's own namespace), which mixes in Discovery::Pipeline
|
|
38
|
+
# and owns the provider-specific fetch/health/evidence/callable. Every
|
|
39
|
+
# generic decision lives in the pipeline; a provider overrides a single
|
|
40
|
+
# method only when its runner lives somewhere non-conventional or its
|
|
41
|
+
# teardown differs.
|
|
42
|
+
#
|
|
43
|
+
# Dispatch contract (Every -> Actors::Base#manual): `use_runner? = false`
|
|
44
|
+
# calls the runner MODULE's `refresh` directly (no Legion::Runner task —
|
|
45
|
+
# the state and the Registry are process-local, so it must run on the
|
|
46
|
+
# owning node); `run_now? = true` fires once on boot then every `time`
|
|
47
|
+
# seconds.
|
|
48
|
+
class Actor < Legion::Extensions::Actors::Every
|
|
49
|
+
include Legion::Extensions::Helpers::Lex
|
|
50
|
+
|
|
51
|
+
def run_now? = true
|
|
52
|
+
def use_runner? = false
|
|
53
|
+
def runner_function = 'refresh'
|
|
54
|
+
|
|
55
|
+
# The provider's discovery runner module, resolved by convention from
|
|
56
|
+
# this actor's namespace: <ProviderModule>::Runners::Discovery. A
|
|
57
|
+
# provider whose runner lives elsewhere overrides this one method.
|
|
58
|
+
def runner_class
|
|
59
|
+
"#{provider_namespace}::Runners::Discovery"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Honor the registered discovery interval. A nil TimerTask interval
|
|
63
|
+
# fires once and then stops, so resolve to the registered default
|
|
64
|
+
# (300s) whenever the setting is missing or non-positive.
|
|
65
|
+
def time
|
|
66
|
+
interval = settings.dig(:instances, :default, :discovery_interval)&.to_i ||
|
|
67
|
+
settings.dig(:discovery, :interval_seconds)&.to_i
|
|
68
|
+
interval&.positive? ? interval : 300
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def shutdown
|
|
72
|
+
Kernel.const_get(runner_class).remove_all_instances
|
|
73
|
+
rescue StandardError => e
|
|
74
|
+
handle_exception(e, level: :warn, operation: "#{provider_slug}.actor.discovery.shutdown")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
# "Legion::Extensions::Llm::Vllm::Actor::Discovery" ->
|
|
80
|
+
# "Legion::Extensions::Llm::Vllm" (drop the trailing Actor::<Name>).
|
|
81
|
+
def provider_namespace
|
|
82
|
+
self.class.name.split('::')[0..-3].join('::')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def provider_slug
|
|
86
|
+
provider_namespace.split('::').last.to_s.downcase
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|