aidp 0.34.1 → 0.39.3
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/README.md +14 -14
- data/lib/aidp/agent_harness_adapter.rb +309 -0
- data/lib/aidp/cli/issue_importer.rb +20 -6
- data/lib/aidp/cli/jobs_command.rb +1 -1
- data/lib/aidp/cli/models_command.rb +9 -16
- data/lib/aidp/cli/prompts_command.rb +253 -0
- data/lib/aidp/cli/security_command.rb +4 -10
- data/lib/aidp/cli/storage_command.rb +277 -0
- data/lib/aidp/cli/temporal_command.rb +460 -0
- data/lib/aidp/cli.rb +24 -2
- data/lib/aidp/config.rb +15 -0
- data/lib/aidp/config_paths.rb +12 -2
- data/lib/aidp/database/migrations.rb +114 -0
- data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
- data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
- data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
- data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
- data/lib/aidp/database/repositories/job_repository.rb +278 -0
- data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
- data/lib/aidp/database/repositories/progress_repository.rb +210 -0
- data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
- data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
- data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
- data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
- data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
- data/lib/aidp/database/repositories/task_repository.rb +231 -0
- data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
- data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
- data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
- data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
- data/lib/aidp/database/repository.rb +166 -0
- data/lib/aidp/database/schema.rb +347 -0
- data/lib/aidp/database/storage_migrator.rb +693 -0
- data/lib/aidp/database.rb +139 -0
- data/lib/aidp/debug_mixin.rb +2 -2
- data/lib/aidp/execute/checkpoint.rb +26 -0
- data/lib/aidp/execute/checkpoint_display.rb +16 -0
- data/lib/aidp/execute/prompt_evaluator.rb +30 -57
- data/lib/aidp/execute/work_loop_runner.rb +294 -46
- data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
- data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
- data/lib/aidp/harness/ai_decision_engine.rb +78 -296
- data/lib/aidp/harness/config_schema.rb +59 -0
- data/lib/aidp/harness/configuration.rb +229 -10
- data/lib/aidp/harness/deprecation_cache.rb +53 -11
- data/lib/aidp/harness/enhanced_runner.rb +1 -1
- data/lib/aidp/harness/error_handler.rb +11 -5
- data/lib/aidp/harness/provider_config.rb +36 -0
- data/lib/aidp/harness/provider_factory.rb +26 -24
- data/lib/aidp/harness/provider_info.rb +9 -5
- data/lib/aidp/harness/provider_manager.rb +2 -2
- data/lib/aidp/harness/provider_metrics.rb +103 -1
- data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
- data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
- data/lib/aidp/harness/runner.rb +7 -2
- data/lib/aidp/harness/state_manager.rb +1 -1
- data/lib/aidp/harness/test_runner.rb +167 -11
- data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
- data/lib/aidp/harness/usage_limit.rb +179 -0
- data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
- data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
- data/lib/aidp/harness/usage_period.rb +234 -0
- data/lib/aidp/init/agent_instructions_generator.rb +317 -0
- data/lib/aidp/init/runner.rb +25 -4
- data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
- data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
- data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
- data/lib/aidp/interfaces/logger_interface.rb +150 -0
- data/lib/aidp/interfaces/ui_interface.rb +254 -0
- data/lib/aidp/logger.rb +25 -7
- data/lib/aidp/orchestration_adapter.rb +279 -0
- data/lib/aidp/pr_worktree_manager.rb +4 -15
- data/lib/aidp/prompts/feedback_collector.rb +198 -0
- data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
- data/lib/aidp/prompts/template_evolver.rb +305 -0
- data/lib/aidp/prompts/template_version_manager.rb +325 -0
- data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
- data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
- data/lib/aidp/setup/wizard.rb +616 -176
- data/lib/aidp/shell_executor.rb +58 -0
- data/lib/aidp/style_guide/selector.rb +56 -1
- data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
- data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
- data/lib/aidp/temporal/activities/base_activity.rb +78 -0
- data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
- data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
- data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
- data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
- data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
- data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
- data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
- data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
- data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
- data/lib/aidp/temporal/configuration.rb +169 -0
- data/lib/aidp/temporal/connection.rb +93 -0
- data/lib/aidp/temporal/worker.rb +119 -0
- data/lib/aidp/temporal/workflow_client.rb +171 -0
- data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
- data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
- data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
- data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
- data/lib/aidp/temporal.rb +120 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/base_processor.rb +35 -0
- data/lib/aidp/watch/build_processor.rb +132 -0
- data/lib/aidp/watch/github_state_extractor.rb +0 -8
- data/lib/aidp/watch/rebase_processor.rb +245 -0
- data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
- data/lib/aidp/watch/runner.rb +366 -285
- data/lib/aidp/watch/state_store.rb +80 -0
- data/lib/aidp/watch/work_item.rb +96 -0
- data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
- data/lib/aidp/watch/worktree_reconciler.rb +643 -0
- data/lib/aidp/workflows/guided_agent.rb +1 -1
- data/lib/aidp/worktree.rb +62 -0
- data/templates/aidp.yml.example +70 -0
- metadata +117 -16
- data/lib/aidp/analyze/json_file_storage.rb +0 -292
- data/lib/aidp/execute/future_work_backlog.rb +0 -411
- data/lib/aidp/providers/adapter.rb +0 -239
- data/lib/aidp/providers/aider.rb +0 -264
- data/lib/aidp/providers/anthropic.rb +0 -614
- data/lib/aidp/providers/base.rb +0 -617
- data/lib/aidp/providers/capability_registry.rb +0 -204
- data/lib/aidp/providers/codex.rb +0 -267
- data/lib/aidp/providers/cursor.rb +0 -304
- data/lib/aidp/providers/error_taxonomy.rb +0 -195
- data/lib/aidp/providers/gemini.rb +0 -110
- data/lib/aidp/providers/github_copilot.rb +0 -227
- data/lib/aidp/providers/kilocode.rb +0 -157
- data/lib/aidp/providers/opencode.rb +0 -139
data/lib/aidp/providers/base.rb
DELETED
|
@@ -1,617 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "tty-prompt"
|
|
4
|
-
require "tty-spinner"
|
|
5
|
-
require_relative "adapter"
|
|
6
|
-
|
|
7
|
-
module Aidp
|
|
8
|
-
module Providers
|
|
9
|
-
class ProviderUnavailableError < StandardError; end
|
|
10
|
-
|
|
11
|
-
class Base
|
|
12
|
-
include Aidp::MessageDisplay
|
|
13
|
-
include Aidp::Providers::Adapter
|
|
14
|
-
|
|
15
|
-
# Activity indicator states
|
|
16
|
-
ACTIVITY_STATES = {
|
|
17
|
-
idle: "⏳",
|
|
18
|
-
working: "🔄",
|
|
19
|
-
stuck: "⚠️",
|
|
20
|
-
completed: "✅",
|
|
21
|
-
failed: "❌"
|
|
22
|
-
}.freeze
|
|
23
|
-
|
|
24
|
-
# Default timeout for stuck detection (2 minutes)
|
|
25
|
-
DEFAULT_STUCK_TIMEOUT = 120
|
|
26
|
-
|
|
27
|
-
# Configurable timeout values (can be overridden via environment or config)
|
|
28
|
-
# These defaults provide reasonable values for different execution scenarios
|
|
29
|
-
TIMEOUT_QUICK_MODE = 120 # 2 minutes - for quick testing
|
|
30
|
-
TIMEOUT_DEFAULT = 300 # 5 minutes - standard interactive timeout
|
|
31
|
-
TIMEOUT_REPOSITORY_ANALYSIS = 180 # 3 minutes - repository analysis
|
|
32
|
-
TIMEOUT_ARCHITECTURE_ANALYSIS = 600 # 10 minutes - architecture analysis
|
|
33
|
-
TIMEOUT_TEST_ANALYSIS = 300 # 5 minutes - test analysis
|
|
34
|
-
TIMEOUT_FUNCTIONALITY_ANALYSIS = 600 # 10 minutes - functionality analysis
|
|
35
|
-
TIMEOUT_DOCUMENTATION_ANALYSIS = 300 # 5 minutes - documentation analysis
|
|
36
|
-
TIMEOUT_STATIC_ANALYSIS = 450 # 7.5 minutes - static analysis
|
|
37
|
-
TIMEOUT_REFACTORING_RECOMMENDATIONS = 600 # 10 minutes - refactoring
|
|
38
|
-
TIMEOUT_IMPLEMENTATION = 900 # 15 minutes - implementation (write files, run tests, fix issues)
|
|
39
|
-
|
|
40
|
-
# Tier-based timeout multipliers (applied on top of base timeouts)
|
|
41
|
-
# Higher tiers need more time for deeper reasoning
|
|
42
|
-
TIER_TIMEOUT_MULTIPLIERS = {
|
|
43
|
-
"mini" => 1.0, # 5 minutes default (300s base)
|
|
44
|
-
"standard" => 2.0, # 10 minutes default (600s base)
|
|
45
|
-
"thinking" => 6.0, # 30 minutes default (1800s base)
|
|
46
|
-
"pro" => 6.0, # 30 minutes default (1800s base)
|
|
47
|
-
"max" => 12.0 # 60 minutes default (3600s base)
|
|
48
|
-
}.freeze
|
|
49
|
-
|
|
50
|
-
attr_reader :activity_state, :last_activity_time, :start_time, :step_name, :model
|
|
51
|
-
# Expose for testability
|
|
52
|
-
attr_writer :harness_context
|
|
53
|
-
|
|
54
|
-
def initialize(output: nil, prompt: TTY::Prompt.new)
|
|
55
|
-
@activity_state = :idle
|
|
56
|
-
@last_activity_time = Time.now
|
|
57
|
-
@start_time = nil
|
|
58
|
-
@step_name = nil
|
|
59
|
-
@activity_callback = nil
|
|
60
|
-
@stuck_timeout = DEFAULT_STUCK_TIMEOUT
|
|
61
|
-
@output_count = 0
|
|
62
|
-
@last_output_time = Time.now
|
|
63
|
-
@job_context = nil
|
|
64
|
-
@harness_context = nil
|
|
65
|
-
@output = output
|
|
66
|
-
@prompt = prompt
|
|
67
|
-
@model = nil
|
|
68
|
-
@harness_metrics = {
|
|
69
|
-
total_requests: 0,
|
|
70
|
-
successful_requests: 0,
|
|
71
|
-
failed_requests: 0,
|
|
72
|
-
rate_limited_requests: 0,
|
|
73
|
-
total_tokens_used: 0,
|
|
74
|
-
total_cost: 0.0,
|
|
75
|
-
average_response_time: 0.0,
|
|
76
|
-
last_request_time: nil
|
|
77
|
-
}
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def name
|
|
81
|
-
raise NotImplementedError, "#{self.class} must implement #name"
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Human-friendly display name for UI
|
|
85
|
-
# Override in subclasses to provide a better display name
|
|
86
|
-
def display_name
|
|
87
|
-
name
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Configure the provider with options
|
|
91
|
-
# @param config [Hash] Configuration options, may include :model
|
|
92
|
-
def configure(config)
|
|
93
|
-
if config[:model]
|
|
94
|
-
@model = resolve_model_name(config[:model].to_s)
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def send_message(prompt:, session: nil, options: {})
|
|
99
|
-
raise NotImplementedError, "#{self.class} must implement #send_message"
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# Fetch MCP servers configured for this provider
|
|
103
|
-
# Returns an array of server hashes with keys: :name, :status, :description, :enabled
|
|
104
|
-
# Override in subclasses to provide provider-specific MCP server detection
|
|
105
|
-
def fetch_mcp_servers
|
|
106
|
-
[]
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Check if this provider supports MCP servers
|
|
110
|
-
# Override in subclasses to provide accurate MCP support detection
|
|
111
|
-
def supports_mcp?
|
|
112
|
-
false
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Set job context for background execution
|
|
116
|
-
def set_job_context(job_id:, execution_id:, job_manager:)
|
|
117
|
-
@job_context = {
|
|
118
|
-
job_id: job_id,
|
|
119
|
-
execution_id: execution_id,
|
|
120
|
-
job_manager: job_manager
|
|
121
|
-
}
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Set up activity monitoring for a step
|
|
125
|
-
def setup_activity_monitoring(step_name, activity_callback = nil, stuck_timeout = nil)
|
|
126
|
-
@step_name = step_name
|
|
127
|
-
@activity_callback = activity_callback
|
|
128
|
-
@stuck_timeout = stuck_timeout || DEFAULT_STUCK_TIMEOUT
|
|
129
|
-
@start_time = Time.now
|
|
130
|
-
@last_activity_time = @start_time
|
|
131
|
-
@output_count = 0
|
|
132
|
-
@last_output_time = @start_time
|
|
133
|
-
update_activity_state(:working)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Update activity state and notify callback
|
|
137
|
-
def update_activity_state(state, message = nil)
|
|
138
|
-
@activity_state = state
|
|
139
|
-
@last_activity_time = Time.now if state == :working
|
|
140
|
-
|
|
141
|
-
# Log state change to job if in background mode
|
|
142
|
-
if @job_context
|
|
143
|
-
level = case state
|
|
144
|
-
when :completed then "info"
|
|
145
|
-
when :failed then "error"
|
|
146
|
-
else "debug"
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
log_to_job(message || "Provider state changed to #{state}", level)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
@activity_callback&.call(state, message, self)
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Check if provider appears to be stuck
|
|
156
|
-
def stuck?
|
|
157
|
-
return false unless @activity_state == :working
|
|
158
|
-
|
|
159
|
-
time_since_activity = Time.now - @last_activity_time
|
|
160
|
-
time_since_activity > @stuck_timeout
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
# Get current execution time
|
|
164
|
-
def execution_time
|
|
165
|
-
return 0 unless @start_time
|
|
166
|
-
Time.now - @start_time
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
# Get time since last activity
|
|
170
|
-
def time_since_last_activity
|
|
171
|
-
Time.now - @last_activity_time
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
# Record activity (called when provider produces output)
|
|
175
|
-
def record_activity(message = nil)
|
|
176
|
-
@output_count += 1
|
|
177
|
-
@last_output_time = Time.now
|
|
178
|
-
update_activity_state(:working, message)
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
# Mark as completed
|
|
182
|
-
def mark_completed
|
|
183
|
-
update_activity_state(:completed)
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
# Mark as failed
|
|
187
|
-
def mark_failed(error_message = nil)
|
|
188
|
-
update_activity_state(:failed, error_message)
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
# Get activity summary for metrics
|
|
192
|
-
def activity_summary
|
|
193
|
-
{
|
|
194
|
-
provider: name,
|
|
195
|
-
step_name: @step_name,
|
|
196
|
-
start_time: @start_time&.iso8601,
|
|
197
|
-
end_time: Time.now.iso8601,
|
|
198
|
-
duration: execution_time,
|
|
199
|
-
final_state: @activity_state,
|
|
200
|
-
stuck_detected: stuck?,
|
|
201
|
-
output_count: @output_count
|
|
202
|
-
}
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
# Check if provider supports activity monitoring
|
|
206
|
-
def supports_activity_monitoring?
|
|
207
|
-
true # Default to true, override in subclasses if needed
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
# Get stuck timeout for this provider
|
|
211
|
-
attr_reader :stuck_timeout
|
|
212
|
-
|
|
213
|
-
# Harness integration methods
|
|
214
|
-
|
|
215
|
-
# Set harness context for provider
|
|
216
|
-
def set_harness_context(harness_runner)
|
|
217
|
-
@harness_context = harness_runner
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
# Check if provider is operating in harness mode
|
|
221
|
-
def harness_mode?
|
|
222
|
-
!@harness_context.nil?
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
# Get harness metrics
|
|
226
|
-
def harness_metrics
|
|
227
|
-
@harness_metrics.dup
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
# Record harness request metrics
|
|
231
|
-
def record_harness_request(success:, tokens_used: 0, cost: 0.0, response_time: 0.0, rate_limited: false)
|
|
232
|
-
@harness_metrics[:total_requests] += 1
|
|
233
|
-
@harness_metrics[:last_request_time] = Time.now
|
|
234
|
-
|
|
235
|
-
if success
|
|
236
|
-
@harness_metrics[:successful_requests] += 1
|
|
237
|
-
else
|
|
238
|
-
@harness_metrics[:failed_requests] += 1
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
if rate_limited
|
|
242
|
-
@harness_metrics[:rate_limited_requests] += 1
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
@harness_metrics[:total_tokens_used] += tokens_used
|
|
246
|
-
@harness_metrics[:total_cost] += cost
|
|
247
|
-
|
|
248
|
-
# Update average response time
|
|
249
|
-
total_time = @harness_metrics[:average_response_time] * (@harness_metrics[:total_requests] - 1) + response_time
|
|
250
|
-
@harness_metrics[:average_response_time] = total_time / @harness_metrics[:total_requests]
|
|
251
|
-
|
|
252
|
-
# Notify harness context if available
|
|
253
|
-
@harness_context&.record_provider_metrics(name, @harness_metrics)
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
# Get provider health status for harness
|
|
257
|
-
def harness_health_status
|
|
258
|
-
{
|
|
259
|
-
provider: name,
|
|
260
|
-
activity_state: @activity_state,
|
|
261
|
-
stuck: stuck?,
|
|
262
|
-
success_rate: calculate_success_rate,
|
|
263
|
-
average_response_time: @harness_metrics[:average_response_time],
|
|
264
|
-
total_requests: @harness_metrics[:total_requests],
|
|
265
|
-
rate_limit_ratio: calculate_rate_limit_ratio,
|
|
266
|
-
last_activity: @last_activity_time,
|
|
267
|
-
health_score: calculate_health_score
|
|
268
|
-
}
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
# Check if provider is healthy for harness use
|
|
272
|
-
def harness_healthy?
|
|
273
|
-
return false if stuck?
|
|
274
|
-
return false if @harness_metrics[:total_requests] > 0 && calculate_success_rate < 0.5
|
|
275
|
-
return false if calculate_rate_limit_ratio > 0.3
|
|
276
|
-
|
|
277
|
-
true
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
# Get provider configuration for harness
|
|
281
|
-
def harness_config
|
|
282
|
-
{
|
|
283
|
-
name: name,
|
|
284
|
-
supports_activity_monitoring: supports_activity_monitoring?,
|
|
285
|
-
default_timeout: @stuck_timeout,
|
|
286
|
-
available: available?,
|
|
287
|
-
health_status: harness_health_status
|
|
288
|
-
}
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
# Check if provider is available (override in subclasses)
|
|
292
|
-
def available?
|
|
293
|
-
true # Default to true, override in subclasses
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
# Enhanced send method that integrates with harness
|
|
297
|
-
def send_with_harness(prompt:, session: nil, _options: {})
|
|
298
|
-
start_time = Time.now
|
|
299
|
-
success = false
|
|
300
|
-
rate_limited = false
|
|
301
|
-
tokens_used = 0
|
|
302
|
-
cost = 0.0
|
|
303
|
-
error_message = nil
|
|
304
|
-
|
|
305
|
-
begin
|
|
306
|
-
# Call the original send_message method
|
|
307
|
-
result = send_message(prompt: prompt, session: session)
|
|
308
|
-
success = true
|
|
309
|
-
|
|
310
|
-
# Extract token usage and cost if available
|
|
311
|
-
if result.is_a?(Hash) && result[:token_usage]
|
|
312
|
-
tokens_used = result[:token_usage][:total] || 0
|
|
313
|
-
cost = result[:token_usage][:cost] || 0.0
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
# Check for rate limiting in result
|
|
317
|
-
if result.is_a?(Hash) && result[:rate_limited]
|
|
318
|
-
rate_limited = true
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
result
|
|
322
|
-
rescue => e
|
|
323
|
-
error_message = e.message
|
|
324
|
-
|
|
325
|
-
# Check if error is rate limiting
|
|
326
|
-
if e.message.match?(/rate.?limit/i) || e.message.match?(/quota/i) || e.message.match?(/session limit/i)
|
|
327
|
-
rate_limited = true
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
raise e
|
|
331
|
-
ensure
|
|
332
|
-
response_time = Time.now - start_time
|
|
333
|
-
record_harness_request(
|
|
334
|
-
success: success,
|
|
335
|
-
tokens_used: tokens_used,
|
|
336
|
-
cost: cost,
|
|
337
|
-
response_time: response_time,
|
|
338
|
-
rate_limited: rate_limited
|
|
339
|
-
)
|
|
340
|
-
|
|
341
|
-
# Log to harness context if available
|
|
342
|
-
if @harness_context && error_message
|
|
343
|
-
@harness_context.record_provider_error(name, error_message, rate_limited)
|
|
344
|
-
end
|
|
345
|
-
end
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
# Helper method for registry-based model discovery
|
|
349
|
-
#
|
|
350
|
-
# Providers that use the model registry can call this method to discover models
|
|
351
|
-
# based on a model family pattern.
|
|
352
|
-
#
|
|
353
|
-
# @param model_pattern [Regexp] Pattern to match model families
|
|
354
|
-
# @param provider_name [String] Name of the provider
|
|
355
|
-
# @return [Array<Hash>] Array of discovered models
|
|
356
|
-
def self.discover_models_from_registry(model_pattern, provider_name)
|
|
357
|
-
require_relative "../harness/model_registry"
|
|
358
|
-
registry = Aidp::Harness::ModelRegistry.new
|
|
359
|
-
|
|
360
|
-
# Get all models from registry that match the pattern
|
|
361
|
-
models = registry.all_families.filter_map do |family|
|
|
362
|
-
next unless model_pattern.match?(family)
|
|
363
|
-
|
|
364
|
-
info = registry.get_model_info(family)
|
|
365
|
-
next unless info
|
|
366
|
-
|
|
367
|
-
{
|
|
368
|
-
name: family,
|
|
369
|
-
family: family,
|
|
370
|
-
tier: info["tier"],
|
|
371
|
-
capabilities: info["capabilities"] || [],
|
|
372
|
-
context_window: info["context_window"],
|
|
373
|
-
provider: provider_name
|
|
374
|
-
}
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
Aidp.log_info("#{provider_name}_provider", "using registry models", count: models.size)
|
|
378
|
-
models
|
|
379
|
-
rescue => e
|
|
380
|
-
Aidp.log_debug("#{provider_name}_provider", "discovery failed", error: e.message)
|
|
381
|
-
[]
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
# Get firewall requirements for this provider
|
|
385
|
-
#
|
|
386
|
-
# Returns domains and IP ranges that need to be accessible for this provider
|
|
387
|
-
# to function properly. Used by devcontainer firewall configuration.
|
|
388
|
-
#
|
|
389
|
-
# @return [Hash] Firewall requirements with :domains and :ip_ranges keys
|
|
390
|
-
# - domains: Array of domain strings
|
|
391
|
-
# - ip_ranges: Array of CIDR strings
|
|
392
|
-
#
|
|
393
|
-
# Override in subclasses to provide provider-specific requirements
|
|
394
|
-
def self.firewall_requirements
|
|
395
|
-
{
|
|
396
|
-
domains: [],
|
|
397
|
-
ip_ranges: []
|
|
398
|
-
}
|
|
399
|
-
end
|
|
400
|
-
|
|
401
|
-
protected
|
|
402
|
-
|
|
403
|
-
# Log message to job if in background mode
|
|
404
|
-
def log_to_job(message, level = "info", metadata = {})
|
|
405
|
-
return unless @job_context && @job_context[:job_manager]
|
|
406
|
-
|
|
407
|
-
metadata = metadata.merge(
|
|
408
|
-
provider: name,
|
|
409
|
-
step_name: @step_name,
|
|
410
|
-
activity_state: @activity_state,
|
|
411
|
-
execution_time: execution_time,
|
|
412
|
-
output_count: @output_count
|
|
413
|
-
)
|
|
414
|
-
|
|
415
|
-
@job_context[:job_manager].log_message(
|
|
416
|
-
@job_context[:job_id],
|
|
417
|
-
@job_context[:execution_id],
|
|
418
|
-
message,
|
|
419
|
-
level,
|
|
420
|
-
metadata
|
|
421
|
-
)
|
|
422
|
-
end
|
|
423
|
-
|
|
424
|
-
# Calculate success rate for harness metrics
|
|
425
|
-
def calculate_success_rate
|
|
426
|
-
return 1.0 if @harness_metrics[:total_requests] == 0
|
|
427
|
-
@harness_metrics[:successful_requests].to_f / @harness_metrics[:total_requests]
|
|
428
|
-
end
|
|
429
|
-
|
|
430
|
-
# Calculate rate limit ratio for harness metrics
|
|
431
|
-
def calculate_rate_limit_ratio
|
|
432
|
-
return 0.0 if @harness_metrics[:total_requests] == 0
|
|
433
|
-
@harness_metrics[:rate_limited_requests].to_f / @harness_metrics[:total_requests]
|
|
434
|
-
end
|
|
435
|
-
|
|
436
|
-
# Calculate overall health score for harness
|
|
437
|
-
def calculate_health_score
|
|
438
|
-
return 100.0 if @harness_metrics[:total_requests] == 0
|
|
439
|
-
|
|
440
|
-
success_rate = calculate_success_rate
|
|
441
|
-
rate_limit_ratio = calculate_rate_limit_ratio
|
|
442
|
-
response_time_score = [100 - (@harness_metrics[:average_response_time] * 10), 0].max
|
|
443
|
-
|
|
444
|
-
# Weighted health score
|
|
445
|
-
(success_rate * 50) + ((1 - rate_limit_ratio) * 30) + (response_time_score * 0.2)
|
|
446
|
-
end
|
|
447
|
-
|
|
448
|
-
# Update spinner status with elapsed time
|
|
449
|
-
# This is a shared method used by all providers to display progress
|
|
450
|
-
def update_spinner_status(spinner, elapsed, provider_name)
|
|
451
|
-
minutes = (elapsed / 60).to_i
|
|
452
|
-
seconds = (elapsed % 60).to_i
|
|
453
|
-
|
|
454
|
-
if minutes > 0
|
|
455
|
-
spinner.update(title: "#{provider_name} is running... (#{minutes}m #{seconds}s)")
|
|
456
|
-
else
|
|
457
|
-
spinner.update(title: "#{provider_name} is running... (#{seconds}s)")
|
|
458
|
-
end
|
|
459
|
-
end
|
|
460
|
-
|
|
461
|
-
# Clean up activity display thread and spinner
|
|
462
|
-
# Used by providers to ensure proper cleanup in both success and error paths
|
|
463
|
-
def cleanup_activity_display(activity_display_thread, spinner)
|
|
464
|
-
activity_display_thread.kill if activity_display_thread&.alive?
|
|
465
|
-
activity_display_thread&.join(0.1) # Give it 100ms to finish
|
|
466
|
-
spinner&.stop
|
|
467
|
-
end
|
|
468
|
-
|
|
469
|
-
# Check if we should skip permissions based on devcontainer/codespace environment
|
|
470
|
-
# This enables providers to run with elevated permissions in safe development environments
|
|
471
|
-
# Returns true if running in a devcontainer or GitHub Codespace
|
|
472
|
-
def in_devcontainer_or_codespace?
|
|
473
|
-
ENV["REMOTE_CONTAINERS"] == "true" || ENV["CODESPACES"] == "true"
|
|
474
|
-
end
|
|
475
|
-
|
|
476
|
-
# Check if provider should skip sandbox permissions
|
|
477
|
-
# Providers can override this to add additional logic beyond environment detection
|
|
478
|
-
def should_skip_permissions?
|
|
479
|
-
# First, check for devcontainer/codespace environment (most reliable)
|
|
480
|
-
return true if in_devcontainer_or_codespace?
|
|
481
|
-
|
|
482
|
-
# Fallback: Check if harness context is available and has configuration
|
|
483
|
-
return false unless @harness_context
|
|
484
|
-
|
|
485
|
-
# Get configuration from harness
|
|
486
|
-
config = @harness_context.config
|
|
487
|
-
return false unless config
|
|
488
|
-
|
|
489
|
-
# Use configuration method to determine if full permissions should be used
|
|
490
|
-
# Provider subclasses should pass their provider name
|
|
491
|
-
false # Base implementation returns false, subclasses should override
|
|
492
|
-
end
|
|
493
|
-
|
|
494
|
-
# Calculate timeout for provider operations
|
|
495
|
-
#
|
|
496
|
-
# Priority order:
|
|
497
|
-
# 1. Quick mode (for testing)
|
|
498
|
-
# 2. Provider-specific environment variable override
|
|
499
|
-
# 3. Adaptive timeout based on step type and thinking tier
|
|
500
|
-
# 4. Default timeout (with tier multiplier)
|
|
501
|
-
#
|
|
502
|
-
# Override provider_env_var to customize the environment variable name
|
|
503
|
-
# @param options [Hash] Options hash that may include :tier
|
|
504
|
-
def calculate_timeout(options = {})
|
|
505
|
-
if ENV["AIDP_QUICK_MODE"]
|
|
506
|
-
display_message("⚡ Quick mode enabled - #{TIMEOUT_QUICK_MODE / 60} minute timeout", type: :highlight)
|
|
507
|
-
return TIMEOUT_QUICK_MODE
|
|
508
|
-
end
|
|
509
|
-
|
|
510
|
-
provider_env_var = "AIDP_#{name.upcase}_TIMEOUT"
|
|
511
|
-
return ENV[provider_env_var].to_i if ENV[provider_env_var]
|
|
512
|
-
|
|
513
|
-
tier = options[:tier]&.to_s
|
|
514
|
-
step_timeout = adaptive_timeout(tier)
|
|
515
|
-
if step_timeout
|
|
516
|
-
tier_label = tier ? " (tier: #{tier})" : ""
|
|
517
|
-
display_message("🧠 Using adaptive timeout: #{step_timeout} seconds#{tier_label}", type: :info)
|
|
518
|
-
return step_timeout
|
|
519
|
-
end
|
|
520
|
-
|
|
521
|
-
# Default timeout with tier multiplier
|
|
522
|
-
base_timeout = TIMEOUT_DEFAULT
|
|
523
|
-
final_timeout = apply_tier_multiplier(base_timeout, tier)
|
|
524
|
-
tier_label = tier ? " (tier: #{tier})" : ""
|
|
525
|
-
display_message("📋 Using default timeout: #{final_timeout / 60} minutes#{tier_label}", type: :info)
|
|
526
|
-
final_timeout
|
|
527
|
-
end
|
|
528
|
-
|
|
529
|
-
# Get adaptive timeout based on step type and thinking tier
|
|
530
|
-
#
|
|
531
|
-
# This method returns different timeout values based on the type of operation
|
|
532
|
-
# being performed, as indicated by the AIDP_CURRENT_STEP environment variable,
|
|
533
|
-
# and applies a multiplier based on the thinking tier (mini/standard/thinking/pro/max).
|
|
534
|
-
# Returns nil for unknown steps to allow calculate_timeout to use the default.
|
|
535
|
-
#
|
|
536
|
-
# @param tier [String, nil] The thinking tier (mini, standard, thinking, pro, max)
|
|
537
|
-
def adaptive_timeout(tier = nil)
|
|
538
|
-
# Don't cache - tier may change between calls
|
|
539
|
-
step_name = ENV["AIDP_CURRENT_STEP"] || ""
|
|
540
|
-
|
|
541
|
-
base_timeout = case step_name
|
|
542
|
-
when /REPOSITORY_ANALYSIS/
|
|
543
|
-
TIMEOUT_REPOSITORY_ANALYSIS
|
|
544
|
-
when /ARCHITECTURE_ANALYSIS/
|
|
545
|
-
TIMEOUT_ARCHITECTURE_ANALYSIS
|
|
546
|
-
when /TEST_ANALYSIS/
|
|
547
|
-
TIMEOUT_TEST_ANALYSIS
|
|
548
|
-
when /FUNCTIONALITY_ANALYSIS/
|
|
549
|
-
TIMEOUT_FUNCTIONALITY_ANALYSIS
|
|
550
|
-
when /DOCUMENTATION_ANALYSIS/
|
|
551
|
-
TIMEOUT_DOCUMENTATION_ANALYSIS
|
|
552
|
-
when /STATIC_ANALYSIS/
|
|
553
|
-
TIMEOUT_STATIC_ANALYSIS
|
|
554
|
-
when /REFACTORING_RECOMMENDATIONS/
|
|
555
|
-
TIMEOUT_REFACTORING_RECOMMENDATIONS
|
|
556
|
-
when /IMPLEMENTATION/
|
|
557
|
-
TIMEOUT_IMPLEMENTATION
|
|
558
|
-
else
|
|
559
|
-
nil # Return nil for unknown steps
|
|
560
|
-
end
|
|
561
|
-
|
|
562
|
-
return nil unless base_timeout
|
|
563
|
-
|
|
564
|
-
apply_tier_multiplier(base_timeout, tier)
|
|
565
|
-
end
|
|
566
|
-
|
|
567
|
-
# Apply tier-based multiplier to a base timeout
|
|
568
|
-
#
|
|
569
|
-
# @param base_timeout [Integer] The base timeout in seconds
|
|
570
|
-
# @param tier [String, nil] The thinking tier (mini, standard, thinking, pro, max)
|
|
571
|
-
# @return [Integer] The adjusted timeout with tier multiplier applied
|
|
572
|
-
def apply_tier_multiplier(base_timeout, tier)
|
|
573
|
-
return base_timeout unless tier
|
|
574
|
-
|
|
575
|
-
multiplier = TIER_TIMEOUT_MULTIPLIERS[tier.to_s] || 1.0
|
|
576
|
-
(base_timeout * multiplier).to_i
|
|
577
|
-
end
|
|
578
|
-
|
|
579
|
-
# Resolve a model name using the RubyLLM registry
|
|
580
|
-
#
|
|
581
|
-
# Attempts to resolve a model family name (e.g., "claude-3-5-haiku") to a
|
|
582
|
-
# versioned model name (e.g., "claude-3-5-haiku-20241022") using the RubyLLM
|
|
583
|
-
# registry. Falls back to using the name as-is if resolution fails.
|
|
584
|
-
#
|
|
585
|
-
# @param model_name [String] The model family name or versioned name
|
|
586
|
-
# @return [String] The resolved model name (versioned if found, original if not)
|
|
587
|
-
def resolve_model_name(model_name)
|
|
588
|
-
require_relative "../harness/ruby_llm_registry" unless defined?(Aidp::Harness::RubyLLMRegistry)
|
|
589
|
-
|
|
590
|
-
begin
|
|
591
|
-
registry = Aidp::Harness::RubyLLMRegistry.new
|
|
592
|
-
resolved = registry.resolve_model(model_name, provider: name)
|
|
593
|
-
|
|
594
|
-
if resolved
|
|
595
|
-
Aidp.log_debug(name, "Resolved model using registry",
|
|
596
|
-
requested: model_name,
|
|
597
|
-
resolved: resolved)
|
|
598
|
-
resolved
|
|
599
|
-
else
|
|
600
|
-
# Fall back to using the name as-is
|
|
601
|
-
Aidp.log_warn(name, "Model not found in registry, using as-is",
|
|
602
|
-
model: model_name)
|
|
603
|
-
model_name
|
|
604
|
-
end
|
|
605
|
-
rescue => e
|
|
606
|
-
# If registry fails, fall back to using the name as-is
|
|
607
|
-
Aidp.log_error(name, "Registry lookup failed, using model name as-is",
|
|
608
|
-
model: model_name,
|
|
609
|
-
error: e.message)
|
|
610
|
-
model_name
|
|
611
|
-
end
|
|
612
|
-
end
|
|
613
|
-
|
|
614
|
-
private
|
|
615
|
-
end
|
|
616
|
-
end
|
|
617
|
-
end
|