aidp 0.26.0 ā 0.27.0
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/lib/aidp/cli/checkpoint_command.rb +198 -0
- data/lib/aidp/cli/config_command.rb +71 -0
- data/lib/aidp/cli/enhanced_input.rb +2 -0
- data/lib/aidp/cli/first_run_wizard.rb +8 -7
- data/lib/aidp/cli/harness_command.rb +102 -0
- data/lib/aidp/cli/jobs_command.rb +3 -3
- data/lib/aidp/cli/mcp_dashboard.rb +4 -3
- data/lib/aidp/cli/models_command.rb +662 -0
- data/lib/aidp/cli/providers_command.rb +223 -0
- data/lib/aidp/cli.rb +35 -456
- data/lib/aidp/daemon/runner.rb +2 -2
- data/lib/aidp/debug_mixin.rb +2 -9
- data/lib/aidp/execute/async_work_loop_runner.rb +2 -1
- data/lib/aidp/execute/checkpoint_display.rb +38 -37
- data/lib/aidp/execute/interactive_repl.rb +2 -1
- data/lib/aidp/execute/prompt_manager.rb +4 -4
- data/lib/aidp/execute/work_loop_runner.rb +29 -2
- data/lib/aidp/execute/workflow_selector.rb +2 -2
- data/lib/aidp/harness/config_manager.rb +5 -5
- data/lib/aidp/harness/configuration.rb +32 -2
- data/lib/aidp/harness/enhanced_runner.rb +24 -15
- data/lib/aidp/harness/error_handler.rb +26 -5
- data/lib/aidp/harness/model_cache.rb +269 -0
- data/lib/aidp/harness/model_discovery_service.rb +259 -0
- data/lib/aidp/harness/model_registry.rb +201 -0
- data/lib/aidp/harness/runner.rb +5 -0
- data/lib/aidp/harness/thinking_depth_manager.rb +223 -7
- data/lib/aidp/message_display.rb +0 -46
- data/lib/aidp/providers/adapter.rb +2 -4
- data/lib/aidp/providers/anthropic.rb +141 -128
- data/lib/aidp/providers/base.rb +98 -2
- data/lib/aidp/providers/capability_registry.rb +0 -1
- data/lib/aidp/providers/codex.rb +49 -67
- data/lib/aidp/providers/cursor.rb +71 -59
- data/lib/aidp/providers/gemini.rb +44 -60
- data/lib/aidp/providers/github_copilot.rb +2 -66
- data/lib/aidp/providers/kilocode.rb +24 -80
- data/lib/aidp/providers/opencode.rb +24 -80
- data/lib/aidp/setup/wizard.rb +345 -8
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/plan_generator.rb +93 -14
- data/lib/aidp/watch/review_processor.rb +3 -3
- data/lib/aidp/workflows/guided_agent.rb +3 -3
- data/templates/aidp-development.yml.example +2 -2
- data/templates/aidp-production.yml.example +3 -3
- metadata +9 -1
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
module Aidp
|
|
7
|
+
module Harness
|
|
8
|
+
# ModelRegistry manages the static registry of known model families and their tier classifications
|
|
9
|
+
#
|
|
10
|
+
# The registry uses model families (e.g., "claude-3-5-sonnet") rather than specific versioned
|
|
11
|
+
# model IDs (e.g., "claude-3-5-sonnet-20241022"). This design provides:
|
|
12
|
+
# - No version tracking burden - registry tracks families, not every dated version
|
|
13
|
+
# - Future-proofing - new model versions automatically inherit family tier
|
|
14
|
+
# - Provider autonomy - each provider handles version-specific naming
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# registry = ModelRegistry.new
|
|
18
|
+
# registry.get_model_info("claude-3-5-sonnet")
|
|
19
|
+
# # => { name: "Claude 3.5 Sonnet", tier: "standard", ... }
|
|
20
|
+
#
|
|
21
|
+
# registry.models_for_tier("standard")
|
|
22
|
+
# # => ["claude-3-5-sonnet", "gpt-4-turbo", ...]
|
|
23
|
+
#
|
|
24
|
+
# registry.match_to_family("claude-3-5-sonnet-20241022")
|
|
25
|
+
# # => "claude-3-5-sonnet"
|
|
26
|
+
class ModelRegistry
|
|
27
|
+
class RegistryError < StandardError; end
|
|
28
|
+
class InvalidRegistrySchema < RegistryError; end
|
|
29
|
+
class ModelNotFound < RegistryError; end
|
|
30
|
+
|
|
31
|
+
VALID_TIERS = %w[mini standard advanced].freeze
|
|
32
|
+
VALID_CAPABILITIES = %w[chat code vision tool_use streaming json_mode].freeze
|
|
33
|
+
VALID_SPEEDS = %w[very_fast fast medium slow].freeze
|
|
34
|
+
|
|
35
|
+
attr_reader :registry_data
|
|
36
|
+
|
|
37
|
+
def initialize(registry_path: nil)
|
|
38
|
+
@registry_path = registry_path || default_registry_path
|
|
39
|
+
@registry_data = load_static_registry
|
|
40
|
+
validate_registry_schema
|
|
41
|
+
Aidp.log_debug("model_registry", "initialized", models: @registry_data["model_families"].keys.size)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Get complete model information for a family
|
|
45
|
+
#
|
|
46
|
+
# @param family_name [String] The model family name (e.g., "claude-3-5-sonnet")
|
|
47
|
+
# @return [Hash, nil] Model metadata hash or nil if not found
|
|
48
|
+
def get_model_info(family_name)
|
|
49
|
+
info = @registry_data["model_families"][family_name]
|
|
50
|
+
return nil unless info
|
|
51
|
+
|
|
52
|
+
info.merge("family" => family_name)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Get all model families for a specific tier
|
|
56
|
+
#
|
|
57
|
+
# @param tier [String, Symbol] The tier name (mini, standard, advanced)
|
|
58
|
+
# @return [Array<String>] List of model family names for the tier
|
|
59
|
+
def models_for_tier(tier)
|
|
60
|
+
tier_str = tier.to_s
|
|
61
|
+
unless VALID_TIERS.include?(tier_str)
|
|
62
|
+
Aidp.log_warn("model_registry", "invalid tier requested", tier: tier_str, valid: VALID_TIERS)
|
|
63
|
+
return []
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
families = @registry_data["model_families"].select { |_family, info|
|
|
67
|
+
info["tier"] == tier_str
|
|
68
|
+
}.keys
|
|
69
|
+
|
|
70
|
+
Aidp.log_debug("model_registry", "found models for tier", tier: tier_str, count: families.size)
|
|
71
|
+
families
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Classify a model family's tier
|
|
75
|
+
#
|
|
76
|
+
# @param family_name [String] The model family name
|
|
77
|
+
# @return [String, nil] The tier name or nil if not found
|
|
78
|
+
def classify_model_tier(family_name)
|
|
79
|
+
info = get_model_info(family_name)
|
|
80
|
+
tier = info&.fetch("tier", nil)
|
|
81
|
+
Aidp.log_debug("model_registry", "classified tier", family: family_name, tier: tier)
|
|
82
|
+
tier
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Match a versioned model name to its family using pattern matching
|
|
86
|
+
#
|
|
87
|
+
# This method attempts to normalize versioned model names (e.g., "claude-3-5-sonnet-20241022")
|
|
88
|
+
# to their family name (e.g., "claude-3-5-sonnet") by testing against version_pattern regexes.
|
|
89
|
+
#
|
|
90
|
+
# @param versioned_name [String] The versioned model name
|
|
91
|
+
# @return [String, nil] The family name if matched, nil otherwise
|
|
92
|
+
def match_to_family(versioned_name)
|
|
93
|
+
@registry_data["model_families"].each do |family, info|
|
|
94
|
+
pattern = info["version_pattern"]
|
|
95
|
+
next unless pattern
|
|
96
|
+
|
|
97
|
+
begin
|
|
98
|
+
regex = Regexp.new("^#{pattern}$")
|
|
99
|
+
if regex.match?(versioned_name)
|
|
100
|
+
Aidp.log_debug("model_registry", "matched to family", versioned: versioned_name, family: family)
|
|
101
|
+
return family
|
|
102
|
+
end
|
|
103
|
+
rescue RegexpError => e
|
|
104
|
+
Aidp.log_error("model_registry", "invalid pattern", family: family, pattern: pattern, error: e.message)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# If no pattern matches, check if the versioned_name is itself a family
|
|
109
|
+
if @registry_data["model_families"].key?(versioned_name)
|
|
110
|
+
Aidp.log_debug("model_registry", "exact family match", name: versioned_name)
|
|
111
|
+
return versioned_name
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
Aidp.log_debug("model_registry", "no family match", versioned: versioned_name)
|
|
115
|
+
nil
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Get all registered model families
|
|
119
|
+
#
|
|
120
|
+
# @return [Array<String>] List of all model family names
|
|
121
|
+
def all_families
|
|
122
|
+
@registry_data["model_families"].keys
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Check if a model family exists in the registry
|
|
126
|
+
#
|
|
127
|
+
# @param family_name [String] The model family name
|
|
128
|
+
# @return [Boolean] True if the family exists
|
|
129
|
+
def family_exists?(family_name)
|
|
130
|
+
@registry_data["model_families"].key?(family_name)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Get all tiers that have at least one model
|
|
134
|
+
#
|
|
135
|
+
# @return [Array<String>] List of tier names
|
|
136
|
+
def available_tiers
|
|
137
|
+
@registry_data["model_families"].values.map { |info| info["tier"] }.uniq.sort
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
def default_registry_path
|
|
143
|
+
Pathname.new(__dir__).parent.join("data", "model_registry.yml")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def load_static_registry
|
|
147
|
+
unless File.exist?(@registry_path)
|
|
148
|
+
raise RegistryError, "Model registry file not found at #{@registry_path}"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
data = YAML.load_file(@registry_path)
|
|
152
|
+
unless data.is_a?(Hash) && data.key?("model_families")
|
|
153
|
+
raise InvalidRegistrySchema, "Registry must contain 'model_families' key"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
Aidp.log_info("model_registry", "loaded registry", path: @registry_path, families: data["model_families"].size)
|
|
157
|
+
data
|
|
158
|
+
rescue Psych::SyntaxError => e
|
|
159
|
+
raise InvalidRegistrySchema, "Invalid YAML in registry file: #{e.message}"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def validate_registry_schema
|
|
163
|
+
@registry_data["model_families"].each do |family, info|
|
|
164
|
+
validate_model_entry(family, info)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def validate_model_entry(family, info)
|
|
169
|
+
# Required fields
|
|
170
|
+
unless info["tier"]
|
|
171
|
+
raise InvalidRegistrySchema, "Model family '#{family}' missing required 'tier' field"
|
|
172
|
+
end
|
|
173
|
+
unless VALID_TIERS.include?(info["tier"])
|
|
174
|
+
raise InvalidRegistrySchema, "Model family '#{family}' has invalid tier '#{info["tier"]}'. Valid: #{VALID_TIERS.join(", ")}"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Optional but validated if present
|
|
178
|
+
if info["capabilities"]
|
|
179
|
+
unless info["capabilities"].is_a?(Array)
|
|
180
|
+
raise InvalidRegistrySchema, "Model family '#{family}' capabilities must be an array"
|
|
181
|
+
end
|
|
182
|
+
invalid_caps = info["capabilities"] - VALID_CAPABILITIES
|
|
183
|
+
unless invalid_caps.empty?
|
|
184
|
+
Aidp.log_warn("model_registry", "unknown capabilities", family: family, unknown: invalid_caps)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
if info["speed"] && !VALID_SPEEDS.include?(info["speed"])
|
|
189
|
+
Aidp.log_warn("model_registry", "invalid speed", family: family, speed: info["speed"], valid: VALID_SPEEDS)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Validate numeric fields if present
|
|
193
|
+
%w[context_window max_output cost_per_1m_input cost_per_1m_output].each do |field|
|
|
194
|
+
if info[field] && !info[field].is_a?(Numeric)
|
|
195
|
+
raise InvalidRegistrySchema, "Model family '#{family}' field '#{field}' must be numeric"
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
data/lib/aidp/harness/runner.rb
CHANGED
|
@@ -11,6 +11,7 @@ require_relative "error_handler"
|
|
|
11
11
|
require_relative "status_display"
|
|
12
12
|
require_relative "completion_checker"
|
|
13
13
|
require_relative "../concurrency"
|
|
14
|
+
require_relative "../errors"
|
|
14
15
|
|
|
15
16
|
module Aidp
|
|
16
17
|
module Harness
|
|
@@ -135,6 +136,10 @@ module Aidp
|
|
|
135
136
|
end
|
|
136
137
|
end
|
|
137
138
|
end
|
|
139
|
+
rescue Aidp::Errors::ConfigurationError
|
|
140
|
+
# Configuration errors should crash immediately (crash-early principle)
|
|
141
|
+
# Re-raise without catching
|
|
142
|
+
raise
|
|
138
143
|
rescue => e
|
|
139
144
|
@state = STATES[:error]
|
|
140
145
|
@last_error = e
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "capability_registry"
|
|
4
4
|
require_relative "configuration"
|
|
5
|
+
require_relative "../message_display"
|
|
5
6
|
|
|
6
7
|
module Aidp
|
|
7
8
|
module Harness
|
|
8
9
|
# Manages thinking depth tier selection and escalation
|
|
9
10
|
# Integrates with CapabilityRegistry and Configuration to select appropriate models
|
|
10
11
|
class ThinkingDepthManager
|
|
12
|
+
include Aidp::MessageDisplay
|
|
13
|
+
|
|
11
14
|
attr_reader :configuration, :registry
|
|
12
15
|
|
|
13
16
|
def initialize(configuration, registry: nil, root_dir: nil)
|
|
@@ -150,11 +153,48 @@ module Aidp
|
|
|
150
153
|
tier ||= current_tier
|
|
151
154
|
validate_tier!(tier)
|
|
152
155
|
|
|
153
|
-
#
|
|
156
|
+
# First, try to get models from user's configuration for this tier
|
|
157
|
+
configured_models = configuration.models_for_tier(tier)
|
|
158
|
+
|
|
159
|
+
if configured_models.any?
|
|
160
|
+
# If provider specified, try to find model for that provider in config
|
|
161
|
+
if provider
|
|
162
|
+
matching_model = configured_models.find { |m| m[:provider] == provider }
|
|
163
|
+
if matching_model
|
|
164
|
+
Aidp.log_debug("thinking_depth_manager", "Selected model from user config",
|
|
165
|
+
tier: tier,
|
|
166
|
+
provider: provider,
|
|
167
|
+
model: matching_model[:model])
|
|
168
|
+
return [matching_model[:provider], matching_model[:model], {}]
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# If provider doesn't support tier and switching allowed, try other providers in config
|
|
172
|
+
unless configuration.allow_provider_switch_for_tier?
|
|
173
|
+
Aidp.log_warn("thinking_depth_manager", "Provider lacks tier in config, switching disabled",
|
|
174
|
+
tier: tier,
|
|
175
|
+
provider: provider)
|
|
176
|
+
return nil
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Try any configured model for this tier (prioritize first in list)
|
|
181
|
+
first_model = configured_models.first
|
|
182
|
+
if first_model
|
|
183
|
+
Aidp.log_info("thinking_depth_manager", "Selected model from user config",
|
|
184
|
+
tier: tier,
|
|
185
|
+
original_provider: provider,
|
|
186
|
+
selected_provider: first_model[:provider],
|
|
187
|
+
model: first_model[:model])
|
|
188
|
+
return [first_model[:provider], first_model[:model], {}]
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Fall back to catalog-based selection if no models in user config
|
|
193
|
+
# If provider specified, try to find model for that provider in catalog
|
|
154
194
|
if provider
|
|
155
195
|
model_name, model_data = @registry.best_model_for_tier(tier, provider)
|
|
156
196
|
if model_name
|
|
157
|
-
Aidp.log_debug("thinking_depth_manager", "Selected model",
|
|
197
|
+
Aidp.log_debug("thinking_depth_manager", "Selected model from catalog",
|
|
158
198
|
tier: tier,
|
|
159
199
|
provider: provider,
|
|
160
200
|
model: model_name)
|
|
@@ -163,20 +203,24 @@ module Aidp
|
|
|
163
203
|
|
|
164
204
|
# If provider doesn't support tier and switching allowed, try others
|
|
165
205
|
unless configuration.allow_provider_switch_for_tier?
|
|
166
|
-
Aidp.log_warn("thinking_depth_manager", "Provider lacks tier, switching disabled",
|
|
206
|
+
Aidp.log_warn("thinking_depth_manager", "Provider lacks tier in catalog, switching disabled",
|
|
167
207
|
tier: tier,
|
|
168
208
|
provider: provider)
|
|
169
209
|
return nil
|
|
170
210
|
end
|
|
171
211
|
end
|
|
172
212
|
|
|
173
|
-
# Try all providers
|
|
213
|
+
# Try all providers in catalog
|
|
214
|
+
if provider && !configuration.allow_provider_switch_for_tier?
|
|
215
|
+
return nil
|
|
216
|
+
end
|
|
217
|
+
|
|
174
218
|
providers_to_try = provider ? [@registry.provider_names - [provider]].flatten : @registry.provider_names
|
|
175
219
|
|
|
176
220
|
providers_to_try.each do |prov_name|
|
|
177
221
|
model_name, model_data = @registry.best_model_for_tier(tier, prov_name)
|
|
178
222
|
if model_name
|
|
179
|
-
Aidp.log_info("thinking_depth_manager", "Selected model from alternate provider",
|
|
223
|
+
Aidp.log_info("thinking_depth_manager", "Selected model from catalog (alternate provider)",
|
|
180
224
|
tier: tier,
|
|
181
225
|
original_provider: provider,
|
|
182
226
|
selected_provider: prov_name,
|
|
@@ -185,10 +229,23 @@ module Aidp
|
|
|
185
229
|
end
|
|
186
230
|
end
|
|
187
231
|
|
|
188
|
-
|
|
232
|
+
# No model found for requested tier - try fallback to other tiers
|
|
233
|
+
Aidp.log_warn("thinking_depth_manager", "No model found for requested tier, trying fallback",
|
|
189
234
|
tier: tier,
|
|
190
235
|
provider: provider)
|
|
191
|
-
|
|
236
|
+
|
|
237
|
+
result = try_fallback_tiers(tier, provider)
|
|
238
|
+
|
|
239
|
+
unless result
|
|
240
|
+
# Enhanced error message with discovery hints
|
|
241
|
+
display_enhanced_tier_error(tier, provider)
|
|
242
|
+
|
|
243
|
+
Aidp.log_error("thinking_depth_manager", "No model found for tier or fallback tiers",
|
|
244
|
+
tier: tier,
|
|
245
|
+
provider: provider)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
result
|
|
192
249
|
end
|
|
193
250
|
|
|
194
251
|
# Get tier for a specific model
|
|
@@ -330,6 +387,165 @@ module Aidp
|
|
|
330
387
|
# Keep history bounded
|
|
331
388
|
@tier_history.shift if @tier_history.size > 100
|
|
332
389
|
end
|
|
390
|
+
|
|
391
|
+
# Try to find a model in fallback tiers when requested tier has no models
|
|
392
|
+
# Tries lower tiers first (cheaper), then higher tiers
|
|
393
|
+
# Returns [provider_name, model_name, model_data] or nil
|
|
394
|
+
def try_fallback_tiers(requested_tier, provider)
|
|
395
|
+
# Generate fallback order: try lower tiers first, then higher
|
|
396
|
+
fallback_tiers = generate_fallback_tier_order(requested_tier)
|
|
397
|
+
|
|
398
|
+
fallback_tiers.each do |fallback_tier|
|
|
399
|
+
# First, try user's configuration for this fallback tier
|
|
400
|
+
configured_models = configuration.models_for_tier(fallback_tier)
|
|
401
|
+
|
|
402
|
+
if configured_models.any?
|
|
403
|
+
# Try specified provider first if given
|
|
404
|
+
if provider
|
|
405
|
+
matching_model = configured_models.find { |m| m[:provider] == provider }
|
|
406
|
+
if matching_model
|
|
407
|
+
Aidp.log_warn("thinking_depth_manager", "Falling back to different tier (from config)",
|
|
408
|
+
requested_tier: requested_tier,
|
|
409
|
+
fallback_tier: fallback_tier,
|
|
410
|
+
provider: provider,
|
|
411
|
+
model: matching_model[:model])
|
|
412
|
+
return [matching_model[:provider], matching_model[:model], {}]
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# Try any configured model for this tier
|
|
417
|
+
first_model = configured_models.first
|
|
418
|
+
if first_model
|
|
419
|
+
Aidp.log_warn("thinking_depth_manager", "Falling back to different tier and provider (from config)",
|
|
420
|
+
requested_tier: requested_tier,
|
|
421
|
+
fallback_tier: fallback_tier,
|
|
422
|
+
requested_provider: provider,
|
|
423
|
+
fallback_provider: first_model[:provider],
|
|
424
|
+
model: first_model[:model])
|
|
425
|
+
return [first_model[:provider], first_model[:model], {}]
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# Fall back to catalog if no models in config
|
|
430
|
+
# Try specified provider first if given
|
|
431
|
+
if provider
|
|
432
|
+
model_name, model_data = @registry.best_model_for_tier(fallback_tier, provider)
|
|
433
|
+
if model_name
|
|
434
|
+
Aidp.log_warn("thinking_depth_manager", "Falling back to different tier (from catalog)",
|
|
435
|
+
requested_tier: requested_tier,
|
|
436
|
+
fallback_tier: fallback_tier,
|
|
437
|
+
provider: provider,
|
|
438
|
+
model: model_name)
|
|
439
|
+
return [provider, model_name, model_data]
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# Try all available providers in catalog
|
|
444
|
+
@registry.provider_names.each do |prov_name|
|
|
445
|
+
next if prov_name == provider # Skip if already tried above
|
|
446
|
+
|
|
447
|
+
model_name, model_data = @registry.best_model_for_tier(fallback_tier, prov_name)
|
|
448
|
+
if model_name
|
|
449
|
+
Aidp.log_warn("thinking_depth_manager", "Falling back to different tier and provider (from catalog)",
|
|
450
|
+
requested_tier: requested_tier,
|
|
451
|
+
fallback_tier: fallback_tier,
|
|
452
|
+
requested_provider: provider,
|
|
453
|
+
fallback_provider: prov_name,
|
|
454
|
+
model: model_name)
|
|
455
|
+
return [prov_name, model_name, model_data]
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
nil
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
# Generate fallback tier order: lower tiers first (cheaper), then higher
|
|
464
|
+
# For example, if tier is "standard", try: mini, thinking, pro, max
|
|
465
|
+
def generate_fallback_tier_order(tier)
|
|
466
|
+
current_priority = @registry.tier_priority(tier) || 1
|
|
467
|
+
all_tiers = CapabilityRegistry::VALID_TIERS
|
|
468
|
+
|
|
469
|
+
# Split into lower and higher tiers
|
|
470
|
+
lower_tiers = all_tiers.select { |t| (@registry.tier_priority(t) || 0) < current_priority }.reverse
|
|
471
|
+
higher_tiers = all_tiers.select { |t| (@registry.tier_priority(t) || 0) > current_priority }
|
|
472
|
+
|
|
473
|
+
# Try lower tiers first (cost optimization), then higher tiers
|
|
474
|
+
lower_tiers + higher_tiers
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
# Display enhanced error message with discovery hints
|
|
478
|
+
def display_enhanced_tier_error(tier, provider)
|
|
479
|
+
return unless defined?(Aidp::MessageDisplay)
|
|
480
|
+
|
|
481
|
+
# Check if there are discovered models in cache
|
|
482
|
+
discovered_models = check_discovered_models(tier, provider)
|
|
483
|
+
|
|
484
|
+
if discovered_models&.any?
|
|
485
|
+
display_tier_error_with_suggestions(tier, provider, discovered_models)
|
|
486
|
+
else
|
|
487
|
+
display_tier_error_with_discovery_hint(tier, provider)
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# Check cache for discovered models for this tier
|
|
492
|
+
def check_discovered_models(tier, provider)
|
|
493
|
+
require_relative "model_cache"
|
|
494
|
+
require_relative "model_registry"
|
|
495
|
+
|
|
496
|
+
cache = Aidp::Harness::ModelCache.new
|
|
497
|
+
registry = Aidp::Harness::ModelRegistry.new
|
|
498
|
+
|
|
499
|
+
# Get all cached models for the provider
|
|
500
|
+
cached_models = cache.get_cached_models(provider)
|
|
501
|
+
return nil unless cached_models&.any?
|
|
502
|
+
|
|
503
|
+
# Filter to models for the requested tier
|
|
504
|
+
tier_models = cached_models.select do |model|
|
|
505
|
+
family = model[:family] || model["family"]
|
|
506
|
+
model_info = registry.get_model_info(family)
|
|
507
|
+
model_info && model_info["tier"] == tier.to_s
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
tier_models.any? ? tier_models : nil
|
|
511
|
+
rescue => e
|
|
512
|
+
Aidp.log_debug("thinking_depth_manager", "failed to check cached models",
|
|
513
|
+
error: e.message)
|
|
514
|
+
nil
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# Display error with model suggestions from cache
|
|
518
|
+
def display_tier_error_with_suggestions(tier, provider, models)
|
|
519
|
+
display_message("\nā No model configured for '#{tier}' tier", type: :error)
|
|
520
|
+
display_message(" Provider: #{provider}", type: :info) if provider
|
|
521
|
+
|
|
522
|
+
display_message("\nš” Discovered models for this tier:", type: :highlight)
|
|
523
|
+
models.first(3).each do |model|
|
|
524
|
+
model_name = model[:name] || model["name"]
|
|
525
|
+
display_message(" - #{model_name}", type: :info)
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
display_message("\n Add to aidp.yml:", type: :highlight)
|
|
529
|
+
display_message(" providers:", type: :info)
|
|
530
|
+
display_message(" #{provider}:", type: :info)
|
|
531
|
+
display_message(" thinking:", type: :info)
|
|
532
|
+
display_message(" tiers:", type: :info)
|
|
533
|
+
display_message(" #{tier}:", type: :info)
|
|
534
|
+
display_message(" models:", type: :info)
|
|
535
|
+
first_model = models.first[:name] || models.first["name"]
|
|
536
|
+
display_message(" - model: #{first_model}\n", type: :info)
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
# Display error with discovery hint
|
|
540
|
+
def display_tier_error_with_discovery_hint(tier, provider)
|
|
541
|
+
display_message("\nā No model configured for '#{tier}' tier", type: :error)
|
|
542
|
+
display_message(" Provider: #{provider}", type: :info) if provider
|
|
543
|
+
|
|
544
|
+
display_message("\nš” Suggested actions:", type: :highlight)
|
|
545
|
+
display_message(" 1. Run 'aidp models discover' to find available models", type: :info)
|
|
546
|
+
display_message(" 2. Run 'aidp models list --tier=#{tier}' to see models for this tier", type: :info)
|
|
547
|
+
display_message(" 3. Run 'aidp models validate' to check your configuration\n", type: :info)
|
|
548
|
+
end
|
|
333
549
|
end
|
|
334
550
|
end
|
|
335
551
|
end
|
data/lib/aidp/message_display.rb
CHANGED
|
@@ -25,7 +25,6 @@ module Aidp
|
|
|
25
25
|
|
|
26
26
|
# Instance helper for displaying a colored message via TTY::Prompt
|
|
27
27
|
def display_message(message, type: :info)
|
|
28
|
-
return if suppress_display_message?(message)
|
|
29
28
|
# Ensure message is UTF-8 encoded to handle emoji and special characters
|
|
30
29
|
message_str = message.to_s
|
|
31
30
|
message_str = message_str.force_encoding("UTF-8") if message_str.encoding.name == "ASCII-8BIT"
|
|
@@ -43,32 +42,9 @@ module Aidp
|
|
|
43
42
|
end
|
|
44
43
|
end
|
|
45
44
|
|
|
46
|
-
# Check if specific display message should be suppressed in test/CI environments
|
|
47
|
-
def suppress_display_message?(message)
|
|
48
|
-
return false unless in_test_environment?
|
|
49
|
-
|
|
50
|
-
message_str = message.to_s
|
|
51
|
-
# Only suppress specific automated status messages, not CLI output
|
|
52
|
-
message_str.include?("š Provider switch:") ||
|
|
53
|
-
message_str.include?("š Model switch:") ||
|
|
54
|
-
message_str.include?("š“ Circuit breaker opened") ||
|
|
55
|
-
message_str.include?("š¢ Circuit breaker reset") ||
|
|
56
|
-
message_str.include?("ā No providers available") ||
|
|
57
|
-
message_str.include?("ā No models available") ||
|
|
58
|
-
message_str.include?("š Execution Summary") ||
|
|
59
|
-
message_str.include?("ā¶ļø [") || # Workstream execution messages
|
|
60
|
-
message_str.include?("ā
[") || # Workstream success messages
|
|
61
|
-
message_str.include?("ā [") # Workstream failure messages
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def in_test_environment?
|
|
65
|
-
ENV["RSPEC_RUNNING"] || ENV["CI"] || ENV["RAILS_ENV"] == "test" || ENV["RACK_ENV"] == "test"
|
|
66
|
-
end
|
|
67
|
-
|
|
68
45
|
module ClassMethods
|
|
69
46
|
# Class-level display helper (uses fresh prompt to respect $stdout changes)
|
|
70
47
|
def display_message(message, type: :info)
|
|
71
|
-
return if suppress_display_message?(message)
|
|
72
48
|
# Ensure message is UTF-8 encoded to handle emoji and special characters
|
|
73
49
|
message_str = message.to_s
|
|
74
50
|
message_str = message_str.force_encoding("UTF-8") if message_str.encoding.name == "ASCII-8BIT"
|
|
@@ -78,28 +54,6 @@ module Aidp
|
|
|
78
54
|
|
|
79
55
|
private
|
|
80
56
|
|
|
81
|
-
# Check if specific display message should be suppressed in test/CI environments
|
|
82
|
-
def suppress_display_message?(message)
|
|
83
|
-
return false unless in_test_environment?
|
|
84
|
-
|
|
85
|
-
message_str = message.to_s
|
|
86
|
-
# Only suppress specific automated status messages, not CLI output
|
|
87
|
-
message_str.include?("š Provider switch:") ||
|
|
88
|
-
message_str.include?("š Model switch:") ||
|
|
89
|
-
message_str.include?("š“ Circuit breaker opened") ||
|
|
90
|
-
message_str.include?("š¢ Circuit breaker reset") ||
|
|
91
|
-
message_str.include?("ā No providers available") ||
|
|
92
|
-
message_str.include?("ā No models available") ||
|
|
93
|
-
message_str.include?("š Execution Summary") ||
|
|
94
|
-
message_str.include?("ā¶ļø [") || # Workstream execution messages
|
|
95
|
-
message_str.include?("ā
[") || # Workstream success messages
|
|
96
|
-
message_str.include?("ā [") # Workstream failure messages
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def in_test_environment?
|
|
100
|
-
ENV["RSPEC_RUNNING"] || ENV["CI"] || ENV["RAILS_ENV"] == "test" || ENV["RACK_ENV"] == "test"
|
|
101
|
-
end
|
|
102
|
-
|
|
103
57
|
# Don't memoize - create fresh prompt each time to respect $stdout redirection in tests
|
|
104
58
|
def class_message_display_prompt
|
|
105
59
|
TTY::Prompt.new
|
|
@@ -65,8 +65,7 @@ module Aidp
|
|
|
65
65
|
# supports_json_mode: true,
|
|
66
66
|
# supports_tool_use: true,
|
|
67
67
|
# supports_vision: false,
|
|
68
|
-
# supports_file_upload: true
|
|
69
|
-
# streaming: true
|
|
68
|
+
# supports_file_upload: true
|
|
70
69
|
# }
|
|
71
70
|
def capabilities
|
|
72
71
|
{
|
|
@@ -75,8 +74,7 @@ module Aidp
|
|
|
75
74
|
supports_json_mode: false,
|
|
76
75
|
supports_tool_use: false,
|
|
77
76
|
supports_vision: false,
|
|
78
|
-
supports_file_upload: false
|
|
79
|
-
streaming: false
|
|
77
|
+
supports_file_upload: false
|
|
80
78
|
}
|
|
81
79
|
end
|
|
82
80
|
|