legionio 1.6.21 → 1.6.24

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.
@@ -1,13 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'base'
4
+ require_relative 'dsl'
4
5
 
5
6
  module Legion
6
7
  module Extensions
7
8
  module Actors
8
9
  class Once
10
+ extend Legion::Extensions::Actors::Dsl
9
11
  include Legion::Extensions::Actors::Base
10
12
 
13
+ define_dsl_accessor :delay, default: 1.0
14
+
11
15
  def initialize
12
16
  return unless enabled?
13
17
 
@@ -2,15 +2,22 @@
2
2
 
3
3
  require_relative 'base'
4
4
  require_relative 'fingerprint'
5
+ require_relative 'dsl'
5
6
  require 'time'
6
7
 
7
8
  module Legion
8
9
  module Extensions
9
10
  module Actors
10
11
  class Poll
12
+ extend Legion::Extensions::Actors::Dsl
11
13
  include Legion::Extensions::Actors::Base
12
14
  include Legion::Extensions::Actors::Fingerprint
13
15
 
16
+ define_dsl_accessor :time, default: 9
17
+ define_dsl_accessor :timeout, default: 5
18
+ define_dsl_accessor :run_now, default: true
19
+ define_dsl_accessor :int_percentage_normalize, default: 0.00
20
+
14
21
  def initialize
15
22
  log.debug "Starting timer for #{self.class} with #{{ execution_interval: time, run_now: run_now?,
16
23
  check_subtask: check_subtask? }}"
@@ -64,20 +71,8 @@ check_subtask: check_subtask? }}"
64
71
  0.00
65
72
  end
66
73
 
67
- def time
68
- 9
69
- end
70
-
71
74
  def run_now?
72
- true
73
- end
74
-
75
- def check_subtask?
76
- true
77
- end
78
-
79
- def timeout
80
- 5
75
+ run_now
81
76
  end
82
77
 
83
78
  def action(_payload = {})
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'base'
4
+ require_relative 'dsl'
4
5
  require 'date'
5
6
  require 'securerandom'
6
7
 
@@ -8,10 +9,17 @@ module Legion
8
9
  module Extensions
9
10
  module Actors
10
11
  class Subscription
12
+ extend Legion::Extensions::Actors::Dsl
11
13
  include Concurrent::Async
12
14
  include Legion::Extensions::Actors::Base
13
15
  include Legion::Extensions::Helpers::Transport
14
16
 
17
+ define_dsl_accessor :consumers, default: 1
18
+ define_dsl_accessor :manual_ack, default: true
19
+ define_dsl_accessor :delay_start, default: 0
20
+ define_dsl_accessor :block, default: false
21
+ define_dsl_accessor :prefetch, default: 2
22
+
15
23
  def initialize(**_options)
16
24
  super()
17
25
  @queue = queue.new
@@ -25,8 +33,7 @@ module Legion
25
33
  exchange_object = default_exchange.new
26
34
  queue_object = Kernel.const_get(queue_string).new
27
35
 
28
- queue_object.bind(exchange_object, routing_key: actor_name)
29
- queue_object.bind(exchange_object, routing_key: "#{lex_name}.#{actor_name}.#")
36
+ queue_object.bind(exchange_object, routing_key: "#{amqp_prefix}.runners.#{runner_name}.#")
30
37
  end
31
38
 
32
39
  def queue
@@ -93,22 +100,6 @@ module Legion
93
100
  log.info "[Subscription] activated: #{lex_name}/#{runner_name} (consumer registered)"
94
101
  end
95
102
 
96
- def block
97
- false
98
- end
99
-
100
- def consumers
101
- 1
102
- end
103
-
104
- def manual_ack
105
- true
106
- end
107
-
108
- def delay_start
109
- 0
110
- end
111
-
112
103
  def include_metadata_in_message?
113
104
  true
114
105
  end
@@ -28,8 +28,8 @@ module Legion
28
28
  hook_class = Kernel.const_get(hook_class_name)
29
29
  next unless hook_class < Legion::Extensions::Hooks::Base
30
30
 
31
- mount_suffix = hook_class.mount_path || ''
32
- route_path = "#{extension_name}/#{hook_name}#{mount_suffix}"
31
+ route_path = "#{extension_name}/#{hook_name}"
32
+ runner = hook_class.respond_to?(:runner_class) ? hook_class.runner_class : nil
33
33
 
34
34
  @hooks[hook_name.to_sym] = {
35
35
  extension: lex_class.to_s.downcase,
@@ -38,6 +38,23 @@ module Legion
38
38
  hook_class: hook_class,
39
39
  route_path: route_path
40
40
  }
41
+
42
+ next unless defined?(Legion::API) && Legion::API.respond_to?(:router)
43
+
44
+ # Register hook component in the router (explicit methods derived from hook class)
45
+ hook_methods = hook_class.public_instance_methods(false).reject { |m| m.to_s.start_with?('_') }
46
+ hook_methods = [:handle] if hook_methods.empty?
47
+ hook_methods.each do |method_name|
48
+ Legion::API.router.register_extension_route(
49
+ lex_name: extension_name,
50
+ amqp_prefix: respond_to?(:amqp_prefix) ? amqp_prefix : "lex.#{extension_name.to_s.tr('_', '.')}",
51
+ component_type: 'hooks',
52
+ component_name: hook_name,
53
+ method_name: method_name.to_s,
54
+ runner_class: runner || hook_class,
55
+ definition: hook_class.respond_to?(:definition_for) ? hook_class.definition_for(method_name) : nil
56
+ )
57
+ end
41
58
  end
42
59
  end
43
60
 
@@ -28,14 +28,29 @@ module Legion
28
28
 
29
29
  methods.each do |function|
30
30
  route_path = "#{extension_name}/#{runner_name}/#{function}"
31
+ defn = runner_module.respond_to?(:definition_for) ? runner_module.definition_for(function) : nil
31
32
  log.info "[Routes] auto-route registered: POST /api/lex/#{route_path}"
32
33
  @routes[route_path] = {
33
- lex_name: extension_name,
34
- runner_name: runner_name,
35
- function: function,
36
- runner_class: runner_class,
37
- route_path: route_path
34
+ lex_name: extension_name,
35
+ runner_name: runner_name,
36
+ function: function,
37
+ component_type: 'runners',
38
+ runner_class: runner_class,
39
+ route_path: route_path,
40
+ definition: defn
38
41
  }
42
+
43
+ next unless defined?(Legion::API) && Legion::API.respond_to?(:router)
44
+
45
+ Legion::API.router.register_extension_route(
46
+ lex_name: extension_name,
47
+ amqp_prefix: respond_to?(:amqp_prefix) ? amqp_prefix : "lex.#{extension_name.to_s.tr('_', '.')}",
48
+ component_type: 'runners',
49
+ component_name: runner_name,
50
+ method_name: function.to_s,
51
+ runner_class: runner_class,
52
+ definition: defn
53
+ )
39
54
  end
40
55
  end
41
56
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'base'
4
+ require_relative '../definitions'
4
5
 
5
6
  module Legion
6
7
  module Extensions
@@ -22,6 +23,7 @@ module Legion
22
23
  runner_name = file.split('/').last.sub('.rb', '')
23
24
  runner_class = "#{lex_class}::Runners::#{runner_name.split('_').collect(&:capitalize).join}"
24
25
  loaded_runner = Kernel.const_get(runner_class)
26
+ loaded_runner.extend(Legion::Extensions::Definitions) unless loaded_runner.respond_to?(:definition)
25
27
  Legion::Logging.debug "[Runners] registered: #{runner_class}" if defined?(Legion::Logging)
26
28
 
27
29
  @runners[runner_name.to_sym] = {
@@ -76,6 +76,7 @@ module Legion
76
76
  end
77
77
  build_helpers
78
78
  build_runners
79
+ generate_messages_from_definitions
79
80
  build_absorbers
80
81
  build_actors
81
82
  build_hooks
@@ -113,6 +114,55 @@ module Legion
113
114
  true
114
115
  end
115
116
 
117
+ # Auto-generate AMQP message classes for each runner method that has a definition.
118
+ # Explicit Messages::* classes in the transport directory take precedence.
119
+ # Runs after build_runners so definitions are populated.
120
+ def generate_messages_from_definitions
121
+ ctx = message_generation_context
122
+ return unless ctx
123
+
124
+ @runners.each do |runner_name, attr|
125
+ generate_runner_messages(ctx, runner_name, attr)
126
+ end
127
+ rescue StandardError => e
128
+ log.warn "[Core] generate_messages_from_definitions failed: #{e.message}" if defined?(log)
129
+ end
130
+
131
+ def message_generation_context
132
+ return unless defined?(Legion::Transport::Message)
133
+ return unless lex_class.const_defined?('Transport', false)
134
+
135
+ transport_mod = lex_class::Transport
136
+ return unless transport_mod.const_defined?('Messages', false) && transport_mod.const_defined?('Exchanges', false)
137
+
138
+ default_exch = transport_mod.default_exchange
139
+ { messages_mod: transport_mod::Messages, default_exch: default_exch, prefix: amqp_prefix }
140
+ rescue StandardError
141
+ nil
142
+ end
143
+
144
+ def generate_runner_messages(ctx, runner_name, attr)
145
+ runner_module = attr[:runner_module]
146
+ return unless runner_module.respond_to?(:definitions)
147
+
148
+ runner_module.definitions.each_key do |method_name|
149
+ const_name = "#{camelize(runner_name)}#{camelize(method_name)}"
150
+ next if ctx[:messages_mod].const_defined?(const_name, false)
151
+
152
+ rk_value = "#{ctx[:prefix]}.runners.#{runner_name}.#{method_name}"
153
+ ctx[:messages_mod].const_set(const_name, Class.new(Legion::Transport::Message) do
154
+ define_method(:exchange) { ctx[:default_exch] }
155
+ define_method(:routing_key) { rk_value }
156
+ end)
157
+ end
158
+ rescue StandardError => e
159
+ log.warn "[Core] message generation error for #{runner_name}: #{e.message}" if defined?(log)
160
+ end
161
+
162
+ def camelize(name)
163
+ name.to_s.split('_').collect(&:capitalize).join
164
+ end
165
+
116
166
  def build_data
117
167
  Legion::Logging.debug "[Core] build_data: #{name}" if defined?(Legion::Logging)
118
168
  auto_generate_data
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Definitions
6
+ DEFAULTS = {
7
+ remote_invocable: true,
8
+ mcp_exposed: true,
9
+ idempotent: false,
10
+ risk_tier: :standard,
11
+ tags: [],
12
+ requires: [],
13
+ inputs: {},
14
+ outputs: {}
15
+ }.freeze
16
+
17
+ def definition(method_name, **opts)
18
+ base = DEFAULTS.transform_values do |value|
19
+ case value
20
+ when Array, Hash
21
+ value.dup
22
+ else
23
+ value
24
+ end
25
+ end
26
+ own_definitions[method_name.to_sym] = base.merge(opts)
27
+ end
28
+
29
+ def definitions
30
+ if respond_to?(:superclass) && superclass.respond_to?(:definitions)
31
+ superclass.definitions.merge(own_definitions)
32
+ else
33
+ own_definitions.dup
34
+ end
35
+ end
36
+
37
+ def definition_for(method_name)
38
+ definitions[method_name.to_sym]
39
+ end
40
+
41
+ private
42
+
43
+ def own_definitions
44
+ @own_definitions ||= {}
45
+ end
46
+ end
47
+ end
48
+ end
@@ -13,6 +13,7 @@ module Legion
13
13
  include Legion::Extensions::Helpers::Secret
14
14
 
15
15
  module ClassMethods
16
+ # @deprecated Use mcp_exposed: flag in definition DSL instead
16
17
  def expose_as_mcp_tool(value = :_unset)
17
18
  if value == :_unset
18
19
  return @expose_as_mcp_tool unless @expose_as_mcp_tool.nil?
@@ -27,6 +28,7 @@ module Legion
27
28
  end
28
29
  end
29
30
 
31
+ # @deprecated Use mcp_exposed: flag in definition DSL instead
30
32
  def mcp_tool_prefix(value = :_unset)
31
33
  if value == :_unset
32
34
  @mcp_tool_prefix
@@ -36,42 +38,52 @@ module Legion
36
38
  end
37
39
  end
38
40
 
41
+ # @deprecated Use definition DSL instead: definition :method, desc:, inputs:, outputs:
39
42
  def function_example(function, example)
40
43
  function_set(function, :example, example)
41
44
  end
42
45
 
46
+ # @deprecated Use definition DSL instead: definition :method, inputs: { ... }
43
47
  def function_options(function, options)
44
48
  function_set(function, :options, options)
45
49
  end
46
50
 
51
+ # @deprecated Use definition DSL instead: definition :method, desc: '...'
47
52
  def function_desc(function, desc)
48
53
  function_set(function, :desc, desc)
49
54
  end
50
55
 
56
+ # @deprecated Use definition DSL instead: definition :method, outputs: { ... }
51
57
  def function_outputs(function, outputs)
52
58
  function_set(function, :outputs, outputs)
53
59
  end
54
60
 
61
+ # @deprecated Use definition DSL instead: definition :method, category: '...'
55
62
  def function_category(function, category)
56
63
  function_set(function, :category, category)
57
64
  end
58
65
 
66
+ # @deprecated Use definition DSL instead: definition :method, tags: [...]
59
67
  def function_tags(function, tags)
60
68
  function_set(function, :tags, tags)
61
69
  end
62
70
 
71
+ # @deprecated Use definition DSL instead: definition :method, risk_tier: :standard
63
72
  def function_risk_tier(function, tier)
64
73
  function_set(function, :risk_tier, tier)
65
74
  end
66
75
 
76
+ # @deprecated Use definition DSL instead: definition :method, idempotent: true
67
77
  def function_idempotent(function, value)
68
78
  function_set(function, :idempotent, value)
69
79
  end
70
80
 
81
+ # @deprecated Use definition DSL instead: definition :method, requires: [...]
71
82
  def function_requires(function, deps)
72
83
  function_set(function, :requires, deps)
73
84
  end
74
85
 
86
+ # @deprecated Use definition DSL instead: definition :method, mcp_exposed: true
75
87
  def function_expose(function, value)
76
88
  function_set(function, :expose, value)
77
89
  end
@@ -27,7 +27,7 @@ module Legion
27
27
  end
28
28
 
29
29
  def segments_to_amqp_prefix(segments)
30
- "legion.#{segments.join('.')}"
30
+ "lex.#{segments.join('.')}"
31
31
  end
32
32
 
33
33
  def segments_to_settings_path(segments)
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../definitions'
4
+
3
5
  module Legion
4
6
  module Extensions
5
7
  module Hooks
6
8
  class Base
9
+ extend Legion::Extensions::Definitions
7
10
  include Legion::Extensions::Helpers::Lex
8
11
 
9
12
  class << self
@@ -44,12 +47,8 @@ module Legion
44
47
  @verify_config = { header: header.upcase.tr('-', '_'), secret: secret }
45
48
  end
46
49
 
47
- def mount(path)
48
- @mount_path = path
49
- end
50
-
51
50
  attr_reader :route_type, :route_header_name, :route_field_name,
52
- :route_mapping, :verify_type, :verify_config, :mount_path
51
+ :route_mapping, :verify_type, :verify_config
53
52
  end
54
53
 
55
54
  # Instance methods called by the API layer
@@ -63,7 +62,7 @@ module Legion
63
62
  when :field
64
63
  route_by_field(payload)
65
64
  else
66
- :handle
65
+ :handle # deprecated fallback; prefer explicit route_header/route_field
67
66
  end
68
67
  end
69
68
 
@@ -146,7 +146,7 @@ module Legion
146
146
  return [] if @exchanges.count != 1
147
147
 
148
148
  @queues.map do |queue|
149
- { from: @exchanges.first, to: queue, routing_key: queue }
149
+ { from: @exchanges.first, to: queue, routing_key: "#{amqp_prefix}.runners.#{queue}.#" }
150
150
  end
151
151
  end
152
152
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Legion
4
- VERSION = '1.6.21'
4
+ VERSION = '1.6.24'
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.6.21
4
+ version: 1.6.24
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -470,6 +470,8 @@ files:
470
470
  - lib/legion/api/helpers.rb
471
471
  - lib/legion/api/hooks.rb
472
472
  - lib/legion/api/lex.rb
473
+ - lib/legion/api/lex_dispatch.rb
474
+ - lib/legion/api/library_routes.rb
473
475
  - lib/legion/api/llm.rb
474
476
  - lib/legion/api/marketplace.rb
475
477
  - lib/legion/api/metrics.rb
@@ -485,9 +487,11 @@ files:
485
487
  - lib/legion/api/prompts.rb
486
488
  - lib/legion/api/rbac.rb
487
489
  - lib/legion/api/relationships.rb
490
+ - lib/legion/api/router.rb
488
491
  - lib/legion/api/schedules.rb
489
492
  - lib/legion/api/settings.rb
490
493
  - lib/legion/api/stats.rb
494
+ - lib/legion/api/sync_dispatch.rb
491
495
  - lib/legion/api/tasks.rb
492
496
  - lib/legion/api/tenants.rb
493
497
  - lib/legion/api/token.rb
@@ -511,6 +515,7 @@ files:
511
515
  - lib/legion/cli.rb
512
516
  - lib/legion/cli/absorb_command.rb
513
517
  - lib/legion/cli/acp_command.rb
518
+ - lib/legion/cli/admin/purge_topology.rb
514
519
  - lib/legion/cli/apollo_command.rb
515
520
  - lib/legion/cli/audit_command.rb
516
521
  - lib/legion/cli/auth_command.rb
@@ -524,6 +529,7 @@ files:
524
529
  - lib/legion/cli/chat/checkpoint.rb
525
530
  - lib/legion/cli/chat/context.rb
526
531
  - lib/legion/cli/chat/context_manager.rb
532
+ - lib/legion/cli/chat/daemon_chat.rb
527
533
  - lib/legion/cli/chat/extension_tool.rb
528
534
  - lib/legion/cli/chat/extension_tool_loader.rb
529
535
  - lib/legion/cli/chat/markdown_renderer.rb
@@ -749,6 +755,7 @@ files:
749
755
  - lib/legion/extensions/actors/absorber_dispatch.rb
750
756
  - lib/legion/extensions/actors/base.rb
751
757
  - lib/legion/extensions/actors/defaults.rb
758
+ - lib/legion/extensions/actors/dsl.rb
752
759
  - lib/legion/extensions/actors/every.rb
753
760
  - lib/legion/extensions/actors/fingerprint.rb
754
761
  - lib/legion/extensions/actors/loop.rb
@@ -771,6 +778,7 @@ files:
771
778
  - lib/legion/extensions/data.rb
772
779
  - lib/legion/extensions/data/migrator.rb
773
780
  - lib/legion/extensions/data/model.rb
781
+ - lib/legion/extensions/definitions.rb
774
782
  - lib/legion/extensions/helpers/base.rb
775
783
  - lib/legion/extensions/helpers/cache.rb
776
784
  - lib/legion/extensions/helpers/core.rb