legionio 1.9.31 → 1.9.33
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 +21 -0
- data/Gemfile +0 -1
- data/lib/legion/cli/chat/extension_tool_loader.rb +1 -1
- data/lib/legion/cli/chat/permissions.rb +6 -7
- data/lib/legion/cli/chat/tool_registry.rb +83 -93
- data/lib/legion/cli/chat/tools/arbitrage_status.rb +13 -12
- data/lib/legion/cli/chat/tools/budget_status.rb +20 -19
- data/lib/legion/cli/chat/tools/consolidate_memory.rb +14 -11
- data/lib/legion/cli/chat/tools/cost_summary.rb +18 -16
- data/lib/legion/cli/chat/tools/detect_anomalies.rb +13 -11
- data/lib/legion/cli/chat/tools/edit_file.rb +16 -12
- data/lib/legion/cli/chat/tools/entity_extract.rb +18 -24
- data/lib/legion/cli/chat/tools/escalation_status.rb +13 -12
- data/lib/legion/cli/chat/tools/generate_insights.rb +23 -24
- data/lib/legion/cli/chat/tools/graph_explore.rb +17 -19
- data/lib/legion/cli/chat/tools/ingest_knowledge.rb +17 -14
- data/lib/legion/cli/chat/tools/knowledge_maintenance.rb +16 -12
- data/lib/legion/cli/chat/tools/knowledge_stats.rb +8 -9
- data/lib/legion/cli/chat/tools/list_extensions.rb +17 -15
- data/lib/legion/cli/chat/tools/manage_schedules.rb +21 -19
- data/lib/legion/cli/chat/tools/manage_tasks.rb +27 -36
- data/lib/legion/cli/chat/tools/memory_status.rb +23 -23
- data/lib/legion/cli/chat/tools/model_comparison.rb +18 -21
- data/lib/legion/cli/chat/tools/provider_health.rb +22 -19
- data/lib/legion/cli/chat/tools/query_knowledge.rb +15 -11
- data/lib/legion/cli/chat/tools/read_file.rb +12 -6
- data/lib/legion/cli/chat/tools/reflect.rb +19 -16
- data/lib/legion/cli/chat/tools/relate_knowledge.rb +16 -12
- data/lib/legion/cli/chat/tools/run_command.rb +17 -13
- data/lib/legion/cli/chat/tools/save_memory.rb +13 -10
- data/lib/legion/cli/chat/tools/scheduling_status.rb +16 -15
- data/lib/legion/cli/chat/tools/search_content.rb +12 -6
- data/lib/legion/cli/chat/tools/search_files.rb +11 -5
- data/lib/legion/cli/chat/tools/search_memory.rb +12 -8
- data/lib/legion/cli/chat/tools/search_traces.rb +31 -27
- data/lib/legion/cli/chat/tools/shadow_eval_status.rb +15 -14
- data/lib/legion/cli/chat/tools/spawn_agent.rb +12 -8
- data/lib/legion/cli/chat/tools/summarize_traces.rb +14 -11
- data/lib/legion/cli/chat/tools/system_status.rb +10 -11
- data/lib/legion/cli/chat/tools/trigger_dream.rb +19 -17
- data/lib/legion/cli/chat/tools/view_events.rb +14 -12
- data/lib/legion/cli/chat/tools/view_trends.rb +18 -18
- data/lib/legion/cli/chat/tools/web_search.rb +11 -5
- data/lib/legion/cli/chat/tools/worker_status.rb +19 -18
- data/lib/legion/cli/chat/tools/write_file.rb +11 -5
- data/lib/legion/cli/generate_command.rb +13 -6
- data/lib/legion/identity/process.rb +50 -36
- data/lib/legion/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a0e7ab0c576dcf1950fb2dd7d38d1113dfd5f584883cd30286cd990f851ce6a3
|
|
4
|
+
data.tar.gz: a4dc69a591dd00dbb757f5e1bc5b8913005e374fed607b63d5f28088e51018a1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ef50ae89f614aeec8385558444afb27f57aa1797e415ce25361ec57dec5d87ecf1181fdaf9e59bf0e696cc4ec8d68c0aa9c641bb30b1c01c08a72dd5792d0e6e
|
|
7
|
+
data.tar.gz: e9049722257ccdc3415ec4e33af0100d692dbf85806cd2c1fb1f4094c1189364d575943e0f39907bcc295ec7fecabfd8065aae57c0857997a22dd0904ea958ea
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.9.33] - 2026-05-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `Legion::Identity::Process` stores and exposes `db_principal_id` and `db_identity_id` integer PKs — present in `EMPTY_STATE`, persisted through `bind!`, and included in `identity_hash`. Both default to nil until an identity provider populates them.
|
|
9
|
+
|
|
10
|
+
## [1.9.32] - 2026-05-14
|
|
11
|
+
|
|
12
|
+
### Removed
|
|
13
|
+
- Removed `gem 'ruby_llm'` dependency from Gemfile; all 40 CLI chat tools now use `Legion::Tools::Base` natively.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Migrated all 40 CLI chat tool classes from `RubyLLM::Tool` to `Legion::Tools::Base`:
|
|
17
|
+
- `param` DSL replaced with `input_schema` (JSON Schema hash)
|
|
18
|
+
- `def execute` instance method replaced with `def self.call` class method
|
|
19
|
+
- Explicit `tool_name 'legion.<snake_case>'` added to each tool
|
|
20
|
+
- Private instance helpers converted to class methods
|
|
21
|
+
- Updated `tool_registry.rb`: removed `require 'ruby_llm'` and `begin/rescue LoadError` guard.
|
|
22
|
+
- Updated `extension_tool_loader.rb`: `klass < RubyLLM::Tool` changed to `klass < Legion::Tools::Base`.
|
|
23
|
+
- Updated `generate_command.rb` tool template to emit `Legion::Tools::Base` with `input_schema` and `def self.call`.
|
|
24
|
+
- `Permissions::Gate` now prepends on the singleton class to intercept `self.call` correctly.
|
|
25
|
+
|
|
5
26
|
## [1.9.31] - 2026-05-14
|
|
6
27
|
|
|
7
28
|
### Added
|
data/Gemfile
CHANGED
|
@@ -24,7 +24,7 @@ module Legion
|
|
|
24
24
|
def collect_tool_classes(tools_module)
|
|
25
25
|
tools_module.constants.filter_map do |const_name|
|
|
26
26
|
klass = tools_module.const_get(const_name)
|
|
27
|
-
klass if klass.is_a?(Class) && klass <
|
|
27
|
+
klass if klass.is_a?(Class) && klass < Legion::Tools::Base
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
@@ -58,16 +58,15 @@ module Legion
|
|
|
58
58
|
def apply!(tool_classes)
|
|
59
59
|
tool_classes.each do |klass|
|
|
60
60
|
tier = tier_for(klass)
|
|
61
|
-
klass.prepend(Gate) unless tier == :read
|
|
61
|
+
klass.singleton_class.prepend(Gate) unless tier == :read
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
module Gate
|
|
67
|
-
def call(args)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return 'Tool execution denied by user.' unless Permissions.confirm?(desc)
|
|
67
|
+
def call(**args)
|
|
68
|
+
desc = permission_description(args)
|
|
69
|
+
return error_response('Tool execution denied by user.') unless Permissions.confirm?(desc)
|
|
71
70
|
|
|
72
71
|
super
|
|
73
72
|
end
|
|
@@ -75,11 +74,11 @@ module Legion
|
|
|
75
74
|
private
|
|
76
75
|
|
|
77
76
|
def permission_description(args)
|
|
78
|
-
tier = Permissions.tier_for(self
|
|
77
|
+
tier = Permissions.tier_for(self)
|
|
79
78
|
case tier
|
|
80
79
|
when :write
|
|
81
80
|
path = args[:path] || '(unknown)'
|
|
82
|
-
action =
|
|
81
|
+
action = name.split('::').last.gsub(/([a-z])([A-Z])/, '\1 \2')
|
|
83
82
|
"#{action}: #{path}"
|
|
84
83
|
when :shell
|
|
85
84
|
"Run command: #{args[:command]}"
|
|
@@ -2,52 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
require 'legion/cli/chat_command'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
require 'legion/cli/chat/tools/graph_explore'
|
|
46
|
-
require 'legion/cli/chat/tools/scheduling_status'
|
|
47
|
-
require 'legion/cli/chat/tools/memory_status'
|
|
48
|
-
rescue LoadError => e
|
|
49
|
-
Legion::Logging.debug("ToolRegistry ruby_llm not available, chat tools will not be registered: #{e.message}") if defined?(Legion::Logging)
|
|
50
|
-
end
|
|
5
|
+
require 'legion/cli/chat/tools/read_file'
|
|
6
|
+
require 'legion/cli/chat/tools/write_file'
|
|
7
|
+
require 'legion/cli/chat/tools/edit_file'
|
|
8
|
+
require 'legion/cli/chat/tools/search_files'
|
|
9
|
+
require 'legion/cli/chat/tools/search_content'
|
|
10
|
+
require 'legion/cli/chat/tools/run_command'
|
|
11
|
+
require 'legion/cli/chat/tools/save_memory'
|
|
12
|
+
require 'legion/cli/chat/tools/search_memory'
|
|
13
|
+
require 'legion/cli/chat/tools/web_search'
|
|
14
|
+
require 'legion/cli/chat/tools/spawn_agent'
|
|
15
|
+
require 'legion/cli/chat/tools/search_traces'
|
|
16
|
+
require 'legion/cli/chat/tools/query_knowledge'
|
|
17
|
+
require 'legion/cli/chat/tools/ingest_knowledge'
|
|
18
|
+
require 'legion/cli/chat/tools/consolidate_memory'
|
|
19
|
+
require 'legion/cli/chat/tools/relate_knowledge'
|
|
20
|
+
require 'legion/cli/chat/tools/knowledge_maintenance'
|
|
21
|
+
require 'legion/cli/chat/tools/knowledge_stats'
|
|
22
|
+
require 'legion/cli/chat/tools/summarize_traces'
|
|
23
|
+
require 'legion/cli/chat/tools/list_extensions'
|
|
24
|
+
require 'legion/cli/chat/tools/manage_tasks'
|
|
25
|
+
require 'legion/cli/chat/tools/system_status'
|
|
26
|
+
require 'legion/cli/chat/tools/view_events'
|
|
27
|
+
require 'legion/cli/chat/tools/cost_summary'
|
|
28
|
+
require 'legion/cli/chat/tools/reflect'
|
|
29
|
+
require 'legion/cli/chat/tools/manage_schedules'
|
|
30
|
+
require 'legion/cli/chat/tools/worker_status'
|
|
31
|
+
require 'legion/cli/chat/tools/detect_anomalies'
|
|
32
|
+
require 'legion/cli/chat/tools/view_trends'
|
|
33
|
+
require 'legion/cli/chat/tools/trigger_dream'
|
|
34
|
+
require 'legion/cli/chat/tools/generate_insights'
|
|
35
|
+
require 'legion/cli/chat/tools/budget_status'
|
|
36
|
+
require 'legion/cli/chat/tools/provider_health'
|
|
37
|
+
require 'legion/cli/chat/tools/model_comparison'
|
|
38
|
+
require 'legion/cli/chat/tools/shadow_eval_status'
|
|
39
|
+
require 'legion/cli/chat/tools/entity_extract'
|
|
40
|
+
require 'legion/cli/chat/tools/arbitrage_status'
|
|
41
|
+
require 'legion/cli/chat/tools/escalation_status'
|
|
42
|
+
require 'legion/cli/chat/tools/graph_explore'
|
|
43
|
+
require 'legion/cli/chat/tools/scheduling_status'
|
|
44
|
+
require 'legion/cli/chat/tools/memory_status'
|
|
51
45
|
|
|
52
46
|
require 'legion/cli/chat/permissions'
|
|
53
47
|
|
|
@@ -55,54 +49,50 @@ module Legion
|
|
|
55
49
|
module CLI
|
|
56
50
|
class Chat
|
|
57
51
|
module ToolRegistry
|
|
58
|
-
BUILTIN_TOOLS =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
].freeze
|
|
101
|
-
else
|
|
102
|
-
[].freeze
|
|
103
|
-
end
|
|
52
|
+
BUILTIN_TOOLS = [
|
|
53
|
+
Tools::ReadFile,
|
|
54
|
+
Tools::WriteFile,
|
|
55
|
+
Tools::EditFile,
|
|
56
|
+
Tools::SearchFiles,
|
|
57
|
+
Tools::SearchContent,
|
|
58
|
+
Tools::RunCommand,
|
|
59
|
+
Tools::SaveMemory,
|
|
60
|
+
Tools::SearchMemory,
|
|
61
|
+
Tools::WebSearch,
|
|
62
|
+
Tools::SpawnAgent,
|
|
63
|
+
Tools::SearchTraces,
|
|
64
|
+
Tools::QueryKnowledge,
|
|
65
|
+
Tools::IngestKnowledge,
|
|
66
|
+
Tools::ConsolidateMemory,
|
|
67
|
+
Tools::RelateKnowledge,
|
|
68
|
+
Tools::KnowledgeMaintenance,
|
|
69
|
+
Tools::KnowledgeStats,
|
|
70
|
+
Tools::SummarizeTraces,
|
|
71
|
+
Tools::ListExtensions,
|
|
72
|
+
Tools::ManageTasks,
|
|
73
|
+
Tools::SystemStatus,
|
|
74
|
+
Tools::ViewEvents,
|
|
75
|
+
Tools::CostSummary,
|
|
76
|
+
Tools::Reflect,
|
|
77
|
+
Tools::ManageSchedules,
|
|
78
|
+
Tools::WorkerStatus,
|
|
79
|
+
Tools::DetectAnomalies,
|
|
80
|
+
Tools::ViewTrends,
|
|
81
|
+
Tools::TriggerDream,
|
|
82
|
+
Tools::GenerateInsights,
|
|
83
|
+
Tools::BudgetStatus,
|
|
84
|
+
Tools::ProviderHealth,
|
|
85
|
+
Tools::ModelComparison,
|
|
86
|
+
Tools::ShadowEvalStatus,
|
|
87
|
+
Tools::EntityExtract,
|
|
88
|
+
Tools::ArbitrageStatus,
|
|
89
|
+
Tools::EscalationStatus,
|
|
90
|
+
Tools::GraphExplore,
|
|
91
|
+
Tools::SchedulingStatus,
|
|
92
|
+
Tools::MemoryStatus
|
|
93
|
+
].freeze
|
|
104
94
|
|
|
105
|
-
Permissions.apply!(BUILTIN_TOOLS)
|
|
95
|
+
Permissions.apply!(BUILTIN_TOOLS)
|
|
106
96
|
|
|
107
97
|
def self.builtin_tools
|
|
108
98
|
BUILTIN_TOOLS.dup
|
|
@@ -4,17 +4,20 @@ module Legion
|
|
|
4
4
|
module CLI
|
|
5
5
|
class Chat
|
|
6
6
|
module Tools
|
|
7
|
-
class ArbitrageStatus <
|
|
7
|
+
class ArbitrageStatus < Legion::Tools::Base
|
|
8
|
+
tool_name 'legion.arbitrage_status'
|
|
8
9
|
description 'Show LLM cost arbitrage status: model pricing table, cheapest model per capability tier'
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
input_schema({
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
capability: { type: 'string', description: 'Capability tier to check: basic, moderate, or reasoning (default: show all)' }
|
|
14
|
+
},
|
|
15
|
+
required: []
|
|
16
|
+
})
|
|
14
17
|
|
|
15
18
|
TIERS = %i[basic moderate reasoning].freeze
|
|
16
19
|
|
|
17
|
-
def
|
|
20
|
+
def self.call(capability: nil)
|
|
18
21
|
return 'LLM arbitrage module not available.' unless arbitrage_available?
|
|
19
22
|
|
|
20
23
|
if capability
|
|
@@ -24,13 +27,11 @@ module Legion
|
|
|
24
27
|
end
|
|
25
28
|
end
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def arbitrage_available?
|
|
30
|
+
def self.arbitrage_available?
|
|
30
31
|
defined?(Legion::LLM::Arbitrage)
|
|
31
32
|
end
|
|
32
33
|
|
|
33
|
-
def format_overview
|
|
34
|
+
def self.format_overview
|
|
34
35
|
arb = Legion::LLM::Arbitrage
|
|
35
36
|
lines = ["LLM Cost Arbitrage\n"]
|
|
36
37
|
lines << format(' Enabled: %<v>s', v: arb.enabled? ? 'YES' : 'no')
|
|
@@ -56,7 +57,7 @@ module Legion
|
|
|
56
57
|
lines.join("\n")
|
|
57
58
|
end
|
|
58
59
|
|
|
59
|
-
def format_tier(tier)
|
|
60
|
+
def self.format_tier(tier)
|
|
60
61
|
arb = Legion::LLM::Arbitrage
|
|
61
62
|
return format('Invalid tier: %<t>s. Use: %<valid>s', t: tier, valid: TIERS.join(', ')) unless TIERS.include?(tier)
|
|
62
63
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'ruby_llm'
|
|
4
|
-
|
|
5
3
|
begin
|
|
6
4
|
require 'legion/cli/chat_command'
|
|
7
5
|
rescue LoadError
|
|
@@ -12,15 +10,20 @@ module Legion
|
|
|
12
10
|
module CLI
|
|
13
11
|
class Chat
|
|
14
12
|
module Tools
|
|
15
|
-
class BudgetStatus <
|
|
13
|
+
class BudgetStatus < Legion::Tools::Base
|
|
14
|
+
tool_name 'legion.budget_status'
|
|
16
15
|
description 'Check the current LLM session cost budget status. Shows how much has been spent, ' \
|
|
17
16
|
'remaining budget, and whether the budget guard is enforcing limits. Works locally ' \
|
|
18
17
|
'without needing the Legion daemon. Use this when the user asks about spending or budget.'
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
input_schema({
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
action: { type: 'string', description: 'Action: "status" (default), "summary" (cost breakdown by model)' }
|
|
22
|
+
},
|
|
23
|
+
required: []
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
def self.call(action: 'status')
|
|
24
27
|
return 'Legion::LLM not available.' unless llm_available?
|
|
25
28
|
|
|
26
29
|
case action.to_s
|
|
@@ -32,9 +35,7 @@ module Legion
|
|
|
32
35
|
"Error checking budget: #{e.message}"
|
|
33
36
|
end
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def format_status
|
|
38
|
+
def self.format_status
|
|
38
39
|
guard = budget_guard_status
|
|
39
40
|
tracker = cost_summary
|
|
40
41
|
lines = ["Session Budget Status:\n"]
|
|
@@ -49,7 +50,7 @@ module Legion
|
|
|
49
50
|
lines.join("\n")
|
|
50
51
|
end
|
|
51
52
|
|
|
52
|
-
def format_summary
|
|
53
|
+
def self.format_summary
|
|
53
54
|
tracker = cost_summary
|
|
54
55
|
return 'No LLM requests recorded this session.' if tracker[:total_requests].zero?
|
|
55
56
|
|
|
@@ -63,7 +64,7 @@ module Legion
|
|
|
63
64
|
lines.join("\n")
|
|
64
65
|
end
|
|
65
66
|
|
|
66
|
-
def append_model_breakdown(lines, by_model)
|
|
67
|
+
def self.append_model_breakdown(lines, by_model)
|
|
67
68
|
return unless by_model&.any?
|
|
68
69
|
|
|
69
70
|
lines << "\n By Model:"
|
|
@@ -73,31 +74,31 @@ module Legion
|
|
|
73
74
|
end
|
|
74
75
|
end
|
|
75
76
|
|
|
76
|
-
def budget_guard_status
|
|
77
|
+
def self.budget_guard_status
|
|
77
78
|
return { enforcing: false, budget_usd: 0.0, ratio: 0.0 } unless budget_guard_available?
|
|
78
79
|
|
|
79
80
|
Legion::LLM::Hooks::BudgetGuard.status
|
|
80
81
|
end
|
|
81
82
|
|
|
82
|
-
def cost_summary
|
|
83
|
+
def self.cost_summary
|
|
83
84
|
return empty_summary unless cost_tracker_available?
|
|
84
85
|
|
|
85
86
|
Legion::LLM::CostTracker.summary
|
|
86
87
|
end
|
|
87
88
|
|
|
88
|
-
def budget_guard_available?
|
|
89
|
+
def self.budget_guard_available?
|
|
89
90
|
defined?(Legion::LLM::Hooks::BudgetGuard)
|
|
90
91
|
end
|
|
91
92
|
|
|
92
|
-
def cost_tracker_available?
|
|
93
|
+
def self.cost_tracker_available?
|
|
93
94
|
defined?(Legion::LLM::CostTracker)
|
|
94
95
|
end
|
|
95
96
|
|
|
96
|
-
def llm_available?
|
|
97
|
+
def self.llm_available?
|
|
97
98
|
defined?(Legion::LLM)
|
|
98
99
|
end
|
|
99
100
|
|
|
100
|
-
def empty_summary
|
|
101
|
+
def self.empty_summary
|
|
101
102
|
{ total_cost_usd: 0.0, total_requests: 0, total_input_tokens: 0, total_output_tokens: 0, by_model: {} }
|
|
102
103
|
end
|
|
103
104
|
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'ruby_llm'
|
|
4
|
-
|
|
5
3
|
begin
|
|
6
4
|
require 'legion/cli/chat_command'
|
|
7
5
|
require 'legion/cli/chat/memory_store'
|
|
@@ -13,12 +11,19 @@ module Legion
|
|
|
13
11
|
module CLI
|
|
14
12
|
class Chat
|
|
15
13
|
module Tools
|
|
16
|
-
class ConsolidateMemory <
|
|
14
|
+
class ConsolidateMemory < Legion::Tools::Base
|
|
15
|
+
tool_name 'legion.consolidate_memory'
|
|
17
16
|
description 'Consolidate and organize memory entries by removing duplicates, merging related items, ' \
|
|
18
17
|
'and creating cleaner summaries. Use this when memory has grown cluttered or has redundant entries. ' \
|
|
19
18
|
'Pass scope "project" or "global" to target the right memory file.'
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
input_schema({
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
scope: { type: 'string', description: 'Memory scope: "project" or "global" (default: project)' },
|
|
23
|
+
dry_run: { type: 'string', description: 'Set to "true" to preview without writing (default: false)' }
|
|
24
|
+
},
|
|
25
|
+
required: []
|
|
26
|
+
})
|
|
22
27
|
|
|
23
28
|
CONSOLIDATION_PROMPT = <<~PROMPT
|
|
24
29
|
You are a memory consolidation engine. Given a list of memory entries, produce a cleaned-up version that:
|
|
@@ -34,7 +39,7 @@ module Legion
|
|
|
34
39
|
Do NOT add headers, explanations, or commentary.
|
|
35
40
|
PROMPT
|
|
36
41
|
|
|
37
|
-
def
|
|
42
|
+
def self.call(scope: 'project', dry_run: nil)
|
|
38
43
|
dry_run = dry_run.to_s == 'true'
|
|
39
44
|
scope_sym = scope.to_s == 'global' ? :global : :project
|
|
40
45
|
|
|
@@ -60,9 +65,7 @@ module Legion
|
|
|
60
65
|
"Error consolidating memory: #{e.message}"
|
|
61
66
|
end
|
|
62
67
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def consolidate_entries(entries)
|
|
68
|
+
def self.consolidate_entries(entries)
|
|
66
69
|
return nil unless defined?(Legion::LLM) && Legion::LLM.respond_to?(:chat_direct)
|
|
67
70
|
|
|
68
71
|
numbered = entries.map.with_index(1) { |e, i| "#{i}. #{e}" }.join("\n")
|
|
@@ -72,7 +75,7 @@ module Legion
|
|
|
72
75
|
response.content
|
|
73
76
|
end
|
|
74
77
|
|
|
75
|
-
def parse_consolidated(text)
|
|
78
|
+
def self.parse_consolidated(text)
|
|
76
79
|
text.lines
|
|
77
80
|
.map(&:strip)
|
|
78
81
|
.select { |line| line.start_with?('- ') }
|
|
@@ -80,7 +83,7 @@ module Legion
|
|
|
80
83
|
.reject(&:empty?)
|
|
81
84
|
end
|
|
82
85
|
|
|
83
|
-
def write_consolidated(entries, scope_sym)
|
|
86
|
+
def self.write_consolidated(entries, scope_sym)
|
|
84
87
|
path = scope_sym == :global ? MemoryStore.global_path : MemoryStore.project_path
|
|
85
88
|
header = scope_sym == :global ? "# Global Memory\n" : "# Project Memory\n"
|
|
86
89
|
timestamp = Time.now.strftime('%Y-%m-%d %H:%M')
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'ruby_llm'
|
|
4
3
|
require 'net/http'
|
|
5
4
|
require 'json'
|
|
6
5
|
|
|
@@ -14,20 +13,25 @@ module Legion
|
|
|
14
13
|
module CLI
|
|
15
14
|
class Chat
|
|
16
15
|
module Tools
|
|
17
|
-
class CostSummary <
|
|
16
|
+
class CostSummary < Legion::Tools::Base
|
|
17
|
+
tool_name 'legion.cost_summary'
|
|
18
18
|
description 'Get cost and token usage summary from the running Legion daemon. Shows spending ' \
|
|
19
19
|
'for today, this week, and this month, plus top cost consumers by worker. ' \
|
|
20
20
|
'Use this to monitor LLM spending and identify expensive operations.'
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
input_schema({
|
|
22
|
+
type: 'object',
|
|
23
|
+
properties: {
|
|
24
|
+
action: { type: 'string', description: 'Action: "summary" (default), "top" (top consumers), or "worker" (specific worker)' },
|
|
25
|
+
worker_id: { type: 'string', description: 'Worker ID (required for "worker" action)' },
|
|
26
|
+
limit: { type: 'integer', description: 'Number of top consumers to show (default: 5)' }
|
|
27
|
+
},
|
|
28
|
+
required: []
|
|
29
|
+
})
|
|
26
30
|
|
|
27
31
|
DEFAULT_PORT = 4567
|
|
28
32
|
DEFAULT_HOST = '127.0.0.1'
|
|
29
33
|
|
|
30
|
-
def
|
|
34
|
+
def self.call(action: 'summary', worker_id: nil, limit: 5)
|
|
31
35
|
case action.to_s
|
|
32
36
|
when 'top'
|
|
33
37
|
handle_top(limit.to_i.clamp(1, 20))
|
|
@@ -45,9 +49,7 @@ module Legion
|
|
|
45
49
|
"Error fetching cost data: #{e.message}"
|
|
46
50
|
end
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def handle_summary
|
|
52
|
+
def self.handle_summary
|
|
51
53
|
data = api_get('/api/costs/summary?period=month')
|
|
52
54
|
return "API error: #{data[:error]}" if data[:error]
|
|
53
55
|
|
|
@@ -60,7 +62,7 @@ module Legion
|
|
|
60
62
|
lines.join("\n")
|
|
61
63
|
end
|
|
62
64
|
|
|
63
|
-
def handle_top(limit)
|
|
65
|
+
def self.handle_top(limit)
|
|
64
66
|
data = api_get('/api/workers')
|
|
65
67
|
return "API error: #{data[:error]}" if data[:error]
|
|
66
68
|
|
|
@@ -77,7 +79,7 @@ module Legion
|
|
|
77
79
|
lines.join("\n")
|
|
78
80
|
end
|
|
79
81
|
|
|
80
|
-
def handle_worker(worker_id)
|
|
82
|
+
def self.handle_worker(worker_id)
|
|
81
83
|
data = api_get("/api/workers/#{worker_id}/value")
|
|
82
84
|
return "API error: #{data[:error]}" if data[:error]
|
|
83
85
|
|
|
@@ -91,7 +93,7 @@ module Legion
|
|
|
91
93
|
lines.join("\n")
|
|
92
94
|
end
|
|
93
95
|
|
|
94
|
-
def fetch_worker_cost(worker_id)
|
|
96
|
+
def self.fetch_worker_cost(worker_id)
|
|
95
97
|
data = api_get("/api/workers/#{worker_id}/value")
|
|
96
98
|
data = data[:data] || data
|
|
97
99
|
(data[:total_cost_usd] || 0).to_f
|
|
@@ -99,7 +101,7 @@ module Legion
|
|
|
99
101
|
0.0
|
|
100
102
|
end
|
|
101
103
|
|
|
102
|
-
def api_get(path)
|
|
104
|
+
def self.api_get(path)
|
|
103
105
|
uri = URI("http://#{DEFAULT_HOST}:#{api_port}#{path}")
|
|
104
106
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
105
107
|
http.open_timeout = 2
|
|
@@ -108,7 +110,7 @@ module Legion
|
|
|
108
110
|
::JSON.parse(response.body, symbolize_names: true)
|
|
109
111
|
end
|
|
110
112
|
|
|
111
|
-
def api_port
|
|
113
|
+
def self.api_port
|
|
112
114
|
return DEFAULT_PORT unless defined?(Legion::Settings)
|
|
113
115
|
|
|
114
116
|
Legion::Settings[:api]&.dig(:port) || DEFAULT_PORT
|