aia 1.1.1 → 2.0.0.0.pre.beta2

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.
Files changed (175) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +9 -1
  3. data/.loki +11 -0
  4. data/.reek.yml +160 -0
  5. data/.rubocop.yml +116 -0
  6. data/.rubocop_strict.yml +15 -0
  7. data/.version +1 -1
  8. data/CHANGELOG.md +269 -55
  9. data/IMPLEMENTATION_PLAN.md +506 -0
  10. data/README.md +267 -239
  11. data/Rakefile +5 -5
  12. data/_typos.toml +10 -0
  13. data/aia.gemspec +92 -0
  14. data/architecture_review.md +314 -0
  15. data/bin/aia +16 -0
  16. data/config/aia.yml +13 -0
  17. data/docs/AGENTS.md +40 -0
  18. data/docs/advanced-prompting.md +67 -3
  19. data/docs/cli-reference.md +312 -56
  20. data/docs/configuration.md +130 -19
  21. data/docs/contributing.md +56 -2
  22. data/docs/directives-reference.md +593 -78
  23. data/docs/faq.md +85 -3
  24. data/docs/guides/available-models.md +1 -1
  25. data/docs/guides/basic-usage.md +6 -6
  26. data/docs/guides/chat.md +40 -16
  27. data/docs/guides/crew.md +239 -0
  28. data/docs/guides/executable-prompts.md +1 -1
  29. data/docs/guides/index.md +1 -0
  30. data/docs/guides/models.md +15 -0
  31. data/docs/index.md +29 -2
  32. data/docs/installation.md +44 -17
  33. data/docs/mcp-integration.md +40 -0
  34. data/docs/prompt_management.md +85 -86
  35. data/docs/security.md +47 -0
  36. data/docs/special_projects_guide.md +386 -0
  37. data/docs/tools-and-mcp-examples.md +23 -0
  38. data/docs/workflows-and-pipelines.md +84 -7
  39. data/examples/.gitignore +1 -0
  40. data/examples/00_setup_aia.sh +27 -44
  41. data/examples/11_multi_model.sh +4 -14
  42. data/examples/12_token_usage.sh +3 -12
  43. data/examples/18_tools.sh +10 -2
  44. data/examples/22_chat_mode.sh +0 -10
  45. data/examples/23_verify.sh +139 -0
  46. data/examples/24_decompose.sh +139 -0
  47. data/examples/25_spawn.sh +139 -0
  48. data/examples/26_debate.sh +97 -0
  49. data/examples/27_mention_routing.sh +157 -0
  50. data/examples/28_model_switching.sh +106 -0
  51. data/examples/29_agent_harness.sh +177 -0
  52. data/examples/README.md +65 -0
  53. data/examples/advanced_multi_robot_capabilities_without_examples.md +106 -0
  54. data/examples/aia_config.yml +1 -1
  55. data/examples/aia_config_orchestrator.yml +45 -0
  56. data/examples/common.sh +18 -6
  57. data/examples/context/tech_stack.md +2 -2
  58. data/examples/prompts_dir/roles/orchestrator.md +21 -0
  59. data/examples/requirements/sinatra_taskflow_app.md +139 -0
  60. data/examples/rules/01_classify_ruby.rb +16 -0
  61. data/examples/rules/02_prefer_claude_for_code.rb +19 -0
  62. data/examples/rules/03_gate_prompt_length.rb +19 -0
  63. data/examples/rules/04_tool_selection.rb +41 -0
  64. data/examples/rules/README.md +30 -0
  65. data/examples/run_all.sh +48 -15
  66. data/examples/tools/word_count_tool.rb +1 -1
  67. data/lib/AGENTS.md +57 -0
  68. data/lib/aia/chat_loop.rb +263 -167
  69. data/lib/aia/config/cli_parser.rb +217 -145
  70. data/lib/aia/config/defaults.yml +62 -33
  71. data/lib/aia/config/mcp_parser.rb +52 -51
  72. data/lib/aia/config/model_spec.rb +34 -2
  73. data/lib/aia/config/validator.rb +171 -216
  74. data/lib/aia/config.rb +111 -145
  75. data/lib/aia/content_extractor.rb +155 -0
  76. data/lib/aia/cost_calculator.rb +39 -0
  77. data/lib/aia/crew.rb +164 -0
  78. data/lib/aia/debate_handler.rb +174 -0
  79. data/lib/aia/delegate_handler.rb +116 -0
  80. data/lib/aia/directive.rb +43 -26
  81. data/lib/aia/directive_processor.rb +16 -7
  82. data/lib/aia/directives/configuration_directives.rb +214 -60
  83. data/lib/aia/directives/context_directives.rb +67 -52
  84. data/lib/aia/directives/execution_directives.rb +141 -4
  85. data/lib/aia/directives/model_directives.rb +163 -141
  86. data/lib/aia/directives/trakflow_directives.rb +62 -0
  87. data/lib/aia/directives/utility_directives.rb +227 -30
  88. data/lib/aia/directives/web_and_file_directives.rb +126 -77
  89. data/lib/aia/errors.rb +15 -0
  90. data/lib/aia/fact_asserter.rb +27 -0
  91. data/lib/aia/fzf.rb +9 -31
  92. data/lib/aia/handler_context.rb +17 -0
  93. data/lib/aia/handler_protocol.rb +19 -0
  94. data/lib/aia/history_transfer.rb +55 -0
  95. data/lib/aia/input_collector.rb +3 -3
  96. data/lib/aia/layered_orchestrator.rb +471 -0
  97. data/lib/aia/logger.rb +45 -25
  98. data/lib/aia/mcp_config_normalizer.rb +35 -0
  99. data/lib/aia/mcp_connection_manager.rb +315 -0
  100. data/lib/aia/mcp_discovery.rb +44 -0
  101. data/lib/aia/mcp_grouper.rb +33 -0
  102. data/lib/aia/mcp_server_config.rb +30 -0
  103. data/lib/aia/mcp_utility.rb +60 -0
  104. data/lib/aia/mention_router.rb +217 -0
  105. data/lib/aia/model_alias_registry.rb +97 -0
  106. data/lib/aia/model_switch_handler.rb +100 -0
  107. data/lib/aia/network_builder.rb +160 -0
  108. data/lib/aia/network_memory_manager.rb +55 -0
  109. data/lib/aia/patches/ruby_llm_streaming_error.rb +43 -0
  110. data/lib/aia/patches/ruby_llm_tool_error.rb +96 -0
  111. data/lib/aia/pipeline_orchestrator.rb +272 -0
  112. data/lib/aia/plugin_loader.rb +170 -0
  113. data/lib/aia/plugin_monitor.rb +211 -0
  114. data/lib/aia/prompt_decomposer.rb +159 -0
  115. data/lib/aia/prompt_handler.rb +56 -82
  116. data/lib/aia/robot_builder.rb +51 -0
  117. data/lib/aia/robot_factory.rb +338 -0
  118. data/lib/aia/robot_namer.rb +110 -0
  119. data/lib/aia/session.rb +87 -17
  120. data/lib/aia/session_tracker.rb +207 -0
  121. data/lib/aia/similarity_scorer.rb +41 -0
  122. data/lib/aia/skill_utils.rb +105 -1
  123. data/lib/aia/spawn_handler.rb +129 -0
  124. data/lib/aia/spawn_spec_parser.rb +65 -0
  125. data/lib/aia/special_mode_handler.rb +322 -0
  126. data/lib/aia/speech.rb +67 -0
  127. data/lib/aia/startup_coordinator.rb +151 -0
  128. data/lib/aia/streaming_runner.rb +172 -0
  129. data/lib/aia/system_prompt_assembler.rb +92 -0
  130. data/lib/aia/task_coordinator.rb +207 -0
  131. data/lib/aia/task_decomposer.rb +57 -0
  132. data/lib/aia/task_executor.rb +51 -0
  133. data/lib/aia/tfidf_math.rb +27 -0
  134. data/lib/aia/timing.rb +15 -0
  135. data/lib/aia/tool_filter/tfidf.rb +116 -0
  136. data/lib/aia/tool_filter/wordnet_expander.rb +127 -0
  137. data/lib/aia/tool_filter.rb +83 -0
  138. data/lib/aia/tool_filter_registry.rb +30 -0
  139. data/lib/aia/tool_filter_strategy.rb +146 -0
  140. data/lib/aia/tool_introspection.rb +17 -0
  141. data/lib/aia/tool_loader.rb +216 -0
  142. data/lib/aia/tool_utility.rb +30 -0
  143. data/lib/aia/tools/delegate_to_foreman_tool.rb +70 -0
  144. data/lib/aia/tools/recruit_robot_tool.rb +60 -0
  145. data/lib/aia/tools/reskill_robot_tool.rb +44 -0
  146. data/lib/aia/tools/task_board_tool.rb +115 -0
  147. data/lib/aia/trakflow_bridge.rb +175 -0
  148. data/lib/aia/turn_state.rb +95 -0
  149. data/lib/aia/ui_presenter.rb +182 -206
  150. data/lib/aia/utility.rb +136 -87
  151. data/lib/aia/{history_manager.rb → variable_input_collector.rb} +9 -9
  152. data/lib/aia/verification_network.rb +57 -0
  153. data/lib/aia.rb +124 -63
  154. data/mkdocs.yml +1 -0
  155. metadata +187 -58
  156. data/justfile +0 -215
  157. data/lib/aia/adapter/chat_execution.rb +0 -242
  158. data/lib/aia/adapter/error_handler.rb +0 -68
  159. data/lib/aia/adapter/gem_activator.rb +0 -57
  160. data/lib/aia/adapter/mcp_connector.rb +0 -274
  161. data/lib/aia/adapter/modality_handlers.rb +0 -167
  162. data/lib/aia/adapter/model_registry.rb +0 -81
  163. data/lib/aia/adapter/multi_model_chat.rb +0 -218
  164. data/lib/aia/adapter/provider_configurator.rb +0 -59
  165. data/lib/aia/adapter/tool_filter.rb +0 -85
  166. data/lib/aia/adapter/tool_loader.rb +0 -90
  167. data/lib/aia/chat_processor_service.rb +0 -178
  168. data/lib/aia/prompt_pipeline.rb +0 -183
  169. data/lib/aia/ruby_llm_adapter.rb +0 -95
  170. data/lib/extensions/openstruct_merge.rb +0 -48
  171. data/lib/extensions/ruby_llm/.irbrc +0 -56
  172. data/lib/extensions/ruby_llm/modalities.rb +0 -36
  173. data/lib/extensions/ruby_llm/provider_fix.rb +0 -79
  174. data/lib/refinements/string.rb +0 -16
  175. data/main.just +0 -76
@@ -1,4 +1,4 @@
1
- # AIA Configuration Schema and Defaults
1
+ # AIA v2 Configuration Schema and Defaults
2
2
  #
3
3
  # This file is the SINGLE SOURCE OF TRUTH for AIA configuration.
4
4
  # All attributes must be declared here to be recognized.
@@ -28,8 +28,8 @@ defaults:
28
28
  # ---------------------------------------------------------------------------
29
29
  llm:
30
30
  temperature: 0.7
31
- max_tokens: 2048
32
- top_p: 1.0
31
+ max_tokens: 32767
32
+ top_p: ~ # nil by default — some models reject requests with both temperature AND top_p
33
33
  frequency_penalty: 0.0
34
34
  presence_penalty: 0.0
35
35
 
@@ -65,13 +65,11 @@ defaults:
65
65
  roles_prefix: roles
66
66
  #
67
67
  skills: []
68
- skills_prefix: skills
69
68
  #
70
69
  tool: ~
71
70
  tools_prefix: tools
72
71
  #
73
72
  system_prompt: ~
74
- parameter_regex: ~
75
73
 
76
74
  roles:
77
75
  dir: ~/.prompts/roles
@@ -96,10 +94,10 @@ defaults:
96
94
  # Env: AIA_AUDIO__VOICE, AIA_AUDIO__SPEAK_COMMAND, etc.
97
95
  # ---------------------------------------------------------------------------
98
96
  audio:
99
- voice: alloy
100
- speak_command: afplay
101
- speech_model: tts-1
102
- transcription_model: whisper-1
97
+ voice: ~
98
+ speak_command: say
99
+ speech_model: ~
100
+ transcription_model: ~
103
101
 
104
102
  # ---------------------------------------------------------------------------
105
103
  # Image Configuration
@@ -145,26 +143,31 @@ defaults:
145
143
  tokens: false
146
144
  no_mcp: false
147
145
  speak: false
148
- terse: false # DEPRECATED: no longer used
149
146
  shell: true
150
147
  erb: true
151
148
  clear: false
152
149
  consensus: false
150
+ track_pipeline: false
151
+ expert_routing: false
152
+ auto_tool_filter: true
153
+ tool_filter_load: false
154
+ tool_filter_save: false
155
+ allow_ruby_eval: false
156
+ paint: false
157
+ thinking: false
158
+
159
+ # ---------------------------------------------------------------------------
160
+ # Tool Filter Configuration
161
+ # Access: AIA.config.tool_filter.timeout_s
162
+ # Env: AIA_TOOL_FILTER__TIMEOUT_S
163
+ # ---------------------------------------------------------------------------
164
+ tool_filter:
165
+ timeout_s: 10
153
166
 
154
167
  # ---------------------------------------------------------------------------
155
168
  # Logger Configuration
156
169
  # Access: AIA.config.logger.aia.file, AIA.config.logger.llm.level, etc.
157
170
  # Env: AIA_LOGGER__AIA__FILE, AIA_LOGGER__LLM__LEVEL, etc.
158
- #
159
- # Configures logging for three systems:
160
- # aia: AIA application logging
161
- # llm: RubyLLM gem logging
162
- # mcp: RubyLLM::MCP gem logging
163
- #
164
- # file: STDOUT, STDERR, or a file path (e.g., ~/.aia/aia.log)
165
- # All three loggers can write to the same file safely.
166
- # level: debug, info, warn, error, fatal
167
- # flush: true = immediate write (no buffering), false = buffered writes
168
171
  # ---------------------------------------------------------------------------
169
172
  logger:
170
173
  aia:
@@ -190,7 +193,6 @@ defaults:
190
193
  # Model Registry Configuration
191
194
  # Access: AIA.config.registry.refresh
192
195
  # Env: AIA_REGISTRY__REFRESH
193
- # Note: last_refresh is derived from models.json file modification time
194
196
  # ---------------------------------------------------------------------------
195
197
  registry:
196
198
  refresh: 7 # days between refreshes (0 = disable periodic refresh)
@@ -204,14 +206,6 @@ defaults:
204
206
  # ---------------------------------------------------------------------------
205
207
  # MCP Servers Configuration
206
208
  # Access: AIA.config.mcp_servers (array of server configs)
207
- #
208
- # Example:
209
- # mcp_servers:
210
- # - name: my-server
211
- # command: /path/to/server
212
- # args: []
213
- # env: {}
214
- # timeout: 8000
215
209
  # ---------------------------------------------------------------------------
216
210
  mcp_servers: []
217
211
  mcp_use: ~
@@ -219,15 +213,50 @@ defaults:
219
213
 
220
214
  # ---------------------------------------------------------------------------
221
215
  # Paths Configuration
222
- # Access: AIA.config.paths.aia_dir, AIA.config.paths.config_file
216
+ # Access: AIA.config.paths.aia_dir, AIA.config.paths.config_file, AIA.config.paths.plugins_dir
223
217
  # Env: AIA_PATHS__AIA_DIR, AIA_PATHS__CONFIG_FILE
224
- #
225
- # Note: MywayConfig uses XDG Base Directory Specification by default.
226
- # User config is loaded from ~/.config/aia/aia.yml
218
+ # Special-case env: AIA_PLUGINS_DIR
227
219
  # ---------------------------------------------------------------------------
228
220
  paths:
229
221
  aia_dir: ~/.config/aia
230
222
  config_file: ~/.config/aia/aia.yml
223
+ plugins_dir: ~
224
+
225
+ # ---------------------------------------------------------------------------
226
+ # Model Aliases (user-defined aliases override defaults)
227
+ # Access: AIA.config.model_aliases
228
+ # ---------------------------------------------------------------------------
229
+ model_aliases: {}
230
+
231
+ # ---------------------------------------------------------------------------
232
+ # Model Switch History Mode
233
+ # Controls conversation history transfer when switching models mid-chat.
234
+ # Values: clean (fresh start), replay (replay messages), summarize (summary)
235
+ # Access: AIA.config.model_switch_history
236
+ # ---------------------------------------------------------------------------
237
+ model_switch_history: clean
238
+
239
+ # ---------------------------------------------------------------------------
240
+ # Concurrency Configuration
241
+ # Controls automatic MCP server parallelization.
242
+ # Access: AIA.config.concurrency.auto, etc.
243
+ # ---------------------------------------------------------------------------
244
+ concurrency:
245
+ auto: false
246
+ independent_servers: []
247
+ threshold: 2
248
+ mcp_timeout: 120
249
+
250
+ # ---------------------------------------------------------------------------
251
+ # Rules Configuration (Enhanced Multi-KB in v2)
252
+ # Access: AIA.config.rules.dir, AIA.config.rules.enabled, etc.
253
+ # Env: AIA_RULES__DIR, AIA_RULES__ENABLED
254
+ # ---------------------------------------------------------------------------
255
+ rules:
256
+ dir: ~/.config/aia/rules
257
+ enabled: true
258
+ store: memory
259
+ db_path: ~/.config/aia/rules.db
231
260
 
232
261
  # ---------------------------------------------------------------------------
233
262
  # Context Files (set at runtime)
@@ -3,9 +3,19 @@
3
3
  # lib/aia/config/mcp_parser.rb
4
4
  #
5
5
  # Parses MCP server JSON configuration files and converts them
6
- # to the format expected by AIA's config system.
6
+ # to the nested transport format expected by robot_lab.
7
7
  #
8
- # Supports two JSON formats:
8
+ # Output format (robot_lab native):
9
+ # {
10
+ # name: "server_name",
11
+ # transport: { type: "stdio", command: "npx", args: [...], env: {...} },
12
+ # timeout: 8000,
13
+ # topics: ["routing"], # AIA routing metadata (optional)
14
+ # independent: false, # AIA concurrency metadata (optional)
15
+ # group: "services" # AIA grouping metadata (optional)
16
+ # }
17
+ #
18
+ # Supports two JSON input formats:
9
19
  #
10
20
  # 1. Simple format (single server):
11
21
  # {
@@ -31,9 +41,11 @@ module AIA
31
41
  module McpParser
32
42
  class << self
33
43
  # Parse MCP server configuration files and return array of server configs
44
+ # in robot_lab's nested transport format.
34
45
  #
35
46
  # @param file_paths [Array<String>] paths to JSON configuration files
36
- # @return [Array<Hash>] array of server configurations
47
+ # @return [Array<Hash>] array of server configurations with nested transport
48
+ # :reek:TooManyStatements -- per-file loop with warn-and-continue handling for missing files, bad JSON, and read errors
37
49
  def parse_files(file_paths)
38
50
  return [] if file_paths.nil? || file_paths.empty?
39
51
 
@@ -43,7 +55,7 @@ module AIA
43
55
  expanded_path = File.expand_path(file_path)
44
56
 
45
57
  unless File.exist?(expanded_path)
46
- warn "Warning: MCP config file not found: #{file_path}"
58
+ $stderr.puts "Warning: MCP config file not found: #{file_path}"
47
59
  next
48
60
  end
49
61
 
@@ -52,9 +64,9 @@ module AIA
52
64
  parsed = JSON.parse(json_content)
53
65
  servers.concat(convert_to_config_format(parsed, file_path))
54
66
  rescue JSON::ParserError => e
55
- warn "Warning: Invalid JSON in MCP config file '#{file_path}': #{e.message}"
67
+ $stderr.puts "Warning: Invalid JSON in MCP config file '#{file_path}': #{e.message}"
56
68
  rescue StandardError => e
57
- warn "Warning: Error reading MCP config file '#{file_path}': #{e.message}"
69
+ $stderr.puts "Warning: Error reading MCP config file '#{file_path}': #{e.message}"
58
70
  end
59
71
  end
60
72
 
@@ -76,43 +88,38 @@ module AIA
76
88
  end
77
89
  end
78
90
 
79
- # Convert mcpServers format to config format
91
+ # Convert mcpServers format to robot_lab nested transport format
80
92
  #
81
93
  # @param mcp_servers [Hash] the mcpServers hash from JSON
82
94
  # @return [Array<Hash>] array of server configurations
83
95
  def convert_mcp_servers_format(mcp_servers)
84
96
  mcp_servers.map do |name, config|
85
- server = { name: name }
86
-
87
- if config['command']
88
- server[:command] = config['command']
89
- end
90
-
91
- if config['args']
92
- server[:args] = Array(config['args'])
93
- end
94
-
95
- if config['env']
96
- server[:env] = config['env']
97
- end
98
-
99
- if config['timeout']
100
- server[:timeout] = config['timeout'].to_i
101
- end
102
-
103
- if config['url']
104
- server[:url] = config['url']
105
- end
97
+ server = { name: name, transport: build_transport(config) }
98
+ server[:timeout] = config['timeout'].to_i if config['timeout']
99
+ server.merge!(routing_metadata(config))
100
+ end
101
+ end
106
102
 
107
- if config['headers']
108
- server[:headers] = config['headers']
109
- end
103
+ def build_transport(config)
104
+ transport = { type: config['type'] || 'stdio' }
105
+ transport[:command] = config['command'] if config['command']
106
+ transport[:args] = Array(config['args']) if config['args']
107
+ transport[:env] = config['env'] if config['env']
108
+ transport[:url] = config['url'] if config['url']
109
+ transport[:headers] = config['headers'] if config['headers']
110
+ transport
111
+ end
110
112
 
111
- server
112
- end
113
+ # Routing metadata preserved for KBS/AIA
114
+ def routing_metadata(config)
115
+ meta = {}
116
+ meta[:topics] = Array(config['topics']) if config['topics']
117
+ meta[:independent] = config['independent'] unless config['independent'].nil?
118
+ meta[:group] = config['group'] if config['group']
119
+ meta
113
120
  end
114
121
 
115
- # Convert simple format to config format
122
+ # Convert simple format to robot_lab nested transport format
116
123
  #
117
124
  # @param parsed [Hash] parsed JSON with type and command
118
125
  # @param file_path [String] file path for deriving server name
@@ -121,28 +128,22 @@ module AIA
121
128
  # Derive name from filename (e.g., "filesystem.json" -> "filesystem")
122
129
  name = File.basename(file_path, '.*')
123
130
 
124
- server = { name: name }
131
+ transport = { type: parsed['type'] || 'stdio' }
125
132
 
126
- if parsed['command'].is_a?(Array)
133
+ command = parsed['command']
134
+ if command.is_a?(Array)
127
135
  # Command is an array: first element is command, rest are args
128
- server[:command] = parsed['command'].first
129
- server[:args] = parsed['command'][1..] || []
130
- elsif parsed['command']
131
- server[:command] = parsed['command']
132
- server[:args] = parsed['args'] || []
136
+ transport[:command] = command.first
137
+ transport[:args] = command[1..] || []
138
+ elsif command
139
+ transport[:command] = command
140
+ transport[:args] = parsed['args'] || []
133
141
  end
134
142
 
135
- if parsed['env']
136
- server[:env] = parsed['env']
137
- end
143
+ transport[:env] = parsed['env'] if parsed['env']
138
144
 
139
- if parsed['timeout']
140
- server[:timeout] = parsed['timeout'].to_i
141
- end
142
-
143
- if parsed['type']
144
- server[:type] = parsed['type']
145
- end
145
+ server = { name: name, transport: transport }
146
+ server[:timeout] = parsed['timeout'].to_i if parsed['timeout']
146
147
 
147
148
  [server]
148
149
  end
@@ -13,7 +13,15 @@
13
13
 
14
14
  module AIA
15
15
  class ModelSpec
16
- attr_accessor :name, :role, :instance, :internal_id
16
+ attr_accessor :name, :role, :instance, :internal_id, :provider
17
+
18
+ # Provider prefixes that map to RubyLLM provider slugs.
19
+ # 'lms' is stored as-is; RobotFactory maps it to the openai provider
20
+ # with a custom API base so it doesn't conflict with real OpenAI models.
21
+ PROVIDER_ALIASES = {
22
+ 'ollama' => 'ollama',
23
+ 'lms' => 'lms'
24
+ }.freeze
17
25
 
18
26
  def initialize(hash = {})
19
27
  hash = hash.transform_keys(&:to_sym) if hash.respond_to?(:transform_keys)
@@ -22,6 +30,10 @@ module AIA
22
30
  @role = hash[:role]
23
31
  @instance = hash[:instance] || 1
24
32
  @internal_id = hash[:internal_id] || @name
33
+ @provider = hash[:provider]
34
+
35
+ # Extract provider from name if prefixed (e.g., "ollama/llama3" or "lms/my-model")
36
+ extract_provider_from_name! unless @provider
25
37
  end
26
38
 
27
39
  def to_h
@@ -29,7 +41,8 @@ module AIA
29
41
  name: @name,
30
42
  role: @role,
31
43
  instance: @instance,
32
- internal_id: @internal_id
44
+ internal_id: @internal_id,
45
+ provider: @provider
33
46
  }
34
47
  end
35
48
 
@@ -63,5 +76,24 @@ module AIA
63
76
  def duplicate?
64
77
  @instance > 1
65
78
  end
79
+
80
+ # Check if this model uses a local provider
81
+ def local_provider?
82
+ !@provider.nil?
83
+ end
84
+
85
+ private
86
+
87
+ # Extract provider prefix from model name.
88
+ # "ollama/llama3:latest" => provider: "ollama", name: "llama3:latest"
89
+ # "lms/my-model" => provider: "openai", name: "my-model"
90
+ def extract_provider_from_name!
91
+ return unless @name&.include?('/')
92
+
93
+ prefix, rest = @name.split('/', 2)
94
+ return unless PROVIDER_ALIASES.key?(prefix)
95
+ @provider = PROVIDER_ALIASES[prefix]
96
+ @name = rest
97
+ end
66
98
  end
67
99
  end