legionio 1.9.36 → 1.9.37

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: 1de97753e1f584403f30a43a47049183871b98e7581bee36c892aac9e758983b
4
- data.tar.gz: 2042c10f023e59093f60258a65ccec746128c3efef7bb980ee33206dafef446c
3
+ metadata.gz: c5783df4062619770ed62ad40019ba12fd1a9d2eee8bd2ca184fa49c45dccf59
4
+ data.tar.gz: 275c32aa2d17855942601c525c4b7ac12850c033ee06ff87cc168ed8439791f8
5
5
  SHA512:
6
- metadata.gz: e206bc92436c737aa2046580af9b705d147d8601601703980d69944afbcc10189aa479527ee6f0da128eb0a80912d54dbc635f2c32eab4aaba2f7f21e595062e
7
- data.tar.gz: da6fc109223bf64dced09fd289f86a3f67592d2ad9a096f64376e0ecea074d2611671eda9fecadbfc14e528a377ee1c2a06e15cc2cbde0de7ffda24c524dab3f
6
+ metadata.gz: 1d75b48306c1a33c286893958639b503a298c805f8aef441c974b0eadbf77c4e8147bb72d1b03ca09e6df1512a0ae4e816fe0cf3b344e65d6c475a2589369a49
7
+ data.tar.gz: 0e9f0c9dd9d1a654ce98fb2117690dff57e81c9d83d09d6685451c2b3f847806a5e3d5e5f55641b0cdabdf67499fc61808ee9d63c7e700773d6fea173ec37eb1
data/CHANGELOG.md CHANGED
@@ -1,6 +1,18 @@
1
1
  # Legion Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [1.9.37] - 2026-05-29
4
+
5
+ ### Added
6
+ - LLM: namespace API enabled by default — LegionIO now routes all `/v1/` and `/api/llm/` traffic
7
+ through `Namespaces::Registration` (Sinatra::Namespace, Phases 0-4 complete in legion-llm ≥ 0.8.50)
8
+ - CLI: `legion setup proxy-mode` (alias: `proxy`) writes `~/.codex/config.toml` and
9
+ `~/.claude/settings.json` env block so Codex CLI and Claude Code connect to LegionIO at
10
+ `http://localhost:4567` out of the box. Supports `--port`, `--host`, `--force`, `--json`.
11
+
12
+ ### Fixed
13
+ - LLM: Anthropic namespace message translation now properly converts `tool_use`/`tool_result` content blocks to OpenAI format for vLLM dispatch (requires legion-llm ≥ 0.10.1)
14
+ - LLM: streaming tool_use blocks emitted inline with guaranteed ordering before `message_stop`
15
+ - LLM: curator preserves recent turns — no longer curates tool results from the current/previous turn
4
16
 
5
17
  ## [1.9.36] - 2026-05-22
6
18
 
data/Gemfile CHANGED
@@ -19,8 +19,11 @@ gem 'legion-mcp', path: '../legion-mcp' if File.exist?(File.expand_path('../legi
19
19
  gem 'legion-tty', path: '../legion-tty' if File.exist?(File.expand_path('../legion-tty', __dir__))
20
20
 
21
21
  gem 'lex-apollo', path: '../extensions/lex-apollo' if File.exist?(File.expand_path('../extensions/lex-apollo', __dir__))
22
+ gem 'lex-lex', path: '../extensions/lex-lex' if File.exist?(File.expand_path('../extensions/lex-lex', __dir__))
22
23
  gem 'lex-llm', path: '../extensions-ai/lex-llm' if File.exist?(File.expand_path('../extensions-ai/lex-llm', __dir__))
23
- # gem 'lex-llm-ledger', path: '../extensions-ai/lex-llm-ledger' if File.exist?(File.expand_path('../extensions-ai/lex-llm-ledger', __dir__))
24
+ gem 'lex-llm-ledger', path: '../extensions-ai/lex-llm-ledger' if File.exist?(File.expand_path('../extensions-ai/lex-llm-ledger', __dir__))
25
+ # gem 'lex-microsoft_teams', path: '../extensions/lex-microsoft_teams' if File.exist?(File.expand_path('../extensions/lex-microsoft_teams', __dir__))
26
+ # gem 'lex-lex', path: '../extensions/lex-lex' if File.exist?(File.expand_path('../extensions/lex-lex', __dir__))
24
27
 
25
28
  if File.exist?(File.expand_path('../extensions-identity/lex-identity-entra', __dir__))
26
29
  gem 'lex-identity-entra', path: '../extensions-identity/lex-identity-entra'
@@ -293,7 +293,7 @@ module Legion
293
293
 
294
294
  def install_single_gem(name, gem_bin, out)
295
295
  puts " Installing #{name}..." unless options[:json]
296
- output, success = shell_capture("#{gem_bin} install #{name} --no-document")
296
+ output, success = shell_capture("#{gem_bin} install #{name} --no-document --clear-sources --source https://rubygems.org/")
297
297
  if success
298
298
  out.success(" #{name} installed") unless options[:json]
299
299
  { name: name, status: 'installed' }
@@ -157,6 +157,34 @@ module Legion
157
157
  end
158
158
  end
159
159
 
160
+ desc 'proxy-mode', 'Configure Codex CLI and Claude Code to use LegionIO as a local API proxy'
161
+ option :port, type: :numeric, default: 4567, desc: 'LegionIO API port'
162
+ option :host, type: :string, default: 'localhost', desc: 'LegionIO API host'
163
+ def proxy_mode
164
+ out = formatter
165
+ base_url = "http://#{options[:host]}:#{options[:port]}/v1"
166
+ written = []
167
+ skipped = []
168
+
169
+ write_codex_config(base_url, written, skipped)
170
+ write_claude_code_proxy_config(base_url, written, skipped)
171
+
172
+ if options[:json]
173
+ out.json(written: written, skipped: skipped, base_url: base_url)
174
+ else
175
+ out.spacer
176
+ out.success("LegionIO proxy mode configured (#{written.size} written, #{skipped.size} skipped)")
177
+ written.each { |f| puts " Written: #{f}" }
178
+ skipped.each { |f| puts " Skipped (already exists, use --force to overwrite): #{f}" }
179
+ out.spacer
180
+ puts " LegionIO API: #{base_url.sub('/v1', '')}"
181
+ puts ' Codex CLI: legion llm proxy (uses ~/.codex/config.toml)'
182
+ puts ' Claude Code: set ANTHROPIC_BASE_URL in your shell or ~/.claude/settings.json'
183
+ out.spacer
184
+ end
185
+ end
186
+ map 'proxy' => :proxy_mode
187
+
160
188
  desc 'agentic', 'Install full cognitive stack (GAIA + LLM + Apollo + all agentic extensions)'
161
189
  option :dry_run, type: :boolean, default: false, desc: 'Show what would be installed without installing'
162
190
  def agentic
@@ -473,7 +501,7 @@ module Legion
473
501
 
474
502
  def install_gem(name, gem_bin, out)
475
503
  puts " Installing #{name}..." unless options[:json]
476
- output = `#{gem_bin} install #{name} --no-document 2>&1`
504
+ output = `#{gem_bin} install #{name} --no-document --clear-sources --source https://rubygems.org/ 2>&1`
477
505
  if $CHILD_STATUS.success?
478
506
  out.success(" #{name} installed") unless options[:json]
479
507
  { name: name, status: 'installed' }
@@ -712,6 +740,74 @@ module Legion
712
740
  end
713
741
  { name: 'VS Code', path: path, configured: configured }
714
742
  end
743
+
744
+ def write_codex_config(base_url, written, skipped)
745
+ codex_dir = File.expand_path('~/.codex')
746
+ codex_path = File.join(codex_dir, 'config.toml')
747
+
748
+ if File.exist?(codex_path) && !options[:force]
749
+ skipped << codex_path
750
+ return
751
+ end
752
+
753
+ FileUtils.mkdir_p(codex_dir)
754
+
755
+ content = <<~TOML
756
+ model = "legionio"
757
+ model_provider = "legion"
758
+
759
+ [model_providers.legion]
760
+ name = "LegionIO"
761
+ env_key = "LEGION_API_KEY"
762
+ base_url = "#{base_url}"
763
+ wire_api = "responses"
764
+ TOML
765
+
766
+ File.write(codex_path, content)
767
+ written << codex_path
768
+ rescue StandardError => e
769
+ raise Thor::Error, "Failed to write #{codex_path}: #{e.message}"
770
+ end
771
+
772
+ def write_claude_code_proxy_config(base_url, written, skipped)
773
+ claude_dir = File.expand_path('~/.claude')
774
+ claude_path = File.join(claude_dir, 'settings.json')
775
+
776
+ existing = if File.exist?(claude_path)
777
+ begin
778
+ ::JSON.parse(File.read(claude_path))
779
+ rescue ::JSON::ParserError
780
+ {}
781
+ end
782
+ else
783
+ {}
784
+ end
785
+
786
+ proxy_env = {
787
+ 'ANTHROPIC_BASE_URL' => base_url.sub(%r{/v1$}, ''),
788
+ 'ANTHROPIC_API_KEY' => 'legion',
789
+ 'ANTHROPIC_AUTH_TOKEN' => 'legion',
790
+ 'ANTHROPIC_DEFAULT_OPUS_MODEL' => 'legionio',
791
+ 'ANTHROPIC_DEFAULT_SONNET_MODEL' => 'legionio',
792
+ 'ANTHROPIC_DEFAULT_HAIKU_MODEL' => 'legionio'
793
+ }
794
+
795
+ current_env = existing['env'] || {}
796
+
797
+ already_set = proxy_env.all? { |k, v| current_env[k] == v }
798
+ if already_set && !options[:force]
799
+ skipped << claude_path
800
+ return
801
+ end
802
+
803
+ merged = existing.merge('env' => current_env.merge(proxy_env))
804
+
805
+ FileUtils.mkdir_p(claude_dir)
806
+ File.write(claude_path, ::JSON.pretty_generate(merged))
807
+ written << claude_path
808
+ rescue StandardError => e
809
+ raise Thor::Error, "Failed to write #{claude_path}: #{e.message}"
810
+ end
715
811
  end
716
812
  end
717
813
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ up do
5
+ alter_table(:tbi_patterns) do
6
+ add_index :pattern_type, name: :idx_tbi_patterns_type
7
+ add_index :tier, name: :idx_tbi_patterns_tier
8
+ end
9
+ end
10
+
11
+ down do
12
+ alter_table(:tbi_patterns) do
13
+ drop_index :tier, name: :idx_tbi_patterns_tier
14
+ drop_index :pattern_type, name: :idx_tbi_patterns_type
15
+ end
16
+ end
17
+ end
@@ -65,8 +65,10 @@ module Legion
65
65
  end
66
66
 
67
67
  def prepare # rubocop:disable Metrics/AbcSize
68
+ @dedicated_channel = create_dedicated_channel
68
69
  @queue = queue.new
69
- @queue.channel.prefetch(prefetch) if defined? prefetch
70
+ reassign_queue_channel(@queue, @dedicated_channel)
71
+ @dedicated_channel.prefetch(prefetch) if defined? prefetch
70
72
  consumer_tag = "#{Legion::Settings[:client][:name]}_#{lex_name}_#{runner_name}_#{SecureRandom.uuid}"
71
73
  @consumer = Bunny::Consumer.new(@queue.channel, @queue, consumer_tag, false, false)
72
74
  @consumer.on_delivery do |delivery_info, metadata, payload|
@@ -298,6 +300,21 @@ module Legion
298
300
  end
299
301
  end
300
302
 
303
+ def create_dedicated_channel
304
+ s = Legion::Transport::Connection.session
305
+ raise IOError, 'transport session unavailable' unless s&.open?
306
+
307
+ settings = Legion::Transport::Connection.settings
308
+ s.create_channel(nil, settings[:channel][:default_worker_pool_size], false, 10)
309
+ end
310
+
311
+ def reassign_queue_channel(queue_instance, new_channel)
312
+ old_channel = queue_instance.channel
313
+ old_channel.deregister_queue(queue_instance) if old_channel.respond_to?(:deregister_queue)
314
+ queue_instance.instance_variable_set(:@channel, new_channel)
315
+ new_channel.register_queue(queue_instance) if new_channel.respond_to?(:register_queue)
316
+ end
317
+
301
318
  def republish_with_retry_count(_delivery_info, metadata, payload, new_count)
302
319
  headers = (metadata&.headers || {}).dup
303
320
  headers[RetryPolicy::RETRY_COUNT_HEADER] = new_count
@@ -7,7 +7,10 @@ module Legion
7
7
  module EmbeddingSimilarity
8
8
  class << self
9
9
  def check(input, safe_embeddings:, threshold: 0.3)
10
- return { safe: true, reason: 'no embeddings service' } unless defined?(Legion::LLM) && Legion::LLM.respond_to?(:embed)
10
+ unless defined?(Legion::LLM) && Legion::LLM.respond_to?(:embed) && Legion::LLM.respond_to?(:started?) && Legion::LLM.started?
11
+ return { safe: true,
12
+ reason: 'no embeddings service' }
13
+ end
11
14
 
12
15
  input_vec = Legion::LLM.embed(input)
13
16
  return { safe: true, reason: 'embedding failed' } unless input_vec
@@ -18,6 +21,8 @@ module Legion
18
21
  Legion::Logging.warn "[Guardrails] EmbeddingSimilarity rejected input: distance=#{min_dist.round(4)} threshold=#{threshold}"
19
22
  end
20
23
  { safe: safe, distance: min_dist.round(4), threshold: threshold }
24
+ rescue StandardError
25
+ { safe: true, reason: 'embedding failed' }
21
26
  end
22
27
 
23
28
  def cosine_distance(vec_a, vec_b)
@@ -452,6 +452,7 @@ module Legion
452
452
  log.info 'Setting up Legion::LLM'
453
453
  require 'legion/llm'
454
454
  Legion::Settings.merge_settings('llm', Legion::LLM::Settings.default)
455
+ Legion::Settings.loader.settings[:llm][:api][:use_namespaces] = true
455
456
  preload_llm_providers
456
457
  Legion::LLM.start
457
458
  log.info 'Legion::LLM started'
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ up do
5
+ alter_table(:tool_embedding_cache) do
6
+ add_index :tool_name, name: :idx_tool_embedding_cache_tool_name
7
+ end
8
+ end
9
+
10
+ down do
11
+ alter_table(:tool_embedding_cache) do
12
+ drop_index :tool_name, name: :idx_tool_embedding_cache_tool_name
13
+ end
14
+ end
15
+ end
@@ -72,6 +72,9 @@ module Legion
72
72
  )
73
73
  Legion::Logging.error "[TraceSearch] LLM filter generation failed for query: #{query.inspect}" if !result[:valid] && defined?(Legion::Logging)
74
74
  result[:data] if result[:valid]
75
+ rescue Legion::LLM::LLMError => e
76
+ handle_exception(e, level: :debug, handled: true, operation: 'trace_search.generate_filter') if respond_to?(:handle_exception)
77
+ nil
75
78
  end
76
79
 
77
80
  def schema_context
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Legion
4
- VERSION = '1.9.36'
4
+ VERSION = '1.9.37'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legionio
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.36
4
+ version: 1.9.37
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -780,6 +780,7 @@ files:
780
780
  - lib/legion/data/local_migrations/20250601000001_create_tbi_patterns.rb
781
781
  - lib/legion/data/local_migrations/20260319000001_create_extension_catalog.rb
782
782
  - lib/legion/data/local_migrations/20260319000002_create_extension_permissions.rb
783
+ - lib/legion/data/local_migrations/20260528000001_add_tbi_patterns_indexes.rb
783
784
  - lib/legion/data/models/tbi_pattern.rb
784
785
  - lib/legion/digital_worker.rb
785
786
  - lib/legion/digital_worker/airb.rb
@@ -914,6 +915,7 @@ files:
914
915
  - lib/legion/tools/do.rb
915
916
  - lib/legion/tools/embedding_cache.rb
916
917
  - lib/legion/tools/embedding_cache/migrations/001_create_tool_embedding_cache.rb
918
+ - lib/legion/tools/embedding_cache/migrations/002_add_tool_name_index.rb
917
919
  - lib/legion/tools/registry.rb
918
920
  - lib/legion/tools/status.rb
919
921
  - lib/legion/tools/trigger_index.rb