aidp 0.23.0 → 0.24.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.rb +3 -0
- data/lib/aidp/execute/work_loop_runner.rb +252 -45
- data/lib/aidp/execute/work_loop_unit_scheduler.rb +27 -2
- data/lib/aidp/harness/condition_detector.rb +42 -8
- data/lib/aidp/harness/config_manager.rb +7 -0
- data/lib/aidp/harness/config_schema.rb +25 -0
- data/lib/aidp/harness/configuration.rb +69 -6
- data/lib/aidp/harness/error_handler.rb +117 -44
- data/lib/aidp/harness/provider_manager.rb +64 -0
- data/lib/aidp/harness/provider_metrics.rb +138 -0
- data/lib/aidp/harness/runner.rb +90 -29
- data/lib/aidp/harness/simple_user_interface.rb +4 -0
- data/lib/aidp/harness/state/ui_state.rb +0 -10
- data/lib/aidp/harness/state_manager.rb +1 -15
- data/lib/aidp/harness/test_runner.rb +39 -2
- data/lib/aidp/logger.rb +34 -4
- data/lib/aidp/providers/adapter.rb +241 -0
- data/lib/aidp/providers/anthropic.rb +75 -7
- data/lib/aidp/providers/base.rb +29 -1
- data/lib/aidp/providers/capability_registry.rb +205 -0
- data/lib/aidp/providers/codex.rb +14 -0
- data/lib/aidp/providers/error_taxonomy.rb +195 -0
- data/lib/aidp/providers/gemini.rb +3 -2
- data/lib/aidp/setup/provider_registry.rb +107 -0
- data/lib/aidp/setup/wizard.rb +115 -31
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +263 -23
- data/lib/aidp/watch/repository_client.rb +4 -4
- data/lib/aidp/watch/runner.rb +37 -5
- data/lib/aidp/workflows/guided_agent.rb +53 -0
- data/lib/aidp/worktree.rb +67 -10
- data/templates/work_loop/decide_whats_next.md +21 -0
- data/templates/work_loop/diagnose_failures.md +21 -0
- metadata +10 -3
- /data/{bin → exe}/aidp +0 -0
|
@@ -44,6 +44,68 @@ module Aidp
|
|
|
44
44
|
self.class.available?
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
# ProviderAdapter interface methods
|
|
48
|
+
|
|
49
|
+
def capabilities
|
|
50
|
+
{
|
|
51
|
+
reasoning_tiers: ["mini", "standard", "thinking"],
|
|
52
|
+
context_window: 200_000,
|
|
53
|
+
supports_json_mode: true,
|
|
54
|
+
supports_tool_use: true,
|
|
55
|
+
supports_vision: false,
|
|
56
|
+
supports_file_upload: true,
|
|
57
|
+
streaming: true
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def supports_dangerous_mode?
|
|
62
|
+
true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def dangerous_mode_flags
|
|
66
|
+
["--dangerously-skip-permissions"]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def error_patterns
|
|
70
|
+
{
|
|
71
|
+
rate_limited: [
|
|
72
|
+
/rate.?limit/i,
|
|
73
|
+
/too.?many.?requests/i,
|
|
74
|
+
/429/,
|
|
75
|
+
/overloaded/i
|
|
76
|
+
],
|
|
77
|
+
auth_expired: [
|
|
78
|
+
/oauth.*token.*expired/i,
|
|
79
|
+
/authentication.*error/i,
|
|
80
|
+
/invalid.*api.*key/i,
|
|
81
|
+
/unauthorized/i,
|
|
82
|
+
/401/
|
|
83
|
+
],
|
|
84
|
+
quota_exceeded: [
|
|
85
|
+
/quota.*exceeded/i,
|
|
86
|
+
/usage.*limit/i,
|
|
87
|
+
/credit.*exhausted/i
|
|
88
|
+
],
|
|
89
|
+
transient: [
|
|
90
|
+
/timeout/i,
|
|
91
|
+
/connection.*reset/i,
|
|
92
|
+
/temporary.*error/i,
|
|
93
|
+
/service.*unavailable/i,
|
|
94
|
+
/503/,
|
|
95
|
+
/502/,
|
|
96
|
+
/504/
|
|
97
|
+
],
|
|
98
|
+
permanent: [
|
|
99
|
+
/invalid.*model/i,
|
|
100
|
+
/unsupported.*operation/i,
|
|
101
|
+
/not.*found/i,
|
|
102
|
+
/404/,
|
|
103
|
+
/bad.*request/i,
|
|
104
|
+
/400/
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
end
|
|
108
|
+
|
|
47
109
|
def send_message(prompt:, session: nil)
|
|
48
110
|
raise "claude CLI not available" unless self.class.available?
|
|
49
111
|
|
|
@@ -156,6 +218,8 @@ module Aidp
|
|
|
156
218
|
TIMEOUT_STATIC_ANALYSIS
|
|
157
219
|
when /REFACTORING_RECOMMENDATIONS/
|
|
158
220
|
TIMEOUT_REFACTORING_RECOMMENDATIONS
|
|
221
|
+
when /IMPLEMENTATION/
|
|
222
|
+
TIMEOUT_IMPLEMENTATION
|
|
159
223
|
else
|
|
160
224
|
nil # Use default
|
|
161
225
|
end
|
|
@@ -163,16 +227,20 @@ module Aidp
|
|
|
163
227
|
end
|
|
164
228
|
|
|
165
229
|
# Check if we should skip permissions based on devcontainer configuration
|
|
230
|
+
# Overrides base class to add logging and Claude-specific config check
|
|
166
231
|
def should_skip_permissions?
|
|
167
|
-
#
|
|
168
|
-
|
|
232
|
+
# Use base class devcontainer detection
|
|
233
|
+
if in_devcontainer_or_codespace?
|
|
234
|
+
debug_log("🔓 Detected devcontainer/codespace environment - enabling full permissions", level: :info)
|
|
235
|
+
return true
|
|
236
|
+
end
|
|
169
237
|
|
|
170
|
-
#
|
|
171
|
-
|
|
172
|
-
|
|
238
|
+
# Fallback: Check harness context for Claude-specific configuration
|
|
239
|
+
if @harness_context&.config&.respond_to?(:should_use_full_permissions?)
|
|
240
|
+
return @harness_context.config.should_use_full_permissions?("claude")
|
|
241
|
+
end
|
|
173
242
|
|
|
174
|
-
|
|
175
|
-
config.should_use_full_permissions?("claude")
|
|
243
|
+
false
|
|
176
244
|
end
|
|
177
245
|
|
|
178
246
|
# Parse stream-json output from Claude CLI
|
data/lib/aidp/providers/base.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "tty-prompt"
|
|
4
4
|
require "tty-spinner"
|
|
5
|
+
require_relative "adapter"
|
|
5
6
|
|
|
6
7
|
module Aidp
|
|
7
8
|
module Providers
|
|
@@ -9,6 +10,7 @@ module Aidp
|
|
|
9
10
|
|
|
10
11
|
class Base
|
|
11
12
|
include Aidp::MessageDisplay
|
|
13
|
+
include Aidp::Providers::Adapter
|
|
12
14
|
|
|
13
15
|
# Activity indicator states
|
|
14
16
|
ACTIVITY_STATES = {
|
|
@@ -33,6 +35,7 @@ module Aidp
|
|
|
33
35
|
TIMEOUT_DOCUMENTATION_ANALYSIS = 300 # 5 minutes - documentation analysis
|
|
34
36
|
TIMEOUT_STATIC_ANALYSIS = 450 # 7.5 minutes - static analysis
|
|
35
37
|
TIMEOUT_REFACTORING_RECOMMENDATIONS = 600 # 10 minutes - refactoring
|
|
38
|
+
TIMEOUT_IMPLEMENTATION = 900 # 15 minutes - implementation (write files, run tests, fix issues)
|
|
36
39
|
|
|
37
40
|
attr_reader :activity_state, :last_activity_time, :start_time, :step_name
|
|
38
41
|
|
|
@@ -299,7 +302,7 @@ module Aidp
|
|
|
299
302
|
error_message = e.message
|
|
300
303
|
|
|
301
304
|
# Check if error is rate limiting
|
|
302
|
-
if e.message.match?(/rate.?limit/i) || e.message.match?(/quota/i)
|
|
305
|
+
if e.message.match?(/rate.?limit/i) || e.message.match?(/quota/i) || e.message.match?(/session limit/i)
|
|
303
306
|
rate_limited = true
|
|
304
307
|
end
|
|
305
308
|
|
|
@@ -391,6 +394,31 @@ module Aidp
|
|
|
391
394
|
spinner&.stop
|
|
392
395
|
end
|
|
393
396
|
|
|
397
|
+
# Check if we should skip permissions based on devcontainer/codespace environment
|
|
398
|
+
# This enables providers to run with elevated permissions in safe development environments
|
|
399
|
+
# Returns true if running in a devcontainer or GitHub Codespace
|
|
400
|
+
def in_devcontainer_or_codespace?
|
|
401
|
+
ENV["REMOTE_CONTAINERS"] == "true" || ENV["CODESPACES"] == "true"
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# Check if provider should skip sandbox permissions
|
|
405
|
+
# Providers can override this to add additional logic beyond environment detection
|
|
406
|
+
def should_skip_permissions?
|
|
407
|
+
# First, check for devcontainer/codespace environment (most reliable)
|
|
408
|
+
return true if in_devcontainer_or_codespace?
|
|
409
|
+
|
|
410
|
+
# Fallback: Check if harness context is available and has configuration
|
|
411
|
+
return false unless @harness_context
|
|
412
|
+
|
|
413
|
+
# Get configuration from harness
|
|
414
|
+
config = @harness_context.config
|
|
415
|
+
return false unless config
|
|
416
|
+
|
|
417
|
+
# Use configuration method to determine if full permissions should be used
|
|
418
|
+
# Provider subclasses should pass their provider name
|
|
419
|
+
false # Base implementation returns false, subclasses should override
|
|
420
|
+
end
|
|
421
|
+
|
|
394
422
|
private
|
|
395
423
|
end
|
|
396
424
|
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aidp
|
|
4
|
+
module Providers
|
|
5
|
+
# CapabilityRegistry maintains a queryable registry of provider capabilities
|
|
6
|
+
# and features. This enables runtime feature detection and provider selection
|
|
7
|
+
# based on required capabilities.
|
|
8
|
+
#
|
|
9
|
+
# @see https://github.com/viamin/aidp/issues/243
|
|
10
|
+
class CapabilityRegistry
|
|
11
|
+
# Standard capability keys
|
|
12
|
+
CAPABILITY_KEYS = [
|
|
13
|
+
:reasoning_tiers, # Array of supported reasoning tiers (mini, standard, thinking, etc.)
|
|
14
|
+
:context_window, # Maximum context window size in tokens
|
|
15
|
+
:supports_json_mode, # Boolean: supports JSON mode output
|
|
16
|
+
:supports_tool_use, # Boolean: supports tool/function calling
|
|
17
|
+
:supports_vision, # Boolean: supports image/vision inputs
|
|
18
|
+
:supports_file_upload, # Boolean: supports file uploads
|
|
19
|
+
:streaming, # Boolean: supports streaming responses
|
|
20
|
+
:supports_mcp, # Boolean: supports Model Context Protocol
|
|
21
|
+
:max_tokens, # Maximum tokens per response
|
|
22
|
+
:supports_dangerous_mode # Boolean: supports elevated permissions mode
|
|
23
|
+
].freeze
|
|
24
|
+
|
|
25
|
+
def initialize
|
|
26
|
+
@capabilities = {}
|
|
27
|
+
@providers = {}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Register a provider and its capabilities
|
|
31
|
+
# @param provider [Aidp::Providers::Base] provider instance
|
|
32
|
+
# @return [void]
|
|
33
|
+
def register(provider)
|
|
34
|
+
provider_name = provider.name
|
|
35
|
+
@providers[provider_name] = provider
|
|
36
|
+
|
|
37
|
+
# Collect capabilities from provider
|
|
38
|
+
caps = provider.capabilities.dup
|
|
39
|
+
caps[:supports_mcp] = provider.supports_mcp?
|
|
40
|
+
caps[:supports_dangerous_mode] = provider.supports_dangerous_mode?
|
|
41
|
+
|
|
42
|
+
@capabilities[provider_name] = caps
|
|
43
|
+
|
|
44
|
+
Aidp.log_debug("CapabilityRegistry", "registered provider",
|
|
45
|
+
provider: provider_name,
|
|
46
|
+
capabilities: caps.keys)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Unregister a provider
|
|
50
|
+
# @param provider_name [String] provider identifier
|
|
51
|
+
# @return [void]
|
|
52
|
+
def unregister(provider_name)
|
|
53
|
+
@capabilities.delete(provider_name)
|
|
54
|
+
@providers.delete(provider_name)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Get capabilities for a specific provider
|
|
58
|
+
# @param provider_name [String] provider identifier
|
|
59
|
+
# @return [Hash, nil] capabilities hash or nil if not found
|
|
60
|
+
def capabilities_for(provider_name)
|
|
61
|
+
@capabilities[provider_name]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Check if a provider has a specific capability
|
|
65
|
+
# @param provider_name [String] provider identifier
|
|
66
|
+
# @param capability [Symbol] capability key
|
|
67
|
+
# @param value [Object, nil] optional value to match
|
|
68
|
+
# @return [Boolean] true if provider has the capability
|
|
69
|
+
def has_capability?(provider_name, capability, value = nil)
|
|
70
|
+
caps = @capabilities[provider_name]
|
|
71
|
+
return false unless caps
|
|
72
|
+
|
|
73
|
+
if value.nil?
|
|
74
|
+
# Just check if capability exists and is truthy
|
|
75
|
+
caps.key?(capability) && caps[capability]
|
|
76
|
+
else
|
|
77
|
+
# Check if capability matches specific value
|
|
78
|
+
caps[capability] == value
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Find providers that match capability requirements
|
|
83
|
+
# @param requirements [Hash] capability requirements
|
|
84
|
+
# @return [Array<String>] array of matching provider names
|
|
85
|
+
# @example
|
|
86
|
+
# registry.find_providers(supports_vision: true, min_context_window: 100_000)
|
|
87
|
+
def find_providers(**requirements)
|
|
88
|
+
matching = []
|
|
89
|
+
|
|
90
|
+
@capabilities.each do |provider_name, caps|
|
|
91
|
+
matches = requirements.all? do |key, required_value|
|
|
92
|
+
case key
|
|
93
|
+
when :min_context_window
|
|
94
|
+
caps[:context_window] && caps[:context_window] >= required_value
|
|
95
|
+
when :max_context_window
|
|
96
|
+
caps[:context_window] && caps[:context_window] <= required_value
|
|
97
|
+
when :reasoning_tier
|
|
98
|
+
caps[:reasoning_tiers]&.include?(required_value)
|
|
99
|
+
else
|
|
100
|
+
# Exact match for boolean and other values
|
|
101
|
+
caps[key] == required_value
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
matching << provider_name if matches
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
matching
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Get all registered providers
|
|
112
|
+
# @return [Array<String>] array of provider names
|
|
113
|
+
def registered_providers
|
|
114
|
+
@providers.keys
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Get detailed information about all registered providers
|
|
118
|
+
# @return [Hash] provider information indexed by provider name
|
|
119
|
+
def provider_info
|
|
120
|
+
info = {}
|
|
121
|
+
|
|
122
|
+
@providers.each do |provider_name, provider|
|
|
123
|
+
caps = @capabilities[provider_name] || {}
|
|
124
|
+
|
|
125
|
+
info[provider_name] = {
|
|
126
|
+
display_name: provider.display_name,
|
|
127
|
+
available: provider.available?,
|
|
128
|
+
capabilities: caps,
|
|
129
|
+
dangerous_mode_enabled: provider.dangerous_mode_enabled?,
|
|
130
|
+
health_status: provider.health_status
|
|
131
|
+
}
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
info
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Check capability compatibility between providers
|
|
138
|
+
# @param provider_name1 [String] first provider
|
|
139
|
+
# @param provider_name2 [String] second provider
|
|
140
|
+
# @return [Hash] compatibility report
|
|
141
|
+
def compatibility_report(provider_name1, provider_name2)
|
|
142
|
+
caps1 = @capabilities[provider_name1]
|
|
143
|
+
caps2 = @capabilities[provider_name2]
|
|
144
|
+
|
|
145
|
+
return {error: "Provider not found"} unless caps1 && caps2
|
|
146
|
+
|
|
147
|
+
common = {}
|
|
148
|
+
differences = {}
|
|
149
|
+
|
|
150
|
+
all_keys = (caps1.keys + caps2.keys).uniq
|
|
151
|
+
|
|
152
|
+
all_keys.each do |key|
|
|
153
|
+
val1 = caps1[key]
|
|
154
|
+
val2 = caps2[key]
|
|
155
|
+
|
|
156
|
+
if val1 == val2
|
|
157
|
+
common[key] = val1
|
|
158
|
+
else
|
|
159
|
+
differences[key] = {provider_name1 => val1, provider_name2 => val2}
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
{
|
|
164
|
+
common_capabilities: common,
|
|
165
|
+
differences: differences,
|
|
166
|
+
compatibility_score: common.size.to_f / all_keys.size
|
|
167
|
+
}
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Get capability statistics across all providers
|
|
171
|
+
# @return [Hash] statistics about capability support
|
|
172
|
+
def capability_statistics
|
|
173
|
+
stats = {}
|
|
174
|
+
|
|
175
|
+
CAPABILITY_KEYS.each do |key|
|
|
176
|
+
stats[key] = {
|
|
177
|
+
total_providers: @providers.size,
|
|
178
|
+
supporting_providers: 0,
|
|
179
|
+
providers: []
|
|
180
|
+
}
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
@capabilities.each do |provider_name, caps|
|
|
184
|
+
caps.each do |key, value|
|
|
185
|
+
next unless stats.key?(key)
|
|
186
|
+
|
|
187
|
+
if value.is_a?(TrueClass) || (value.is_a?(Array) && !value.empty?) || (value.is_a?(Integer) && value > 0)
|
|
188
|
+
stats[key][:supporting_providers] += 1
|
|
189
|
+
stats[key][:providers] << provider_name
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
stats
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Clear all registered providers
|
|
198
|
+
# @return [void]
|
|
199
|
+
def clear
|
|
200
|
+
@capabilities.clear
|
|
201
|
+
@providers.clear
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
data/lib/aidp/providers/codex.rb
CHANGED
|
@@ -79,6 +79,20 @@ module Aidp
|
|
|
79
79
|
args += ["--session", session]
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
+
# In devcontainer, ensure sandbox mode and approval policy are set
|
|
83
|
+
# These are already set via environment variables in devcontainer.json
|
|
84
|
+
# but we verify and log them here for visibility
|
|
85
|
+
if in_devcontainer_or_codespace?
|
|
86
|
+
unless ENV["CODEX_SANDBOX_MODE"] == "danger-full-access"
|
|
87
|
+
ENV["CODEX_SANDBOX_MODE"] = "danger-full-access"
|
|
88
|
+
debug_log("🔓 Set CODEX_SANDBOX_MODE=danger-full-access for devcontainer", level: :info)
|
|
89
|
+
end
|
|
90
|
+
unless ENV["CODEX_APPROVAL_POLICY"] == "never"
|
|
91
|
+
ENV["CODEX_APPROVAL_POLICY"] = "never"
|
|
92
|
+
debug_log("🔓 Set CODEX_APPROVAL_POLICY=never for devcontainer", level: :info)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
82
96
|
# Use debug_execute_command for better debugging
|
|
83
97
|
result = debug_execute_command("codex", args: args, timeout: timeout_seconds, streaming: streaming_enabled)
|
|
84
98
|
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aidp
|
|
4
|
+
module Providers
|
|
5
|
+
# ErrorTaxonomy defines the five standardized error categories that all providers
|
|
6
|
+
# use for consistent error handling, retry logic, and escalation.
|
|
7
|
+
#
|
|
8
|
+
# Categories:
|
|
9
|
+
# - rate_limited: Provider is rate-limiting requests (switch provider immediately)
|
|
10
|
+
# - auth_expired: Authentication credentials are invalid or expired (escalate or switch)
|
|
11
|
+
# - quota_exceeded: Usage quota has been exceeded (switch provider)
|
|
12
|
+
# - transient: Temporary error that may resolve on retry (retry with backoff)
|
|
13
|
+
# - permanent: Permanent error that won't resolve with retry (escalate or abort)
|
|
14
|
+
#
|
|
15
|
+
# @see https://github.com/viamin/aidp/issues/243
|
|
16
|
+
module ErrorTaxonomy
|
|
17
|
+
# Error category constants
|
|
18
|
+
RATE_LIMITED = :rate_limited
|
|
19
|
+
AUTH_EXPIRED = :auth_expired
|
|
20
|
+
QUOTA_EXCEEDED = :quota_exceeded
|
|
21
|
+
TRANSIENT = :transient
|
|
22
|
+
PERMANENT = :permanent
|
|
23
|
+
|
|
24
|
+
# All valid error categories
|
|
25
|
+
CATEGORIES = [
|
|
26
|
+
RATE_LIMITED,
|
|
27
|
+
AUTH_EXPIRED,
|
|
28
|
+
QUOTA_EXCEEDED,
|
|
29
|
+
TRANSIENT,
|
|
30
|
+
PERMANENT
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
# Default error patterns for common error messages
|
|
34
|
+
# Providers can override these with provider-specific patterns
|
|
35
|
+
DEFAULT_PATTERNS = {
|
|
36
|
+
rate_limited: [
|
|
37
|
+
/rate.?limit/i,
|
|
38
|
+
/too.?many.?requests/i,
|
|
39
|
+
/429/,
|
|
40
|
+
/throttl(ed|ing)/i,
|
|
41
|
+
/request.?limit/i,
|
|
42
|
+
/requests.?per.?minute/i,
|
|
43
|
+
/rpm.?exceeded/i
|
|
44
|
+
],
|
|
45
|
+
auth_expired: [
|
|
46
|
+
/auth(entication|orization).?(fail(ed|ure)|error)/i,
|
|
47
|
+
/invalid.?(api.?key|token|credential)/i,
|
|
48
|
+
/expired.?(api.?key|token|credential)/i,
|
|
49
|
+
/unauthorized/i,
|
|
50
|
+
/401/,
|
|
51
|
+
/403/,
|
|
52
|
+
/permission.?denied/i,
|
|
53
|
+
/access.?denied/i
|
|
54
|
+
],
|
|
55
|
+
quota_exceeded: [
|
|
56
|
+
/quota.?(exceed(ed)?|limit|exhausted)/i,
|
|
57
|
+
/usage.?limit/i,
|
|
58
|
+
/billing.?limit/i,
|
|
59
|
+
/credit.?limit/i,
|
|
60
|
+
/insufficient.?quota/i,
|
|
61
|
+
/usage.?cap/i
|
|
62
|
+
],
|
|
63
|
+
transient: [
|
|
64
|
+
/timeout/i,
|
|
65
|
+
/timed?.?out/i,
|
|
66
|
+
/connection.?(reset|refused|lost|closed)/i,
|
|
67
|
+
/temporary.?error/i,
|
|
68
|
+
/try.?again/i,
|
|
69
|
+
/service.?unavailable/i,
|
|
70
|
+
/503/,
|
|
71
|
+
/502/,
|
|
72
|
+
/504/,
|
|
73
|
+
/gateway.?timeout/i,
|
|
74
|
+
/network.?error/i,
|
|
75
|
+
/socket.?error/i,
|
|
76
|
+
/connection.?error/i,
|
|
77
|
+
/broken.?pipe/i,
|
|
78
|
+
/host.?unreachable/i
|
|
79
|
+
],
|
|
80
|
+
permanent: [
|
|
81
|
+
/invalid.?(model|parameter|request|input)/i,
|
|
82
|
+
/unsupported.?(operation|feature|model)/i,
|
|
83
|
+
/not.?found/i,
|
|
84
|
+
/404/,
|
|
85
|
+
/bad.?request/i,
|
|
86
|
+
/400/,
|
|
87
|
+
/malformed/i,
|
|
88
|
+
/syntax.?error/i,
|
|
89
|
+
/validation.?error/i,
|
|
90
|
+
/model.?not.?available/i,
|
|
91
|
+
/model.?deprecated/i
|
|
92
|
+
]
|
|
93
|
+
}.freeze
|
|
94
|
+
|
|
95
|
+
# Retry policy for each category
|
|
96
|
+
RETRY_POLICIES = {
|
|
97
|
+
rate_limited: {
|
|
98
|
+
retry: false,
|
|
99
|
+
switch_provider: true,
|
|
100
|
+
escalate: false,
|
|
101
|
+
backoff_strategy: :none
|
|
102
|
+
},
|
|
103
|
+
auth_expired: {
|
|
104
|
+
retry: false,
|
|
105
|
+
switch_provider: true,
|
|
106
|
+
escalate: true,
|
|
107
|
+
backoff_strategy: :none
|
|
108
|
+
},
|
|
109
|
+
quota_exceeded: {
|
|
110
|
+
retry: false,
|
|
111
|
+
switch_provider: true,
|
|
112
|
+
escalate: false,
|
|
113
|
+
backoff_strategy: :none
|
|
114
|
+
},
|
|
115
|
+
transient: {
|
|
116
|
+
retry: true,
|
|
117
|
+
switch_provider: false,
|
|
118
|
+
escalate: false,
|
|
119
|
+
backoff_strategy: :exponential
|
|
120
|
+
},
|
|
121
|
+
permanent: {
|
|
122
|
+
retry: false,
|
|
123
|
+
switch_provider: false,
|
|
124
|
+
escalate: true,
|
|
125
|
+
backoff_strategy: :none
|
|
126
|
+
}
|
|
127
|
+
}.freeze
|
|
128
|
+
|
|
129
|
+
# Check if a category is valid
|
|
130
|
+
# @param category [Symbol] category to check
|
|
131
|
+
# @return [Boolean] true if valid
|
|
132
|
+
def self.valid_category?(category)
|
|
133
|
+
CATEGORIES.include?(category)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Get retry policy for a category
|
|
137
|
+
# @param category [Symbol] error category
|
|
138
|
+
# @return [Hash] retry policy configuration
|
|
139
|
+
def self.retry_policy(category)
|
|
140
|
+
RETRY_POLICIES[category] || RETRY_POLICIES[:transient]
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Classify an error message using default patterns
|
|
144
|
+
# @param message [String] error message
|
|
145
|
+
# @return [Symbol] error category
|
|
146
|
+
def self.classify_message(message)
|
|
147
|
+
return :transient if message.nil? || message.empty?
|
|
148
|
+
|
|
149
|
+
message_lower = message.downcase
|
|
150
|
+
|
|
151
|
+
# Check each category's patterns
|
|
152
|
+
DEFAULT_PATTERNS.each do |category, patterns|
|
|
153
|
+
patterns.each do |pattern|
|
|
154
|
+
return category if message_lower.match?(pattern)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Default to transient for unknown errors
|
|
159
|
+
:transient
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Check if an error category is retryable
|
|
163
|
+
# @param category [Symbol] error category
|
|
164
|
+
# @return [Boolean] true if should retry
|
|
165
|
+
def self.retryable?(category)
|
|
166
|
+
policy = retry_policy(category)
|
|
167
|
+
policy[:retry] == true
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Check if an error category should trigger provider switch
|
|
171
|
+
# @param category [Symbol] error category
|
|
172
|
+
# @return [Boolean] true if should switch provider
|
|
173
|
+
def self.should_switch_provider?(category)
|
|
174
|
+
policy = retry_policy(category)
|
|
175
|
+
policy[:switch_provider] == true
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Check if an error category should be escalated
|
|
179
|
+
# @param category [Symbol] error category
|
|
180
|
+
# @return [Boolean] true if should escalate
|
|
181
|
+
def self.should_escalate?(category)
|
|
182
|
+
policy = retry_policy(category)
|
|
183
|
+
policy[:escalate] == true
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Get backoff strategy for a category
|
|
187
|
+
# @param category [Symbol] error category
|
|
188
|
+
# @return [Symbol] backoff strategy (:none, :linear, :exponential)
|
|
189
|
+
def self.backoff_strategy(category)
|
|
190
|
+
policy = retry_policy(category)
|
|
191
|
+
policy[:backoff_strategy] || :none
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -36,11 +36,12 @@ module Aidp
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
begin
|
|
39
|
+
command_args = ["--prompt", prompt]
|
|
39
40
|
# Use debug_execute_command with streaming support
|
|
40
|
-
result = debug_execute_command("gemini", args:
|
|
41
|
+
result = debug_execute_command("gemini", args: command_args, timeout: timeout_seconds, streaming: streaming_enabled)
|
|
41
42
|
|
|
42
43
|
# Log the results
|
|
43
|
-
debug_command("gemini", args:
|
|
44
|
+
debug_command("gemini", args: command_args, input: nil, output: result.out, error: result.err, exit_code: result.exit_status)
|
|
44
45
|
|
|
45
46
|
if result.exit_status == 0
|
|
46
47
|
result.out
|