aia 1.1.1 → 2.0.0.0.pre.alpha
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/.envrc +5 -1
- data/.loki +231 -0
- data/.quality/flay_baseline.txt +1 -0
- data/.quality/flog_baseline.txt +29 -0
- data/.quality/reek_baseline.txt +80 -0
- data/.rubocop.yml +116 -0
- data/.version +1 -1
- data/CHANGELOG.md +259 -50
- data/IMPLEMENTATION_PLAN.md +506 -0
- data/README.md +266 -238
- data/Rakefile +118 -5
- data/architecture_review.md +314 -0
- data/bin/aia +16 -0
- data/docs/AGENTS.md +40 -0
- data/docs/advanced-prompting.md +67 -3
- data/docs/cli-reference.md +312 -56
- data/docs/configuration.md +130 -19
- data/docs/contributing.md +56 -2
- data/docs/directives-reference.md +593 -78
- data/docs/faq.md +85 -3
- data/docs/guides/available-models.md +1 -1
- data/docs/guides/basic-usage.md +6 -6
- data/docs/guides/chat.md +40 -16
- data/docs/guides/crew.md +239 -0
- data/docs/guides/executable-prompts.md +1 -1
- data/docs/guides/index.md +1 -0
- data/docs/guides/models.md +15 -0
- data/docs/index.md +29 -2
- data/docs/installation.md +44 -17
- data/docs/mcp-integration.md +40 -0
- data/docs/prompt_management.md +85 -86
- data/docs/security.md +47 -0
- data/docs/special_projects_guide.md +386 -0
- data/docs/tools-and-mcp-examples.md +23 -0
- data/docs/workflows-and-pipelines.md +84 -7
- data/examples/.gitignore +1 -0
- data/examples/00_setup_aia.sh +27 -44
- data/examples/11_multi_model.sh +4 -14
- data/examples/12_token_usage.sh +3 -12
- data/examples/18_tools.sh +10 -2
- data/examples/22_chat_mode.sh +0 -10
- data/examples/23_verify.sh +139 -0
- data/examples/24_decompose.sh +139 -0
- data/examples/25_spawn.sh +139 -0
- data/examples/26_debate.sh +97 -0
- data/examples/27_mention_routing.sh +157 -0
- data/examples/28_model_switching.sh +106 -0
- data/examples/29_agent_harness.sh +177 -0
- data/examples/README.md +65 -0
- data/examples/advanced_multi_robot_capabilities_without_examples.md +106 -0
- data/examples/aia_config.yml +1 -1
- data/examples/aia_config_orchestrator.yml +45 -0
- data/examples/common.sh +18 -6
- data/examples/context/tech_stack.md +2 -2
- data/examples/prompts_dir/roles/orchestrator.md +21 -0
- data/examples/requirements/sinatra_taskflow_app.md +139 -0
- data/examples/rules/01_classify_ruby.rb +16 -0
- data/examples/rules/02_prefer_claude_for_code.rb +19 -0
- data/examples/rules/03_gate_prompt_length.rb +19 -0
- data/examples/rules/04_tool_selection.rb +41 -0
- data/examples/rules/README.md +30 -0
- data/examples/run_all.sh +48 -15
- data/examples/tools/word_count_tool.rb +1 -1
- data/lib/AGENTS.md +57 -0
- data/lib/aia/chat_loop.rb +304 -164
- data/lib/aia/config/cli_parser.rb +174 -111
- data/lib/aia/config/defaults.yml +62 -33
- data/lib/aia/config/mcp_parser.rb +39 -46
- data/lib/aia/config/model_spec.rb +34 -2
- data/lib/aia/config/validator.rb +108 -142
- data/lib/aia/config.rb +110 -145
- data/lib/aia/content_extractor.rb +153 -0
- data/lib/aia/cost_calculator.rb +38 -0
- data/lib/aia/crew.rb +164 -0
- data/lib/aia/debate_handler.rb +166 -0
- data/lib/aia/delegate_handler.rb +112 -0
- data/lib/aia/directive.rb +33 -18
- data/lib/aia/directive_processor.rb +16 -7
- data/lib/aia/directives/configuration_directives.rb +160 -20
- data/lib/aia/directives/context_directives.rb +38 -26
- data/lib/aia/directives/execution_directives.rb +136 -4
- data/lib/aia/directives/model_directives.rb +76 -34
- data/lib/aia/directives/trakflow_directives.rb +44 -0
- data/lib/aia/directives/utility_directives.rb +203 -6
- data/lib/aia/directives/web_and_file_directives.rb +96 -60
- data/lib/aia/errors.rb +15 -0
- data/lib/aia/fact_asserter.rb +27 -0
- data/lib/aia/fzf.rb +9 -31
- data/lib/aia/handler_context.rb +17 -0
- data/lib/aia/handler_protocol.rb +19 -0
- data/lib/aia/history_transfer.rb +55 -0
- data/lib/aia/input_collector.rb +3 -3
- data/lib/aia/layered_orchestrator.rb +448 -0
- data/lib/aia/logger.rb +24 -4
- data/lib/aia/mcp_config_normalizer.rb +35 -0
- data/lib/aia/mcp_connection_manager.rb +305 -0
- data/lib/aia/mcp_discovery.rb +44 -0
- data/lib/aia/mcp_grouper.rb +33 -0
- data/lib/aia/mcp_utility.rb +57 -0
- data/lib/aia/mention_router.rb +260 -0
- data/lib/aia/model_alias_registry.rb +97 -0
- data/lib/aia/model_switch_handler.rb +100 -0
- data/lib/aia/network_builder.rb +155 -0
- data/lib/aia/network_memory_manager.rb +55 -0
- data/lib/aia/patches/ruby_llm_streaming_error.rb +43 -0
- data/lib/aia/patches/ruby_llm_tool_error.rb +96 -0
- data/lib/aia/pipeline_orchestrator.rb +262 -0
- data/lib/aia/plugin_loader.rb +170 -0
- data/lib/aia/plugin_monitor.rb +208 -0
- data/lib/aia/prompt_decomposer.rb +157 -0
- data/lib/aia/prompt_handler.rb +19 -39
- data/lib/aia/robot_builder.rb +51 -0
- data/lib/aia/robot_factory.rb +334 -0
- data/lib/aia/robot_namer.rb +116 -0
- data/lib/aia/session.rb +83 -17
- data/lib/aia/session_tracker.rb +209 -0
- data/lib/aia/similarity_scorer.rb +39 -0
- data/lib/aia/skill_utils.rb +105 -1
- data/lib/aia/spawn_handler.rb +129 -0
- data/lib/aia/spawn_spec_parser.rb +65 -0
- data/lib/aia/special_mode_handler.rb +302 -0
- data/lib/aia/startup_coordinator.rb +150 -0
- data/lib/aia/streaming_runner.rb +169 -0
- data/lib/aia/system_prompt_assembler.rb +88 -0
- data/lib/aia/task_coordinator.rb +202 -0
- data/lib/aia/task_decomposer.rb +57 -0
- data/lib/aia/task_executor.rb +51 -0
- data/lib/aia/tfidf_math.rb +27 -0
- data/lib/aia/tool_filter/tfidf.rb +116 -0
- data/lib/aia/tool_filter/wordnet_expander.rb +127 -0
- data/lib/aia/tool_filter.rb +82 -0
- data/lib/aia/tool_filter_registry.rb +30 -0
- data/lib/aia/tool_filter_strategy.rb +143 -0
- data/lib/aia/tool_loader.rb +210 -0
- data/lib/aia/tool_utility.rb +30 -0
- data/lib/aia/tools/delegate_to_foreman_tool.rb +70 -0
- data/lib/aia/tools/recruit_robot_tool.rb +60 -0
- data/lib/aia/tools/reskill_robot_tool.rb +44 -0
- data/lib/aia/tools/task_board_tool.rb +114 -0
- data/lib/aia/trakflow_bridge.rb +173 -0
- data/lib/aia/turn_state.rb +94 -0
- data/lib/aia/ui_presenter.rb +166 -198
- data/lib/aia/utility.rb +134 -87
- data/lib/aia/{history_manager.rb → variable_input_collector.rb} +8 -9
- data/lib/aia/verification_network.rb +58 -0
- data/lib/aia.rb +108 -63
- data/mkdocs.yml +1 -0
- metadata +179 -56
- data/justfile +0 -215
- data/lib/aia/adapter/chat_execution.rb +0 -242
- data/lib/aia/adapter/error_handler.rb +0 -68
- data/lib/aia/adapter/gem_activator.rb +0 -57
- data/lib/aia/adapter/mcp_connector.rb +0 -274
- data/lib/aia/adapter/modality_handlers.rb +0 -167
- data/lib/aia/adapter/model_registry.rb +0 -81
- data/lib/aia/adapter/multi_model_chat.rb +0 -218
- data/lib/aia/adapter/provider_configurator.rb +0 -59
- data/lib/aia/adapter/tool_filter.rb +0 -85
- data/lib/aia/adapter/tool_loader.rb +0 -90
- data/lib/aia/chat_processor_service.rb +0 -178
- data/lib/aia/prompt_pipeline.rb +0 -183
- data/lib/aia/ruby_llm_adapter.rb +0 -95
- data/lib/extensions/openstruct_merge.rb +0 -48
- data/lib/extensions/ruby_llm/.irbrc +0 -56
- data/lib/extensions/ruby_llm/modalities.rb +0 -36
- data/lib/extensions/ruby_llm/provider_fix.rb +0 -79
- data/lib/refinements/string.rb +0 -16
- data/main.just +0 -76
|
@@ -4,14 +4,14 @@ module AIA
|
|
|
4
4
|
class ModelDirectives < Directive
|
|
5
5
|
desc "List all available AI models"
|
|
6
6
|
def available_models(args = nil, context_manager = nil)
|
|
7
|
-
positive_terms, negative_terms = parse_search_terms(Array(args))
|
|
8
|
-
|
|
9
7
|
current_models = AIA.config.models
|
|
10
8
|
|
|
11
9
|
model_names = current_models.map do |m|
|
|
12
10
|
m.respond_to?(:name) ? m.name : m.to_s
|
|
13
11
|
end
|
|
14
12
|
|
|
13
|
+
positive_terms, negative_terms = parse_search_terms(Array(args))
|
|
14
|
+
|
|
15
15
|
using_local_provider = model_names.any? { |m| m.start_with?('ollama/', 'lms/') }
|
|
16
16
|
|
|
17
17
|
if using_local_provider
|
|
@@ -22,13 +22,14 @@ module AIA
|
|
|
22
22
|
|
|
23
23
|
""
|
|
24
24
|
end
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
alias am available_models
|
|
26
|
+
alias available available_models
|
|
27
|
+
alias models available_models
|
|
28
|
+
alias all_models available_models
|
|
29
|
+
alias llms available_models
|
|
30
30
|
|
|
31
31
|
desc "Compare responses from multiple models"
|
|
32
|
+
# rubocop:disable Metrics/MethodLength
|
|
32
33
|
def compare(args, context_manager = nil)
|
|
33
34
|
return 'Error: No prompt provided for comparison' if args.empty?
|
|
34
35
|
|
|
@@ -73,19 +74,22 @@ module AIA
|
|
|
73
74
|
end
|
|
74
75
|
end
|
|
75
76
|
|
|
76
|
-
puts
|
|
77
|
+
puts "\n" + ('=' * 80)
|
|
77
78
|
puts "\nComparison complete!"
|
|
78
79
|
|
|
79
80
|
''
|
|
80
81
|
end
|
|
81
|
-
|
|
82
|
+
# rubocop:enable Metrics/MethodLength
|
|
83
|
+
alias cmp compare
|
|
82
84
|
|
|
83
85
|
# --- helpers (no desc → not registered) ---
|
|
84
86
|
|
|
85
|
-
def show_local_models(current_models, positive_terms, negative_terms)
|
|
87
|
+
def show_local_models(current_models, positive_terms = nil, negative_terms = nil)
|
|
86
88
|
require 'net/http'
|
|
87
89
|
require 'json'
|
|
88
90
|
|
|
91
|
+
positive_terms, negative_terms = normalized_model_search_terms(positive_terms, negative_terms)
|
|
92
|
+
|
|
89
93
|
puts "\nLocal LLM Models:"
|
|
90
94
|
puts
|
|
91
95
|
|
|
@@ -101,10 +105,16 @@ module AIA
|
|
|
101
105
|
end
|
|
102
106
|
end
|
|
103
107
|
|
|
104
|
-
|
|
108
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
109
|
+
def show_ollama_models(api_base, positive_terms = nil, negative_terms = nil)
|
|
110
|
+
positive_terms, negative_terms = normalized_model_search_terms(positive_terms, negative_terms)
|
|
111
|
+
|
|
105
112
|
begin
|
|
106
113
|
uri = URI("#{api_base}/api/tags")
|
|
107
|
-
|
|
114
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
115
|
+
http.open_timeout = 5
|
|
116
|
+
http.read_timeout = 5
|
|
117
|
+
response = http.request(Net::HTTP::Get.new(uri))
|
|
108
118
|
|
|
109
119
|
unless response.is_a?(Net::HTTPSuccess)
|
|
110
120
|
puts "❌ Cannot connect to Ollama at #{api_base}"
|
|
@@ -129,11 +139,11 @@ module AIA
|
|
|
129
139
|
modified = model['modified_at'] ? Time.parse(model['modified_at']).strftime('%Y-%m-%d') : 'unknown'
|
|
130
140
|
|
|
131
141
|
entry = "- ollama/#{name} (size: #{size}, modified: #{modified})"
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
show_it = positive_terms.empty? || positive_terms.any? { |q| entry_down.include?(q) }
|
|
135
|
-
show_it &&= negative_terms.none? { |q| entry_down.include?(q) }
|
|
142
|
+
entry_lc = entry.downcase
|
|
136
143
|
|
|
144
|
+
# entry_lc is a String; Array#intersect? would raise TypeError
|
|
145
|
+
show_it = positive_terms.empty? || positive_terms.any? { |q| entry_lc.include?(q) }
|
|
146
|
+
show_it &&= negative_terms.none? { |q| entry_lc.include?(q) }
|
|
137
147
|
if show_it
|
|
138
148
|
puts entry
|
|
139
149
|
counter += 1
|
|
@@ -147,11 +157,18 @@ module AIA
|
|
|
147
157
|
puts "❌ Error fetching Ollama models: #{e.message}"
|
|
148
158
|
end
|
|
149
159
|
end
|
|
160
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
161
|
+
|
|
162
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
163
|
+
def show_lms_models(api_base, positive_terms = nil, negative_terms = nil)
|
|
164
|
+
positive_terms, negative_terms = normalized_model_search_terms(positive_terms, negative_terms)
|
|
150
165
|
|
|
151
|
-
def show_lms_models(api_base, positive_terms, negative_terms)
|
|
152
166
|
begin
|
|
153
167
|
uri = URI("#{api_base.gsub(%r{/v1/?$}, '')}/v1/models")
|
|
154
|
-
|
|
168
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
169
|
+
http.open_timeout = 5
|
|
170
|
+
http.read_timeout = 5
|
|
171
|
+
response = http.request(Net::HTTP::Get.new(uri))
|
|
155
172
|
|
|
156
173
|
unless response.is_a?(Net::HTTPSuccess)
|
|
157
174
|
puts "❌ Cannot connect to LM Studio at #{api_base}"
|
|
@@ -173,11 +190,11 @@ module AIA
|
|
|
173
190
|
models.each do |model|
|
|
174
191
|
name = model['id']
|
|
175
192
|
entry = "- lms/#{name}"
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
show_it = positive_terms.empty? || positive_terms.any? { |q| entry_down.include?(q) }
|
|
179
|
-
show_it &&= negative_terms.none? { |q| entry_down.include?(q) }
|
|
193
|
+
entry_lc = entry.downcase
|
|
180
194
|
|
|
195
|
+
# entry_lc is a String; Array#intersect? would raise TypeError
|
|
196
|
+
show_it = positive_terms.empty? || positive_terms.any? { |q| entry_lc.include?(q) }
|
|
197
|
+
show_it &&= negative_terms.none? { |q| entry_lc.include?(q) }
|
|
181
198
|
if show_it
|
|
182
199
|
puts entry
|
|
183
200
|
counter += 1
|
|
@@ -191,28 +208,38 @@ module AIA
|
|
|
191
208
|
puts "❌ Error fetching LM Studio models: #{e.message}"
|
|
192
209
|
end
|
|
193
210
|
end
|
|
211
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
194
212
|
|
|
195
213
|
def format_bytes(bytes)
|
|
196
|
-
units = [
|
|
214
|
+
units = %w[B KB MB GB TB]
|
|
197
215
|
return "0 B" if bytes.zero?
|
|
198
216
|
|
|
199
217
|
exp = (Math.log(bytes) / Math.log(1024)).to_i
|
|
200
218
|
exp = [exp, units.length - 1].min
|
|
201
219
|
|
|
202
|
-
"%.1f %s" % [bytes.to_f / (1024
|
|
220
|
+
"%.1f %s" % [bytes.to_f / (1024**exp), units[exp]]
|
|
203
221
|
end
|
|
204
222
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
223
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
224
|
+
def show_rubyllm_models(positive_terms = nil, negative_terms = nil)
|
|
225
|
+
positive_terms, negative_terms = normalized_model_search_terms(positive_terms, negative_terms)
|
|
226
|
+
|
|
227
|
+
# expand comma-separated terms passed as a single token
|
|
228
|
+
if positive_terms.size == 1
|
|
229
|
+
positive_terms = positive_terms.first.split(',')
|
|
230
|
+
end
|
|
208
231
|
|
|
209
232
|
header = "\nAvailable LLMs"
|
|
210
|
-
header += " for #{positive_terms.join(' and ')}"
|
|
211
|
-
header += " excluding: #{negative_terms.join(', ')}"
|
|
233
|
+
header += " for #{positive_terms.join(' and ')}" if positive_terms.any?
|
|
234
|
+
header += " (excluding: #{negative_terms.join(', ')})" if negative_terms.any?
|
|
212
235
|
|
|
213
236
|
puts header + ':'
|
|
214
237
|
puts
|
|
215
238
|
|
|
239
|
+
# modality terms (e.g. "text_to_text") trigger capability checks; the rest
|
|
240
|
+
# are plain substring filters applied to the formatted entry string
|
|
241
|
+
q1, q2 = positive_terms.partition { |q| q.include?('_to_') }
|
|
242
|
+
|
|
216
243
|
counter = 0
|
|
217
244
|
|
|
218
245
|
RubyLLM.models.all.each do |llm|
|
|
@@ -223,12 +250,17 @@ module AIA
|
|
|
223
250
|
mode = "#{inputs} to #{outputs}"
|
|
224
251
|
in_1m = llm.pricing.text_tokens.standard.to_h[:input_per_million]
|
|
225
252
|
entry = "- #{llm.id} (#{llm.provider}) in: $#{in_1m} cw: #{cw} mode: #{mode} caps: #{caps}"
|
|
226
|
-
|
|
253
|
+
|
|
254
|
+
if positive_terms.empty? && negative_terms.empty?
|
|
255
|
+
counter += 1
|
|
256
|
+
puts entry
|
|
257
|
+
next
|
|
258
|
+
end
|
|
227
259
|
|
|
228
260
|
show_it = true
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
negative_terms.each { |q| show_it &&= !
|
|
261
|
+
q1.each { |q| show_it &&= llm.modalities.send("#{q}?") }
|
|
262
|
+
q2.each { |q| show_it &&= entry.include?(q) }
|
|
263
|
+
negative_terms.each { |q| show_it &&= !entry.downcase.include?(q) }
|
|
232
264
|
|
|
233
265
|
if show_it
|
|
234
266
|
counter += 1
|
|
@@ -236,9 +268,19 @@ module AIA
|
|
|
236
268
|
end
|
|
237
269
|
end
|
|
238
270
|
|
|
239
|
-
puts if counter
|
|
271
|
+
puts if counter.positive?
|
|
240
272
|
puts "#{counter} LLMs matching your query"
|
|
241
273
|
puts
|
|
242
274
|
end
|
|
275
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
276
|
+
|
|
277
|
+
def normalized_model_search_terms(positive_terms, negative_terms = nil)
|
|
278
|
+
return parse_search_terms(Array(positive_terms)) if negative_terms.nil?
|
|
279
|
+
|
|
280
|
+
[
|
|
281
|
+
Array(positive_terms).compact.map { |term| term.to_s.downcase },
|
|
282
|
+
Array(negative_terms).compact.map { |term| term.to_s.downcase }
|
|
283
|
+
]
|
|
284
|
+
end
|
|
243
285
|
end
|
|
244
286
|
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# lib/aia/directives/trakflow_directives.rb
|
|
4
|
+
#
|
|
5
|
+
# Chat directives for interacting with TrakFlow task tracking.
|
|
6
|
+
|
|
7
|
+
module AIA
|
|
8
|
+
class TrakFlowDirectives < Directive
|
|
9
|
+
desc "Show TrakFlow ready tasks and project summary"
|
|
10
|
+
def tasks(args, context_manager = nil)
|
|
11
|
+
bridge = TrakFlowBridge.new
|
|
12
|
+
return "TrakFlow not available. Run 'tf init' to initialize a project." unless bridge.available?
|
|
13
|
+
|
|
14
|
+
if args.first == "summary"
|
|
15
|
+
bridge.project_summary || "No summary available."
|
|
16
|
+
else
|
|
17
|
+
bridge.check_ready_tasks || "No ready tasks found."
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
alias tf tasks
|
|
21
|
+
|
|
22
|
+
desc "Create a TrakFlow plan from a description"
|
|
23
|
+
def plan(args, context_manager = nil)
|
|
24
|
+
bridge = TrakFlowBridge.new
|
|
25
|
+
return "TrakFlow not available. Run 'tf init' to initialize a project." unless bridge.available?
|
|
26
|
+
|
|
27
|
+
description = args.join(' ')
|
|
28
|
+
return "Usage: /plan <description>" if description.empty?
|
|
29
|
+
|
|
30
|
+
bridge.create_task(description) || "Failed to create plan."
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
desc "Create a TrakFlow task"
|
|
34
|
+
def task(args, context_manager = nil)
|
|
35
|
+
bridge = TrakFlowBridge.new
|
|
36
|
+
return "TrakFlow not available. Run 'tf init' to initialize a project." unless bridge.available?
|
|
37
|
+
|
|
38
|
+
title = args.join(' ')
|
|
39
|
+
return "Usage: /task <title>" if title.empty?
|
|
40
|
+
|
|
41
|
+
bridge.create_task(title) || "Failed to create task."
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -5,14 +5,16 @@ require 'word_wrapper'
|
|
|
5
5
|
|
|
6
6
|
module AIA
|
|
7
7
|
class UtilityDirectives < Directive
|
|
8
|
-
desc "List available tools (optional filter by name substring)"
|
|
8
|
+
desc "List available tools (optional filter by name or description substring)"
|
|
9
|
+
# rubocop:disable Metrics/AbcSize
|
|
9
10
|
def tools(args = [], context_manager = nil)
|
|
10
11
|
indent = 4
|
|
11
12
|
spaces = " " * indent
|
|
12
13
|
width = TTY::Screen.width - indent - 2
|
|
13
14
|
filter = args.first&.downcase
|
|
14
15
|
|
|
15
|
-
loaded_tools = AIA.config.loaded_tools
|
|
16
|
+
loaded_tools = Array(AIA.config.loaded_tools) + all_mcp_tools
|
|
17
|
+
|
|
16
18
|
if loaded_tools.empty?
|
|
17
19
|
puts "No tools are available"
|
|
18
20
|
else
|
|
@@ -21,7 +23,8 @@ module AIA
|
|
|
21
23
|
if filter
|
|
22
24
|
tools_to_display = tools_to_display.select do |tool|
|
|
23
25
|
name = tool.respond_to?(:name) ? tool.name : tool.class.name
|
|
24
|
-
|
|
26
|
+
desc = tool.respond_to?(:description) ? tool.description.to_s : ""
|
|
27
|
+
"#{name} #{desc}".downcase.include?(filter)
|
|
25
28
|
end
|
|
26
29
|
end
|
|
27
30
|
|
|
@@ -45,10 +48,95 @@ module AIA
|
|
|
45
48
|
|
|
46
49
|
''
|
|
47
50
|
end
|
|
51
|
+
# rubocop:enable Metrics/AbcSize
|
|
52
|
+
|
|
53
|
+
desc "List loaded plugin basenames"
|
|
54
|
+
def plugins(args = [], context_manager = nil)
|
|
55
|
+
loaded_plugins = Array(AIA.config&.loaded_plugins)
|
|
56
|
+
|
|
57
|
+
if loaded_plugins.empty?
|
|
58
|
+
puts "No plugins are loaded"
|
|
59
|
+
else
|
|
60
|
+
puts
|
|
61
|
+
puts "Loaded Plugins"
|
|
62
|
+
puts "=============="
|
|
63
|
+
loaded_plugins.each { |name| puts name }
|
|
64
|
+
end
|
|
65
|
+
puts
|
|
66
|
+
|
|
67
|
+
''
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
desc "Show MCP server connection status and available tools"
|
|
71
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
72
|
+
def mcp(args = [], context_manager = nil)
|
|
73
|
+
connected = AIA.config&.connected_mcp_servers || []
|
|
74
|
+
failed = AIA.config&.failed_mcp_servers || []
|
|
75
|
+
defined_count = AIA::Utility.effective_mcp_server_names.size
|
|
76
|
+
|
|
77
|
+
puts
|
|
78
|
+
puts "MCP Server Status"
|
|
79
|
+
puts "================="
|
|
80
|
+
puts "Defined: #{defined_count} Connected: #{connected.size} Failed: #{failed.size}"
|
|
81
|
+
puts
|
|
82
|
+
|
|
83
|
+
if connected.any?
|
|
84
|
+
mcp_tools = all_mcp_tools
|
|
85
|
+
|
|
86
|
+
# Group tools by their MCP server name
|
|
87
|
+
tools_by_server = {}
|
|
88
|
+
connected.each { |name| tools_by_server[name] = [] }
|
|
89
|
+
|
|
90
|
+
mcp_tools.each do |tool|
|
|
91
|
+
server_name = tool.respond_to?(:mcp) ? tool.mcp : nil
|
|
92
|
+
if server_name && tools_by_server.key?(server_name)
|
|
93
|
+
tools_by_server[server_name] << tool
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
puts "Connected Servers:"
|
|
98
|
+
connected.each do |name|
|
|
99
|
+
tools = tools_by_server[name] || []
|
|
100
|
+
puts " #{name} (#{tools.size} tools)"
|
|
101
|
+
tools.each do |tool|
|
|
102
|
+
tool_name = tool.respond_to?(:name) ? tool.name : tool.class.name
|
|
103
|
+
puts " - #{tool_name}"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
puts
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if failed.any?
|
|
110
|
+
puts "Failed Servers:"
|
|
111
|
+
failed.each do |f|
|
|
112
|
+
name = f[:name] || f['name']
|
|
113
|
+
error = f[:error] || f['error']
|
|
114
|
+
puts " #{name}: #{error}"
|
|
115
|
+
end
|
|
116
|
+
puts
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
''
|
|
120
|
+
end
|
|
121
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
122
|
+
|
|
123
|
+
desc "Show active robot configuration"
|
|
124
|
+
def robots(args = [], context_manager = nil)
|
|
125
|
+
client = AIA.client
|
|
126
|
+
unless client
|
|
127
|
+
puts "No active robots"
|
|
128
|
+
return ''
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
puts
|
|
132
|
+
if client.network?
|
|
133
|
+
show_network(client)
|
|
134
|
+
else
|
|
135
|
+
show_single_robot(client)
|
|
136
|
+
end
|
|
137
|
+
puts
|
|
48
138
|
|
|
49
|
-
|
|
50
|
-
def terse(args, context_manager = nil)
|
|
51
|
-
"" # DEPRECATED: terse mode has been removed
|
|
139
|
+
''
|
|
52
140
|
end
|
|
53
141
|
|
|
54
142
|
desc "Display ASCII robot art"
|
|
@@ -61,5 +149,114 @@ module AIA
|
|
|
61
149
|
def help(args = nil, context_manager = nil)
|
|
62
150
|
AIA::Directive.help
|
|
63
151
|
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def show_network(network)
|
|
156
|
+
robot_count = network.robot_count
|
|
157
|
+
mode = if AIA.config.flags.consensus
|
|
158
|
+
"Consensus"
|
|
159
|
+
elsif AIA.config.pipeline.length > 1
|
|
160
|
+
"Pipeline"
|
|
161
|
+
elsif AIA.config.models.length > 1
|
|
162
|
+
"Parallel"
|
|
163
|
+
else
|
|
164
|
+
"Crew" # single-model session wrapped as a one-member crew
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
header = "Active Robots"
|
|
168
|
+
puts header
|
|
169
|
+
puts "=" * header.length
|
|
170
|
+
noun = robot_count == 1 ? "robot" : "robots"
|
|
171
|
+
puts "Mode: #{mode} (#{robot_count} #{noun})"
|
|
172
|
+
|
|
173
|
+
network.crew.each do |bot|
|
|
174
|
+
puts
|
|
175
|
+
show_robot_detail(bot)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def show_single_robot(bot)
|
|
180
|
+
header = "Active Robot"
|
|
181
|
+
puts header
|
|
182
|
+
puts "=" * header.length
|
|
183
|
+
puts "Mode: Single"
|
|
184
|
+
puts
|
|
185
|
+
show_robot_detail(bot)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def show_robot_detail(bot)
|
|
189
|
+
puts " #{bot.name}"
|
|
190
|
+
|
|
191
|
+
model_name = bot.model || 'unknown'
|
|
192
|
+
provider = bot.respond_to?(:provider) ? bot.provider : nil
|
|
193
|
+
provider ||= chat_provider(bot)
|
|
194
|
+
model_line = provider ? "#{model_name} (#{provider})" : model_name
|
|
195
|
+
puts " Model: #{model_line}"
|
|
196
|
+
puts " Wage: #{wage_for(model_name)}"
|
|
197
|
+
|
|
198
|
+
local_count = Array(bot.local_tools).size
|
|
199
|
+
mcp_count = Array(bot.mcp_tools).size
|
|
200
|
+
total = local_count + mcp_count
|
|
201
|
+
tool_parts = []
|
|
202
|
+
tool_parts << "#{local_count} local" if local_count.positive?
|
|
203
|
+
tool_parts << "#{mcp_count} mcp" if mcp_count.positive?
|
|
204
|
+
puts " Tools: #{total} (#{tool_parts.join(', ')})" if total.positive?
|
|
205
|
+
puts " Tools: none" if total.zero?
|
|
206
|
+
|
|
207
|
+
role = role_for(bot)
|
|
208
|
+
puts " Role: #{role}"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Cost per 1K tokens (price_per_million * 1000 / 1_000_000)
|
|
212
|
+
def wage_for(model_name)
|
|
213
|
+
model_info = RubyLLM::Models.find(model_name)
|
|
214
|
+
input = model_info&.input_price_per_million
|
|
215
|
+
output = model_info&.output_price_per_million
|
|
216
|
+
return 'N/A' unless input && output
|
|
217
|
+
|
|
218
|
+
in_per_k = input * 1000.0 / 1_000_000
|
|
219
|
+
out_per_k = output * 1000.0 / 1_000_000
|
|
220
|
+
"$#{'%.4f' % in_per_k} in / $#{'%.4f' % out_per_k} out per 1K tokens"
|
|
221
|
+
rescue StandardError
|
|
222
|
+
'N/A'
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def chat_provider(bot)
|
|
226
|
+
bot.chat_provider
|
|
227
|
+
rescue StandardError
|
|
228
|
+
nil
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def role_for(bot)
|
|
232
|
+
spec = AIA.config.models.find { |s| s.name == bot.model }
|
|
233
|
+
if spec&.role?
|
|
234
|
+
spec.role
|
|
235
|
+
else
|
|
236
|
+
"(default)"
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Retrieve MCP tools from robot or first robot in a Network.
|
|
241
|
+
# Falls back to RubyLLM::MCP.clients for tools registered by shared_tools
|
|
242
|
+
# or other gems that use RubyLLM::MCP.add_client directly.
|
|
243
|
+
def all_mcp_tools
|
|
244
|
+
robot = AIA.client
|
|
245
|
+
if robot
|
|
246
|
+
if robot.respond_to?(:mcp_tools) && robot.mcp_tools&.any?
|
|
247
|
+
return Array(robot.mcp_tools)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
first_robot = robot.chief
|
|
251
|
+
if first_robot.respond_to?(:mcp_tools) && first_robot.mcp_tools&.any?
|
|
252
|
+
return Array(first_robot.mcp_tools)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Fall back to RubyLLM::MCP clients (shared_tools and other gems
|
|
257
|
+
# that register MCP servers via RubyLLM::MCP.add_client)
|
|
258
|
+
return [] unless defined?(RubyLLM::MCP)
|
|
259
|
+
RubyLLM::MCP.clients.values.flat_map(&:tools)
|
|
260
|
+
end
|
|
64
261
|
end
|
|
65
262
|
end
|