lex-llm-anthropic 0.2.6 → 0.2.8

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: f5ffb30606527237b262367541e2855945a51f3b678dae09cde5e874ad4b68dd
4
- data.tar.gz: d96e03b52d36827fca0a297e208994da831336f50e64aa234ad07602675835b2
3
+ metadata.gz: 23d189a1ea15ce64735d14d88766fcb6717136b9734c393599a7fbd3c7544b54
4
+ data.tar.gz: 5c3153468a9753fc406e5229ee41036113b5c396eb0b5761f933c2c8a78fba91
5
5
  SHA512:
6
- metadata.gz: 720aa58289499d4289a695e7b83e6f15a6ba6f2508d5fff9d49e203c5cd0c8c14ead5a8b3bcdd22d1f5e08346b6f9ac4c5fb1f54026de32063bcf9c966e2053f
7
- data.tar.gz: f2647cccc377307c382fec5f20acd6a778bf62c0914e59232a79ec6273c4398c73e89a4191a014c8e23e0159d4bc81ca4435096a21e5691c92164ba99dd713cf
6
+ metadata.gz: 97685da785d9c89c5b63cb64406c3487f12b943aa28b289ef553de7ca32ab3e454ef51f8cff9ad0af4762fadd760453899e64f902a79579d652c2ee045ce0b8f
7
+ data.tar.gz: 796d40a66a89b96ac401beb3b74237a09c7bfb2fa26819e589c937adfc771cc40051338899dc19c80204006a351fd0a7886af1524f74d7b507bbb1d8e0e6836a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.8 - 2026-05-13
4
+
5
+ - Remove `:claude` provider alias (`provider_aliases` now returns `[]`).
6
+ - Attach `source` and `credential_fingerprint` to all discovered instances.
7
+ - Inject `default_model: 'claude-sonnet-4-6'` and `capabilities: [:completion, :streaming, :vision]` into every discovered instance.
8
+ - Add static `CONTEXT_WINDOWS` map for known Claude model families.
9
+ - Override `fetch_model_detail` to return context window from static map.
10
+ - Use `model_detail` in `parse_list_models_response` for cached `context_length` lookup.
11
+ - Add `infer_context_window` helper for prefix-based context window inference.
12
+
13
+ ## 0.2.7 - 2026-05-13
14
+
15
+ - Use `Legion::Logging::Helper` for Anthropic provider and registry diagnostics.
16
+ - Route registry fallback errors through `handle_exception` with useful operation metadata.
17
+
3
18
  ## 0.2.6 - 2026-05-08
4
19
 
5
20
  - Accept keyword arguments in `list_models` to match the base provider contract called by `discover_offerings`.
@@ -1,12 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
5
+ actor_load_logger = Object.new.extend(Legion::Logging::Helper)
6
+ actor_load_logger.define_singleton_method(:lex_filename) { 'llm_anthropic' }
7
+
3
8
  begin
4
9
  require 'legion/extensions/actors/subscription'
5
10
  rescue LoadError => e
6
- warn(e.message) if $VERBOSE
11
+ subscription_load_error = e
7
12
  end
8
13
 
9
14
  unless defined?(Legion::Extensions::Actors::Subscription)
15
+ if subscription_load_error
16
+ actor_load_logger.handle_exception(subscription_load_error, level: :warn, handled: true,
17
+ operation: 'anthropic.actor.subscription_load')
18
+ end
10
19
  raise LoadError, 'LegionIO actor runtime is required for Anthropic fleet worker'
11
20
  end
12
21
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/extensions/llm'
4
+ require 'legion/logging/helper'
4
5
 
5
6
  module Legion
6
7
  module Extensions
@@ -8,6 +9,8 @@ module Legion
8
9
  module Anthropic
9
10
  # Anthropic Messages API provider implementation for the Legion::Extensions::Llm contract.
10
11
  class Provider < Legion::Extensions::Llm::Provider # rubocop:disable Metrics/ClassLength
12
+ include Legion::Logging::Helper
13
+
11
14
  class << self
12
15
  attr_writer :registry_publisher
13
16
 
@@ -52,14 +55,27 @@ module Legion
52
55
  end
53
56
 
54
57
  def list_models(**)
58
+ log.debug { 'listing available Anthropic models' }
55
59
  super.tap do |models|
60
+ log.debug { "discovered #{Array(models).size} Anthropic model(s); publishing to registry" }
56
61
  self.class.registry_publisher.publish_models_async(models, readiness: readiness(live: false))
57
62
  end
58
63
  end
59
64
 
65
+ CONTEXT_WINDOWS = {
66
+ 'claude-opus-4' => 200_000,
67
+ 'claude-sonnet-4' => 200_000,
68
+ 'claude-haiku-4' => 200_000,
69
+ 'claude-3-5' => 200_000,
70
+ 'claude-3-opus' => 200_000,
71
+ 'claude-3-sonnet' => 200_000,
72
+ 'claude-3-haiku' => 200_000
73
+ }.freeze
74
+
60
75
  private
61
76
 
62
77
  def render_payload(messages, tools:, temperature:, model:, stream:, schema:, thinking:, tool_prefs:) # rubocop:disable Metrics/ParameterLists
78
+ log_render_payload(messages:, tools:, model:, stream:, schema:)
63
79
  system_messages, chat_messages = messages.partition { |message| message.role == :system }
64
80
 
65
81
  {
@@ -76,6 +92,13 @@ module Legion
76
92
  }.compact
77
93
  end
78
94
 
95
+ def log_render_payload(messages:, tools:, model:, stream:, schema:)
96
+ log.debug do
97
+ "rendering Anthropic #{stream ? 'stream' : 'chat'} payload for #{model.id} " \
98
+ "with #{messages.size} message(s), #{tools.size} tool(s), schema=#{!schema.nil?}"
99
+ end
100
+ end
101
+
79
102
  def system_content(messages)
80
103
  content = messages.flat_map { |message| content_blocks(message.content) }
81
104
  content.empty? ? nil : content
@@ -347,14 +370,26 @@ module Legion
347
370
  def parse_list_models_response(response, provider, _capabilities)
348
371
  Array(response.body['data']).map do |model|
349
372
  model_id = model.fetch('id')
373
+ detail = model_detail(model_id)
374
+ ctx = detail&.dig(:context_window) || infer_context_window(model_id)
350
375
  Legion::Extensions::Llm::Model::Info.new(
351
376
  id: model_id,
352
377
  name: model['display_name'] || model_id,
353
378
  provider: provider,
379
+ context_length: ctx,
354
380
  metadata: model.merge('created_at' => model['created_at']).compact
355
381
  )
356
382
  end
357
383
  end
384
+
385
+ def infer_context_window(model_id)
386
+ CONTEXT_WINDOWS.find { |prefix, _| model_id.start_with?(prefix) }&.last
387
+ end
388
+
389
+ def fetch_model_detail(model_name)
390
+ ctx = infer_context_window(model_name)
391
+ ctx ? { context_window: ctx } : nil
392
+ end
358
393
  end
359
394
  end
360
395
  end
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  module Legion
4
6
  module Extensions
5
7
  module Llm
6
8
  module Anthropic
7
9
  # Builds sanitized lex-llm registry envelopes for Anthropic provider state.
8
10
  class RegistryEventBuilder
11
+ include Legion::Logging::Helper
12
+
9
13
  def model_available(model, readiness:)
10
14
  registry_event_class.available(
11
15
  model_offering(model),
@@ -54,7 +58,9 @@ module Legion
54
58
  configured_node = (::Legion::Settings.dig(:node, :canonical_name) if defined?(::Legion::Settings))
55
59
  value = configured_node.to_s.strip
56
60
  value.empty? ? :anthropic : value.to_sym
57
- rescue StandardError
61
+ rescue StandardError => e
62
+ handle_exception(e, level: :debug, handled: true,
63
+ operation: 'anthropic.registry.provider_instance')
58
64
  :anthropic
59
65
  end
60
66
 
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging/helper'
4
+
3
5
  module Legion
4
6
  module Extensions
5
7
  module Llm
6
8
  module Anthropic
7
9
  # Best-effort publisher for Anthropic provider availability events.
8
10
  class RegistryPublisher
11
+ include Legion::Logging::Helper
12
+
9
13
  APP_ID = 'lex-llm-anthropic'
10
14
 
11
15
  def initialize(builder: RegistryEventBuilder.new)
@@ -13,6 +17,7 @@ module Legion
13
17
  end
14
18
 
15
19
  def publish_models_async(models, readiness:)
20
+ log.debug { "publishing #{Array(models).size} Anthropic model event(s) to llm.registry" }
16
21
  schedule do
17
22
  Array(models).each do |model|
18
23
  publish_event(@builder.model_available(model, readiness:))
@@ -29,10 +34,12 @@ module Legion
29
34
  Thread.current.abort_on_exception = false
30
35
  yield
31
36
  rescue StandardError => e
32
- log_publish_failure(e, level: :debug)
37
+ handle_exception(e, level: :debug, handled: true,
38
+ operation: 'anthropic.registry.schedule_thread')
33
39
  end
34
40
  rescue StandardError => e
35
- log_publish_failure(e, level: :debug)
41
+ handle_exception(e, level: :debug, handled: true,
42
+ operation: 'anthropic.registry.schedule')
36
43
  false
37
44
  end
38
45
 
@@ -41,7 +48,8 @@ module Legion
41
48
 
42
49
  message_class.new(event:, app_id: APP_ID).publish(spool: false)
43
50
  rescue StandardError => e
44
- log_publish_failure(e)
51
+ handle_exception(e, level: :warn, handled: true,
52
+ operation: 'anthropic.registry.publish_event')
45
53
  false
46
54
  end
47
55
 
@@ -52,7 +60,9 @@ module Legion
52
60
  return true unless ::Legion::Transport::Connection.respond_to?(:session_open?)
53
61
 
54
62
  ::Legion::Transport::Connection.session_open?
55
- rescue StandardError
63
+ rescue StandardError => e
64
+ handle_exception(e, level: :debug, handled: true,
65
+ operation: 'anthropic.registry.publishing_available?')
56
66
  false
57
67
  end
58
68
 
@@ -66,7 +76,9 @@ module Legion
66
76
 
67
77
  require 'legion/extensions/llm/anthropic/transport/messages/registry_event'
68
78
  message_class_defined?
69
- rescue LoadError
79
+ rescue LoadError => e
80
+ handle_exception(e, level: :debug, handled: true,
81
+ operation: 'anthropic.registry.transport_load')
70
82
  false
71
83
  end
72
84
 
@@ -77,18 +89,6 @@ module Legion
77
89
  def message_class
78
90
  ::Legion::Extensions::Llm::Anthropic::Transport::Messages::RegistryEvent
79
91
  end
80
-
81
- def log_publish_failure(error, level: :warn)
82
- message = "[lex-llm-anthropic] 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
92
  end
93
93
  end
94
94
  end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Anthropic
7
- VERSION = '0.2.6'
7
+ VERSION = '0.2.8'
8
8
  end
9
9
  end
10
10
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/extensions/llm'
4
+ require 'legion/logging/helper'
4
5
  require 'legion/extensions/llm/anthropic/registry_event_builder'
5
6
  require 'legion/extensions/llm/anthropic/registry_publisher'
6
7
  require 'legion/extensions/llm/anthropic/provider'
@@ -10,8 +11,9 @@ module Legion
10
11
  module Extensions
11
12
  module Llm
12
13
  # Anthropic provider extension namespace.
13
- module Anthropic
14
+ module Anthropic # rubocop:disable Metrics/ModuleLength
14
15
  extend ::Legion::Extensions::Core if ::Legion::Extensions.const_defined?(:Core, false)
16
+ extend Legion::Logging::Helper
15
17
  extend Legion::Extensions::Llm::AutoRegistration
16
18
 
17
19
  PROVIDER_FAMILY = :anthropic
@@ -20,6 +22,7 @@ module Legion
20
22
  ::Legion::Extensions::Llm.provider_settings(
21
23
  family: PROVIDER_FAMILY,
22
24
  instance: {
25
+ default_model: 'claude-sonnet-4-6',
23
26
  endpoint: 'https://api.anthropic.com',
24
27
  tier: :frontier,
25
28
  transport: :http,
@@ -43,7 +46,7 @@ module Legion
43
46
  end
44
47
 
45
48
  def self.provider_aliases
46
- [:claude]
49
+ []
47
50
  end
48
51
 
49
52
  def self.discover_instances # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
@@ -54,7 +57,9 @@ module Legion
54
57
  candidates[:env] = {
55
58
  api_key: env_key,
56
59
  anthropic_api_key: env_key,
57
- tier: :frontier
60
+ tier: :frontier,
61
+ source: CredentialSources.source_tag(:env, 'ANTHROPIC_API_KEY'),
62
+ credential_fingerprint: CredentialSources.credential_fingerprint(env_key)
58
63
  }
59
64
  end
60
65
 
@@ -63,7 +68,9 @@ module Legion
63
68
  candidates[:claude] = {
64
69
  api_key: claude_key,
65
70
  anthropic_api_key: claude_key,
66
- tier: :frontier
71
+ tier: :frontier,
72
+ source: CredentialSources.source_tag(:file, '~/.claude/settings.json', 'anthropicApiKey'),
73
+ credential_fingerprint: CredentialSources.credential_fingerprint(claude_key)
67
74
  }
68
75
  end
69
76
 
@@ -74,7 +81,9 @@ module Legion
74
81
  candidates[:settings] = normalize_instance_config(settings_config).merge(
75
82
  api_key: settings_key,
76
83
  anthropic_api_key: settings_key,
77
- tier: :frontier
84
+ tier: :frontier,
85
+ source: CredentialSources.source_tag(:settings, 'extensions.llm.anthropic'),
86
+ credential_fingerprint: CredentialSources.credential_fingerprint(settings_key)
78
87
  )
79
88
  end
80
89
 
@@ -85,6 +94,10 @@ module Legion
85
94
  next unless normalized[:anthropic_api_key]
86
95
 
87
96
  normalized[:api_key] = normalized[:anthropic_api_key]
97
+ normalized[:source] =
98
+ CredentialSources.source_tag(:settings, "extensions.llm.anthropic.instances.#{name}")
99
+ normalized[:credential_fingerprint] =
100
+ CredentialSources.credential_fingerprint(normalized[:anthropic_api_key])
88
101
  candidates[name.to_sym] = normalized.merge(tier: :frontier)
89
102
  end
90
103
  end
@@ -95,12 +108,19 @@ module Legion
95
108
  candidates[:broker] = {
96
109
  api_key: broker_cred,
97
110
  anthropic_api_key: broker_cred,
98
- tier: :frontier
111
+ tier: :frontier,
112
+ source: CredentialSources.source_tag(:broker, 'identity', 'anthropic'),
113
+ credential_fingerprint: CredentialSources.credential_fingerprint(broker_cred)
99
114
  }
100
115
  end
101
116
  end
102
117
 
103
- CredentialSources.dedup_credentials(candidates).transform_values { |config| sanitize_instance_config(config) }
118
+ CredentialSources.dedup_credentials(candidates).transform_values do |config|
119
+ sanitized = sanitize_instance_config(config)
120
+ sanitized[:capabilities] ||= %i[completion streaming vision].freeze
121
+ sanitized[:default_model] ||= 'claude-sonnet-4-6'
122
+ sanitized
123
+ end
104
124
  end
105
125
 
106
126
  def self.settings_instances(config)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm-anthropic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO