Class: Aidp::Providers::Base

Inherits:
Object
  • Object
show all
Includes:
MessageDisplay, Adapter
Defined in:
lib/aidp/providers/base.rb

Direct Known Subclasses

Anthropic, Codex, Cursor, Gemini, GithubCopilot, Kilocode, Opencode

Constant Summary collapse

ACTIVITY_STATES =

Activity indicator states

{
  idle: "",
  working: "🔄",
  stuck: "⚠️",
  completed: "",
  failed: ""
}.freeze
DEFAULT_STUCK_TIMEOUT =

Default timeout for stuck detection (2 minutes)

120
TIMEOUT_QUICK_MODE =

Configurable timeout values (can be overridden via environment or config) These defaults provide reasonable values for different execution scenarios

120
TIMEOUT_DEFAULT =

2 minutes - for quick testing

300
TIMEOUT_REPOSITORY_ANALYSIS =

5 minutes - standard interactive timeout

180
TIMEOUT_ARCHITECTURE_ANALYSIS =

3 minutes - repository analysis

600
TIMEOUT_TEST_ANALYSIS =

10 minutes - architecture analysis

300
TIMEOUT_FUNCTIONALITY_ANALYSIS =

5 minutes - test analysis

600
TIMEOUT_DOCUMENTATION_ANALYSIS =

10 minutes - functionality analysis

300
TIMEOUT_STATIC_ANALYSIS =

5 minutes - documentation analysis

450
TIMEOUT_REFACTORING_RECOMMENDATIONS =

7.5 minutes - static analysis

600
TIMEOUT_IMPLEMENTATION =

10 minutes - refactoring

900

Constants included from MessageDisplay

MessageDisplay::COLOR_MAP

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Adapter

#capabilities, #classify_error, #dangerous_mode=, #dangerous_mode_enabled?, #dangerous_mode_flags, #error_metadata, #error_patterns, #health_status, #logging_metadata, #redact_secrets, #retryable_error?, #supports_dangerous_mode?, #validate_config

Methods included from MessageDisplay

#display_message, #in_test_environment?, included, #message_display_prompt, #suppress_display_message?

Constructor Details

#initialize(output: nil, prompt: TTY::Prompt.new) ⇒ Base

Returns a new instance of Base.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/aidp/providers/base.rb', line 42

def initialize(output: nil, prompt: TTY::Prompt.new)
  @activity_state = :idle
  @last_activity_time = Time.now
  @start_time = nil
  @step_name = nil
  @activity_callback = nil
  @stuck_timeout = DEFAULT_STUCK_TIMEOUT
  @output_count = 0
  @last_output_time = Time.now
  @job_context = nil
  @harness_context = nil
  @output = output
  @prompt = prompt
  @harness_metrics = {
    total_requests: 0,
    successful_requests: 0,
    failed_requests: 0,
    rate_limited_requests: 0,
    total_tokens_used: 0,
    total_cost: 0.0,
    average_response_time: 0.0,
    last_request_time: nil
  }
end

Instance Attribute Details

#activity_stateObject (readonly)

15 minutes - implementation (write files, run tests, fix issues)



40
41
42
# File 'lib/aidp/providers/base.rb', line 40

def activity_state
  @activity_state
end

#last_activity_timeObject (readonly)

15 minutes - implementation (write files, run tests, fix issues)



40
41
42
# File 'lib/aidp/providers/base.rb', line 40

def last_activity_time
  @last_activity_time
end

#start_timeObject (readonly)

15 minutes - implementation (write files, run tests, fix issues)



40
41
42
# File 'lib/aidp/providers/base.rb', line 40

def start_time
  @start_time
end

#step_nameObject (readonly)

15 minutes - implementation (write files, run tests, fix issues)



40
41
42
# File 'lib/aidp/providers/base.rb', line 40

def step_name
  @step_name
end

#stuck_timeoutObject (readonly)

Get stuck timeout for this provider



190
191
192
# File 'lib/aidp/providers/base.rb', line 190

def stuck_timeout
  @stuck_timeout
end

Instance Method Details

#activity_summaryObject

Get activity summary for metrics



171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/aidp/providers/base.rb', line 171

def activity_summary
  {
    provider: name,
    step_name: @step_name,
    start_time: @start_time&.iso8601,
    end_time: Time.now.iso8601,
    duration: execution_time,
    final_state: @activity_state,
    stuck_detected: stuck?,
    output_count: @output_count
  }
end

#available?Boolean

Check if provider is available (override in subclasses)

Returns:

  • (Boolean)


271
272
273
# File 'lib/aidp/providers/base.rb', line 271

def available?
  true # Default to true, override in subclasses
end

#display_nameObject

Human-friendly display name for UI Override in subclasses to provide a better display name



73
74
75
# File 'lib/aidp/providers/base.rb', line 73

def display_name
  name
end

#execution_timeObject

Get current execution time



143
144
145
146
# File 'lib/aidp/providers/base.rb', line 143

def execution_time
  return 0 unless @start_time
  Time.now - @start_time
end

#fetch_mcp_serversObject

Fetch MCP servers configured for this provider Returns an array of server hashes with keys: :name, :status, :description, :enabled Override in subclasses to provide provider-specific MCP server detection



84
85
86
# File 'lib/aidp/providers/base.rb', line 84

def fetch_mcp_servers
  []
end

#harness_configObject

Get provider configuration for harness



260
261
262
263
264
265
266
267
268
# File 'lib/aidp/providers/base.rb', line 260

def harness_config
  {
    name: name,
    supports_activity_monitoring: supports_activity_monitoring?,
    default_timeout: @stuck_timeout,
    available: available?,
    health_status: harness_health_status
  }
end

#harness_health_statusObject

Get provider health status for harness



236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/aidp/providers/base.rb', line 236

def harness_health_status
  {
    provider: name,
    activity_state: @activity_state,
    stuck: stuck?,
    success_rate: calculate_success_rate,
    average_response_time: @harness_metrics[:average_response_time],
    total_requests: @harness_metrics[:total_requests],
    rate_limit_ratio: calculate_rate_limit_ratio,
    last_activity: @last_activity_time,
    health_score: calculate_health_score
  }
end

#harness_healthy?Boolean

Check if provider is healthy for harness use

Returns:

  • (Boolean)


251
252
253
254
255
256
257
# File 'lib/aidp/providers/base.rb', line 251

def harness_healthy?
  return false if stuck?
  return false if @harness_metrics[:total_requests] > 0 && calculate_success_rate < 0.5
  return false if calculate_rate_limit_ratio > 0.3

  true
end

#harness_metricsObject

Get harness metrics



205
206
207
# File 'lib/aidp/providers/base.rb', line 205

def harness_metrics
  @harness_metrics.dup
end

#harness_mode?Boolean

Check if provider is operating in harness mode

Returns:

  • (Boolean)


200
201
202
# File 'lib/aidp/providers/base.rb', line 200

def harness_mode?
  !@harness_context.nil?
end

#mark_completedObject

Mark as completed



161
162
163
# File 'lib/aidp/providers/base.rb', line 161

def mark_completed
  update_activity_state(:completed)
end

#mark_failed(error_message = nil) ⇒ Object

Mark as failed



166
167
168
# File 'lib/aidp/providers/base.rb', line 166

def mark_failed(error_message = nil)
  update_activity_state(:failed, error_message)
end

#nameObject

Raises:

  • (NotImplementedError)


67
68
69
# File 'lib/aidp/providers/base.rb', line 67

def name
  raise NotImplementedError, "#{self.class} must implement #name"
end

#record_activity(message = nil) ⇒ Object

Record activity (called when provider produces output)



154
155
156
157
158
# File 'lib/aidp/providers/base.rb', line 154

def record_activity(message = nil)
  @output_count += 1
  @last_output_time = Time.now
  update_activity_state(:working, message)
end

#record_harness_request(success:, tokens_used: 0, cost: 0.0, response_time: 0.0, rate_limited: false) ⇒ Object

Record harness request metrics



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/aidp/providers/base.rb', line 210

def record_harness_request(success:, tokens_used: 0, cost: 0.0, response_time: 0.0, rate_limited: false)
  @harness_metrics[:total_requests] += 1
  @harness_metrics[:last_request_time] = Time.now

  if success
    @harness_metrics[:successful_requests] += 1
  else
    @harness_metrics[:failed_requests] += 1
  end

  if rate_limited
    @harness_metrics[:rate_limited_requests] += 1
  end

  @harness_metrics[:total_tokens_used] += tokens_used
  @harness_metrics[:total_cost] += cost

  # Update average response time
  total_time = @harness_metrics[:average_response_time] * (@harness_metrics[:total_requests] - 1) + response_time
  @harness_metrics[:average_response_time] = total_time / @harness_metrics[:total_requests]

  # Notify harness context if available
  @harness_context&.record_provider_metrics(name, @harness_metrics)
end

#send_message(prompt:, session: nil) ⇒ Object

Raises:

  • (NotImplementedError)


77
78
79
# File 'lib/aidp/providers/base.rb', line 77

def send_message(prompt:, session: nil)
  raise NotImplementedError, "#{self.class} must implement #send_message"
end

#send_with_harness(prompt:, session: nil, _options: {}) ⇒ Object

Enhanced send method that integrates with harness



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/aidp/providers/base.rb', line 276

def send_with_harness(prompt:, session: nil, _options: {})
  start_time = Time.now
  success = false
  rate_limited = false
  tokens_used = 0
  cost = 0.0
  error_message = nil

  begin
    # Call the original send_message method
    result = send_message(prompt: prompt, session: session)
    success = true

    # Extract token usage and cost if available
    if result.is_a?(Hash) && result[:token_usage]
      tokens_used = result[:token_usage][:total] || 0
      cost = result[:token_usage][:cost] || 0.0
    end

    # Check for rate limiting in result
    if result.is_a?(Hash) && result[:rate_limited]
      rate_limited = true
    end

    result
  rescue => e
    error_message = e.message

    # Check if error is rate limiting
    if e.message.match?(/rate.?limit/i) || e.message.match?(/quota/i) || e.message.match?(/session limit/i)
      rate_limited = true
    end

    raise e
  ensure
    response_time = Time.now - start_time
    record_harness_request(
      success: success,
      tokens_used: tokens_used,
      cost: cost,
      response_time: response_time,
      rate_limited: rate_limited
    )

    # Log to harness context if available
    if @harness_context && error_message
      @harness_context.record_provider_error(name, error_message, rate_limited)
    end
  end
end

#set_harness_context(harness_runner) ⇒ Object

Set harness context for provider



195
196
197
# File 'lib/aidp/providers/base.rb', line 195

def set_harness_context(harness_runner)
  @harness_context = harness_runner
end

#set_job_context(job_id:, execution_id:, job_manager:) ⇒ Object

Set job context for background execution



95
96
97
98
99
100
101
# File 'lib/aidp/providers/base.rb', line 95

def set_job_context(job_id:, execution_id:, job_manager:)
  @job_context = {
    job_id: job_id,
    execution_id: execution_id,
    job_manager: job_manager
  }
end

#setup_activity_monitoring(step_name, activity_callback = nil, stuck_timeout = nil) ⇒ Object

Set up activity monitoring for a step



104
105
106
107
108
109
110
111
112
113
# File 'lib/aidp/providers/base.rb', line 104

def setup_activity_monitoring(step_name, activity_callback = nil, stuck_timeout = nil)
  @step_name = step_name
  @activity_callback = activity_callback
  @stuck_timeout = stuck_timeout || DEFAULT_STUCK_TIMEOUT
  @start_time = Time.now
  @last_activity_time = @start_time
  @output_count = 0
  @last_output_time = @start_time
  update_activity_state(:working)
end

#stuck?Boolean

Check if provider appears to be stuck

Returns:

  • (Boolean)


135
136
137
138
139
140
# File 'lib/aidp/providers/base.rb', line 135

def stuck?
  return false unless @activity_state == :working

  time_since_activity = Time.now - @last_activity_time
  time_since_activity > @stuck_timeout
end

#supports_activity_monitoring?Boolean

Check if provider supports activity monitoring

Returns:

  • (Boolean)


185
186
187
# File 'lib/aidp/providers/base.rb', line 185

def supports_activity_monitoring?
  true # Default to true, override in subclasses if needed
end

#supports_mcp?Boolean

Check if this provider supports MCP servers Override in subclasses to provide accurate MCP support detection

Returns:

  • (Boolean)


90
91
92
# File 'lib/aidp/providers/base.rb', line 90

def supports_mcp?
  false
end

#time_since_last_activityObject

Get time since last activity



149
150
151
# File 'lib/aidp/providers/base.rb', line 149

def time_since_last_activity
  Time.now - @last_activity_time
end

#update_activity_state(state, message = nil) ⇒ Object

Update activity state and notify callback



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/aidp/providers/base.rb', line 116

def update_activity_state(state, message = nil)
  @activity_state = state
  @last_activity_time = Time.now if state == :working

  # Log state change to job if in background mode
  if @job_context
    level = case state
    when :completed then "info"
    when :failed then "error"
    else "debug"
    end

    log_to_job(message || "Provider state changed to #{state}", level)
  end

  @activity_callback&.call(state, message, self)
end