aidp 0.30.0 → 0.32.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dcf4f30f93195b349c527dd5531e056ad566a34be1ed079ced4b5002f07a93f5
4
- data.tar.gz: 22a7ef871ab691e0072a1bc8f1254784e1605f46d3edfc9585e91c3a21c5180f
3
+ metadata.gz: c7a02d5fb9cd901a6a74a2b402c5b9d70ea07f47727ddbd388993e9ede64c96b
4
+ data.tar.gz: fc92260f2b244295b98b5e2e2cdedf4872a18f94f5c3b8bf0031b6a4fc1f6554
5
5
  SHA512:
6
- metadata.gz: c5630eb8255464816006bb091b02666b29ac58b4a1e3e9136c17f79dff54762d6ebe16a4ec45df3f26c22047959121db04ef4e94904be5a4fb2e03320c6a6113
7
- data.tar.gz: 5bdae7edfb7aad8ef03dd2274c4a8095bd927097cbd882a28856f0f753b46c4f1f0d8f756801c6df239bd13540355cbf9e53805b3bc815e5ac1cfc407d61c1c9
6
+ metadata.gz: 673505d4036ad47cff7ff1b534e1798e893dcf7fc77f086ee1b6f46ba90b48de4b72333cf63b15db1fd0b16b2b0060c324fe6a1bdf371ae7ec796ce606b454e7
7
+ data.tar.gz: f99df6bea4120ec723a907e4f4a1cc15dc1c5c52abf3c4753bc5cdd24e6288a5207dca6ae6dc85a6afab442dc2b845de7d7794731f1bd27caeaf22c9e46f656f
data/lib/aidp/config.rb CHANGED
@@ -111,11 +111,10 @@ module Aidp
111
111
  model_family: "claude",
112
112
  max_tokens: 100_000,
113
113
  default_flags: ["--dangerously-skip-permissions"],
114
- models: ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022", "claude-3-opus-20240229"],
114
+ models: ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022"],
115
115
  model_weights: {
116
116
  "claude-3-5-sonnet-20241022" => 3,
117
- "claude-3-5-haiku-20241022" => 2,
118
- "claude-3-opus-20240229" => 1
117
+ "claude-3-5-haiku-20241022" => 2
119
118
  },
120
119
  models_config: {
121
120
  "claude-3-5-sonnet-20241022" => {
@@ -127,11 +126,6 @@ module Aidp
127
126
  flags: ["--dangerously-skip-permissions"],
128
127
  max_tokens: 200_000,
129
128
  timeout: 180
130
- },
131
- "claude-3-opus-20240229" => {
132
- flags: ["--dangerously-skip-permissions"],
133
- max_tokens: 200_000,
134
- timeout: 600
135
129
  }
136
130
  },
137
131
  auth: {
@@ -7,6 +7,7 @@ require_relative "guard_policy"
7
7
  require_relative "work_loop_unit_scheduler"
8
8
  require_relative "deterministic_unit"
9
9
  require_relative "agent_signal_parser"
10
+ require_relative "steps"
10
11
  require_relative "../harness/test_runner"
11
12
  require_relative "../errors"
12
13
 
@@ -81,6 +82,7 @@ module Aidp
81
82
 
82
83
  display_message("🔄 Starting hybrid work loop for step: #{step_name}", type: :info)
83
84
  display_message(" Flow: Deterministic ↔ Agentic with fix-forward core", type: :info)
85
+ display_work_context(step_name, context)
84
86
 
85
87
  display_guard_policy_status
86
88
  display_pending_tasks
@@ -1286,6 +1288,74 @@ module Aidp
1286
1288
  display_message("")
1287
1289
  end
1288
1290
 
1291
+ # Show watch-mode context (issue/PR, step position) to improve situational awareness
1292
+ def display_work_context(step_name, context)
1293
+ parts = work_context_parts(step_name, context)
1294
+ return if parts.empty?
1295
+
1296
+ Aidp.log_debug("work_loop", "work_context", step: step_name, parts: parts)
1297
+ display_message(" 📡 Context: #{parts.join(" | ")}", type: :info)
1298
+ end
1299
+
1300
+ def work_context_parts(step_name, context)
1301
+ ctx = (@options || {}).merge(context || {})
1302
+ parts = []
1303
+
1304
+ if (step_label = step_position_label(step_name, ctx))
1305
+ parts << step_label
1306
+ end
1307
+
1308
+ if (issue_label = issue_context_label(ctx))
1309
+ parts << issue_label
1310
+ end
1311
+
1312
+ if (pr_label = pr_context_label(ctx))
1313
+ parts << pr_label
1314
+ end
1315
+
1316
+ parts << "Watch mode" if ctx[:workflow_type].to_s == "watch_mode"
1317
+
1318
+ parts.compact
1319
+ end
1320
+
1321
+ def step_position_label(step_name, context)
1322
+ steps = Array(context[:selected_steps]).map(&:to_s)
1323
+ steps = Aidp::Execute::Steps::SPEC.keys if steps.empty?
1324
+ steps = [step_name] if steps.empty?
1325
+ steps << step_name unless steps.include?(step_name)
1326
+
1327
+ index = steps.index(step_name)
1328
+ return nil unless index
1329
+
1330
+ "Step #{index + 1}/#{steps.size} (#{step_name})"
1331
+ end
1332
+
1333
+ def issue_context_label(context)
1334
+ issue_number = context[:issue_number] ||
1335
+ context.dig(:issue, :number) ||
1336
+ extract_number_from_url(context[:issue_url] || context.dig(:issue, :url) || context.dig(:user_input, "Issue URL"), /issues\/(\d+)/)
1337
+
1338
+ return nil unless issue_number
1339
+
1340
+ "Issue ##{issue_number}"
1341
+ end
1342
+
1343
+ def pr_context_label(context)
1344
+ pr_number = context[:pr_number] ||
1345
+ context.dig(:pull_request, :number) ||
1346
+ extract_number_from_url(context[:pr_url] || context.dig(:pull_request, :url) || context.dig(:user_input, "PR URL") || context.dig(:user_input, "Pull Request URL"), /pull\/(\d+)/)
1347
+
1348
+ return nil unless pr_number
1349
+
1350
+ "PR ##{pr_number}"
1351
+ end
1352
+
1353
+ def extract_number_from_url(url, pattern)
1354
+ return nil unless url
1355
+ match = url.to_s.match(pattern)
1356
+ match && match[1]
1357
+ end
1358
+
1289
1359
  # Append task completion requirement to PROMPT.md
1290
1360
  def append_task_requirement_to_prompt(message)
1291
1361
  task_requirement = []
@@ -1057,7 +1057,7 @@ module Aidp
1057
1057
  def default_thinking_config
1058
1058
  {
1059
1059
  default_tier: "mini", # Use mini tier by default for cost optimization
1060
- max_tier: "max",
1060
+ max_tier: "pro", # Max tier rarely needed; pro is sufficient for most tasks
1061
1061
  allow_provider_switch: true,
1062
1062
  auto_escalate: true,
1063
1063
  escalation_threshold: 2,
@@ -38,6 +38,7 @@ module Aidp
38
38
  @model_fallback_chains = {}
39
39
  @model_switching_enabled = true
40
40
  @model_weights = {}
41
+ @model_denylist = Hash.new { |h, k| h[k] = [] }
41
42
  @unavailable_cache = {}
42
43
  @binary_check_cache = {}
43
44
  @binary_check_ttl = 300 # seconds
@@ -387,10 +388,30 @@ module Aidp
387
388
  # Check if model is configured for provider
388
389
  return false unless model_configured?(provider_name, model_name)
389
390
 
391
+ # Skip models that were explicitly denied (e.g., unsupported by provider)
392
+ return false if model_denied?(provider_name, model_name)
393
+
390
394
  # Check if model is not rate limited
391
395
  !is_model_rate_limited?(provider_name, model_name)
392
396
  end
393
397
 
398
+ # Check if a model has been denylisted for a provider
399
+ def model_denied?(provider_name, model_name)
400
+ @model_denylist[provider_name]&.include?(model_name)
401
+ end
402
+
403
+ # Add a model to the denylist for a provider (e.g., unsupported model errors)
404
+ def deny_model(provider_name, model_name, error: nil)
405
+ return if provider_name.nil? || model_name.nil?
406
+ return if model_denied?(provider_name, model_name)
407
+
408
+ @model_denylist[provider_name] << model_name
409
+ Aidp.log_debug("provider_manager", "model_denylisted",
410
+ provider: provider_name,
411
+ model: model_name,
412
+ error: error&.message)
413
+ end
414
+
394
415
  # Check if model is configured for provider
395
416
  def model_configured?(provider_name, model_name)
396
417
  models = provider_models(provider_name)
@@ -1279,6 +1300,7 @@ module Aidp
1279
1300
  @model_health.clear
1280
1301
  @model_metrics.clear
1281
1302
  @model_fallback_chains.clear
1303
+ @model_denylist.clear
1282
1304
  @model_rate_limit_info&.clear
1283
1305
  @model_history&.clear
1284
1306
  initialize_fallback_chains
@@ -1394,9 +1416,9 @@ module Aidp
1394
1416
 
1395
1417
  # Execute a prompt with a specific provider
1396
1418
  def execute_with_provider(provider_type, prompt, options = {})
1397
- # Extract model and tier from options if provided
1419
+ # Extract model from options if provided
1398
1420
  model_name = options.delete(:model)
1399
- tier = options[:tier] # Keep tier in options for provider
1421
+ retry_on_rate_limit = options.delete(:retry_on_rate_limit) != false # Default true
1400
1422
 
1401
1423
  # Create provider factory instance
1402
1424
  provider_factory = ProviderFactory.new
@@ -1415,11 +1437,10 @@ module Aidp
1415
1437
  Aidp.logger.debug("provider_manager", "Executing with provider",
1416
1438
  provider: provider_type,
1417
1439
  model: model_name,
1418
- tier: tier,
1419
1440
  prompt_length: prompt.length)
1420
1441
 
1421
- # Execute the prompt with the provider (pass options including tier)
1422
- result = provider.send_message(prompt: prompt, session: nil, options: options)
1442
+ # Execute the prompt with the provider
1443
+ result = provider.send_message(prompt: prompt, session: nil)
1423
1444
 
1424
1445
  # Return structured result
1425
1446
  {
@@ -1436,6 +1457,42 @@ module Aidp
1436
1457
  }
1437
1458
  rescue => e
1438
1459
  log_rescue(e, component: "provider_manager", action: "execute_with_provider", fallback: "error_result", provider: provider_type, model: model_name, prompt_length: prompt.length)
1460
+
1461
+ if unsupported_model_error?(e, model_name)
1462
+ deny_model(provider_type, model_name, error: e)
1463
+ end
1464
+
1465
+ # Detect rate limit / quota errors and attempt fallback
1466
+ error_message = e.message.to_s.downcase
1467
+ is_rate_limit = error_message.include?("rate limit") ||
1468
+ error_message.include?("quota") ||
1469
+ error_message.include?("limit reached") ||
1470
+ error_message.include?("resource exhausted") ||
1471
+ error_message.include?("too many requests")
1472
+
1473
+ if is_rate_limit && retry_on_rate_limit
1474
+ Aidp.logger.warn("provider_manager", "Rate limit detected, attempting fallback",
1475
+ provider: provider_type,
1476
+ model: model_name,
1477
+ error: e.message)
1478
+
1479
+ # Attempt to switch to fallback provider
1480
+ fallback_provider = switch_provider_for_error("rate_limit", {
1481
+ original_provider: provider_type,
1482
+ model: model_name,
1483
+ error_message: e.message
1484
+ })
1485
+
1486
+ if fallback_provider && fallback_provider != provider_type
1487
+ Aidp.logger.info("provider_manager", "Retrying with fallback provider",
1488
+ original: provider_type,
1489
+ fallback: fallback_provider)
1490
+
1491
+ # Retry with fallback provider (disable retry to prevent infinite loop)
1492
+ return execute_with_provider(fallback_provider, prompt, options.merge(retry_on_rate_limit: false))
1493
+ end
1494
+ end
1495
+
1439
1496
  # Return error result
1440
1497
  {
1441
1498
  status: "error",
@@ -1552,6 +1609,18 @@ module Aidp
1552
1609
  ((order[a] || 0) >= (order[b] || 0)) ? a : b
1553
1610
  end
1554
1611
 
1612
+ # Detect unsupported/invalid model errors to avoid reusing the model
1613
+ def unsupported_model_error?(error, model_name)
1614
+ return false if model_name.nil?
1615
+
1616
+ message = error&.message.to_s.downcase
1617
+ return false if message.empty?
1618
+
1619
+ (message.include?("unsupported") && message.include?("model")) ||
1620
+ (message.include?("model") && message.include?("not supported")) ||
1621
+ message.include?("invalid model")
1622
+ end
1623
+
1555
1624
  public
1556
1625
 
1557
1626
  # Log provider switch
@@ -3,6 +3,7 @@
3
3
  require "yaml"
4
4
  require "fileutils"
5
5
  require_relative "../rescue_logging"
6
+ require_relative "../util"
6
7
 
7
8
  module Aidp
8
9
  module Harness
@@ -14,9 +15,10 @@ module Aidp
14
15
  attr_reader :project_dir, :metrics_file, :rate_limit_file
15
16
 
16
17
  def initialize(project_dir)
17
- @project_dir = project_dir
18
- @metrics_file = File.join(project_dir, ".aidp", "provider_metrics.yml")
19
- @rate_limit_file = File.join(project_dir, ".aidp", "provider_rate_limits.yml")
18
+ # Store metrics at the repository root so different worktrees/modes share state
19
+ @project_dir = Aidp::Util.find_project_root(project_dir)
20
+ @metrics_file = File.join(@project_dir, ".aidp", "provider_metrics.yml")
21
+ @rate_limit_file = File.join(@project_dir, ".aidp", "provider_rate_limits.yml")
20
22
  ensure_directory
21
23
  end
22
24