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
|
@@ -9,10 +9,143 @@ module Aidp
|
|
|
9
9
|
class Anthropic < Base
|
|
10
10
|
include Aidp::DebugMixin
|
|
11
11
|
|
|
12
|
+
# Model name pattern for Anthropic Claude models
|
|
13
|
+
MODEL_PATTERN = /^claude-[\d.-]+-(?:opus|sonnet|haiku)(?:-\d{8})?$/i
|
|
14
|
+
|
|
12
15
|
def self.available?
|
|
13
16
|
!!Aidp::Util.which("claude")
|
|
14
17
|
end
|
|
15
18
|
|
|
19
|
+
# Normalize a provider-specific model name to its model family
|
|
20
|
+
#
|
|
21
|
+
# Anthropic uses date-versioned models (e.g., "claude-3-5-sonnet-20241022").
|
|
22
|
+
# This method strips the date suffix to get the family name.
|
|
23
|
+
#
|
|
24
|
+
# @param provider_model_name [String] The versioned model name
|
|
25
|
+
# @return [String] The model family name (e.g., "claude-3-5-sonnet")
|
|
26
|
+
def self.model_family(provider_model_name)
|
|
27
|
+
# Strip date suffix: "claude-3-5-sonnet-20241022" ā "claude-3-5-sonnet"
|
|
28
|
+
provider_model_name.sub(/-\d{8}$/, "")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Convert a model family name to the provider's preferred model name
|
|
32
|
+
#
|
|
33
|
+
# Returns the family name as-is. Users can configure specific versions in aidp.yml.
|
|
34
|
+
#
|
|
35
|
+
# @param family_name [String] The model family name
|
|
36
|
+
# @return [String] The model name (same as family for flexibility)
|
|
37
|
+
def self.provider_model_name(family_name)
|
|
38
|
+
family_name
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Check if this provider supports a given model family
|
|
42
|
+
#
|
|
43
|
+
# @param family_name [String] The model family name
|
|
44
|
+
# @return [Boolean] True if it matches Claude model pattern
|
|
45
|
+
def self.supports_model_family?(family_name)
|
|
46
|
+
MODEL_PATTERN.match?(family_name)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Discover available models from Claude CLI
|
|
50
|
+
#
|
|
51
|
+
# @return [Array<Hash>] Array of discovered models
|
|
52
|
+
def self.discover_models
|
|
53
|
+
return [] unless available?
|
|
54
|
+
|
|
55
|
+
begin
|
|
56
|
+
require "open3"
|
|
57
|
+
output, _, status = Open3.capture3("claude", "models", "list", {timeout: 10})
|
|
58
|
+
return [] unless status.success?
|
|
59
|
+
|
|
60
|
+
parse_models_list(output)
|
|
61
|
+
rescue => e
|
|
62
|
+
Aidp.log_debug("anthropic_provider", "discovery failed", error: e.message)
|
|
63
|
+
[]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class << self
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def parse_models_list(output)
|
|
71
|
+
return [] if output.nil? || output.empty?
|
|
72
|
+
|
|
73
|
+
models = []
|
|
74
|
+
lines = output.lines.map(&:strip)
|
|
75
|
+
|
|
76
|
+
# Skip header and separator lines
|
|
77
|
+
lines.reject! { |line| line.empty? || line.match?(/^[-=]+$/) || line.match?(/^(Model|Name)/i) }
|
|
78
|
+
|
|
79
|
+
lines.each do |line|
|
|
80
|
+
model_info = parse_model_line(line)
|
|
81
|
+
models << model_info if model_info
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
Aidp.log_info("anthropic_provider", "discovered models", count: models.size)
|
|
85
|
+
models
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def parse_model_line(line)
|
|
89
|
+
# Format 1: Simple list of model names
|
|
90
|
+
if line.match?(/^claude-\d/)
|
|
91
|
+
model_name = line.split.first
|
|
92
|
+
return build_model_info(model_name)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Format 2: Table format with columns
|
|
96
|
+
parts = line.split(/\s{2,}/)
|
|
97
|
+
if parts.size >= 1 && parts[0].match?(/^claude/)
|
|
98
|
+
model_name = parts[0]
|
|
99
|
+
model_name = "#{model_name}-#{parts[1]}" if parts.size > 1 && parts[1].match?(/^\d{8}$/)
|
|
100
|
+
return build_model_info(model_name)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Format 3: JSON-like or key-value pairs
|
|
104
|
+
if line.match?(/name:\s*(.+)/)
|
|
105
|
+
model_name = $1.strip
|
|
106
|
+
return build_model_info(model_name)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
nil
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def build_model_info(model_name)
|
|
113
|
+
family = model_family(model_name)
|
|
114
|
+
tier = classify_tier(model_name)
|
|
115
|
+
|
|
116
|
+
{
|
|
117
|
+
name: model_name,
|
|
118
|
+
family: family,
|
|
119
|
+
tier: tier,
|
|
120
|
+
capabilities: extract_capabilities(model_name),
|
|
121
|
+
context_window: infer_context_window(family),
|
|
122
|
+
provider: "anthropic"
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def classify_tier(model_name)
|
|
127
|
+
name_lower = model_name.downcase
|
|
128
|
+
return "advanced" if name_lower.include?("opus")
|
|
129
|
+
return "mini" if name_lower.include?("haiku")
|
|
130
|
+
return "standard" if name_lower.include?("sonnet")
|
|
131
|
+
"standard"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def extract_capabilities(model_name)
|
|
135
|
+
capabilities = ["chat", "code"]
|
|
136
|
+
name_lower = model_name.downcase
|
|
137
|
+
capabilities << "vision" unless name_lower.include?("haiku")
|
|
138
|
+
capabilities
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def infer_context_window(family)
|
|
142
|
+
family.match?(/claude-3/) ? 200_000 : nil
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Public instance methods (called from workflows and harness)
|
|
147
|
+
public
|
|
148
|
+
|
|
16
149
|
def name
|
|
17
150
|
"anthropic"
|
|
18
151
|
end
|
|
@@ -53,8 +186,7 @@ module Aidp
|
|
|
53
186
|
supports_json_mode: true,
|
|
54
187
|
supports_tool_use: true,
|
|
55
188
|
supports_vision: false,
|
|
56
|
-
supports_file_upload: true
|
|
57
|
-
streaming: true
|
|
189
|
+
supports_file_upload: true
|
|
58
190
|
}
|
|
59
191
|
end
|
|
60
192
|
|
|
@@ -115,19 +247,8 @@ module Aidp
|
|
|
115
247
|
debug_provider("claude", "Starting execution", {timeout: timeout_seconds})
|
|
116
248
|
debug_log("š Sending prompt to claude...", level: :info)
|
|
117
249
|
|
|
118
|
-
#
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
# Build command arguments with proper streaming support
|
|
122
|
-
args = ["--print"]
|
|
123
|
-
if streaming_enabled
|
|
124
|
-
# Claude CLI requires --verbose when using --print with --output-format=stream-json
|
|
125
|
-
args += ["--verbose", "--output-format=stream-json", "--include-partial-messages"]
|
|
126
|
-
display_message("šŗ True streaming enabled - real-time chunks from Claude API", type: :info)
|
|
127
|
-
else
|
|
128
|
-
# Use text format for non-streaming (default behavior)
|
|
129
|
-
args += ["--output-format=text"]
|
|
130
|
-
end
|
|
250
|
+
# Build command arguments
|
|
251
|
+
args = ["--print", "--output-format=text"]
|
|
131
252
|
|
|
132
253
|
# Check if we should skip permissions (devcontainer support)
|
|
133
254
|
if should_skip_permissions?
|
|
@@ -136,26 +257,20 @@ module Aidp
|
|
|
136
257
|
end
|
|
137
258
|
|
|
138
259
|
begin
|
|
139
|
-
|
|
140
|
-
result = debug_execute_command("claude", args: args, input: prompt, timeout: timeout_seconds, streaming: streaming_enabled)
|
|
260
|
+
result = debug_execute_command("claude", args: args, input: prompt, timeout: timeout_seconds)
|
|
141
261
|
|
|
142
262
|
# Log the results
|
|
143
263
|
debug_command("claude", args: args, input: prompt, output: result.out, error: result.err, exit_code: result.exit_status)
|
|
144
264
|
|
|
145
265
|
if result.exit_status == 0
|
|
146
|
-
|
|
147
|
-
if streaming_enabled && args.include?("--output-format=stream-json")
|
|
148
|
-
# Parse stream-json output and extract final content
|
|
149
|
-
parse_stream_json_output(result.out)
|
|
150
|
-
else
|
|
151
|
-
# Return text output as-is
|
|
152
|
-
result.out
|
|
153
|
-
end
|
|
266
|
+
result.out
|
|
154
267
|
else
|
|
155
268
|
# Detect auth issues in stdout/stderr (Claude sometimes prints JSON with auth error to stdout)
|
|
156
269
|
combined = [result.out, result.err].compact.join("\n")
|
|
157
270
|
if combined.downcase.include?("oauth token has expired") || combined.downcase.include?("authentication_error")
|
|
158
|
-
error_message = "Authentication error from Claude CLI: token expired or invalid
|
|
271
|
+
error_message = "Authentication error from Claude CLI: token expired or invalid.\n" \
|
|
272
|
+
"Run 'claude /login' or refresh credentials.\n" \
|
|
273
|
+
"Note: Model discovery requires valid authentication."
|
|
159
274
|
debug_error(StandardError.new(error_message), {exit_code: result.exit_status, stdout: result.out, stderr: result.err})
|
|
160
275
|
# Raise a recognizable error for classifier
|
|
161
276
|
raise error_message
|
|
@@ -172,60 +287,6 @@ module Aidp
|
|
|
172
287
|
|
|
173
288
|
private
|
|
174
289
|
|
|
175
|
-
def calculate_timeout
|
|
176
|
-
# Priority order for timeout calculation:
|
|
177
|
-
# 1. Quick mode (for testing)
|
|
178
|
-
# 2. Environment variable override
|
|
179
|
-
# 3. Adaptive timeout based on step type
|
|
180
|
-
# 4. Default timeout
|
|
181
|
-
|
|
182
|
-
if ENV["AIDP_QUICK_MODE"]
|
|
183
|
-
display_message("ā” Quick mode enabled - #{TIMEOUT_QUICK_MODE / 60} minute timeout", type: :highlight)
|
|
184
|
-
return TIMEOUT_QUICK_MODE
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
if ENV["AIDP_ANTHROPIC_TIMEOUT"]
|
|
188
|
-
return ENV["AIDP_ANTHROPIC_TIMEOUT"].to_i
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
if adaptive_timeout
|
|
192
|
-
display_message("š§ Using adaptive timeout: #{adaptive_timeout} seconds", type: :info)
|
|
193
|
-
return adaptive_timeout
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
# Default timeout
|
|
197
|
-
display_message("š Using default timeout: #{TIMEOUT_DEFAULT / 60} minutes", type: :info)
|
|
198
|
-
TIMEOUT_DEFAULT
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def adaptive_timeout
|
|
202
|
-
@adaptive_timeout ||= begin
|
|
203
|
-
# Timeout recommendations based on step type patterns
|
|
204
|
-
step_name = ENV["AIDP_CURRENT_STEP"] || ""
|
|
205
|
-
|
|
206
|
-
case step_name
|
|
207
|
-
when /REPOSITORY_ANALYSIS/
|
|
208
|
-
TIMEOUT_REPOSITORY_ANALYSIS
|
|
209
|
-
when /ARCHITECTURE_ANALYSIS/
|
|
210
|
-
TIMEOUT_ARCHITECTURE_ANALYSIS
|
|
211
|
-
when /TEST_ANALYSIS/
|
|
212
|
-
TIMEOUT_TEST_ANALYSIS
|
|
213
|
-
when /FUNCTIONALITY_ANALYSIS/
|
|
214
|
-
TIMEOUT_FUNCTIONALITY_ANALYSIS
|
|
215
|
-
when /DOCUMENTATION_ANALYSIS/
|
|
216
|
-
TIMEOUT_DOCUMENTATION_ANALYSIS
|
|
217
|
-
when /STATIC_ANALYSIS/
|
|
218
|
-
TIMEOUT_STATIC_ANALYSIS
|
|
219
|
-
when /REFACTORING_RECOMMENDATIONS/
|
|
220
|
-
TIMEOUT_REFACTORING_RECOMMENDATIONS
|
|
221
|
-
when /IMPLEMENTATION/
|
|
222
|
-
TIMEOUT_IMPLEMENTATION
|
|
223
|
-
else
|
|
224
|
-
nil # Use default
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
|
|
229
290
|
# Check if we should skip permissions based on devcontainer configuration
|
|
230
291
|
# Overrides base class to add logging and Claude-specific config check
|
|
231
292
|
def should_skip_permissions?
|
|
@@ -243,54 +304,6 @@ module Aidp
|
|
|
243
304
|
false
|
|
244
305
|
end
|
|
245
306
|
|
|
246
|
-
# Parse stream-json output from Claude CLI
|
|
247
|
-
def parse_stream_json_output(output)
|
|
248
|
-
return output if output.nil? || output.empty?
|
|
249
|
-
|
|
250
|
-
# Stream-json output contains multiple JSON objects, one per line
|
|
251
|
-
# We want to extract the final content from the last complete message
|
|
252
|
-
lines = output.strip.split("\n")
|
|
253
|
-
content_parts = []
|
|
254
|
-
|
|
255
|
-
lines.each do |line|
|
|
256
|
-
next if line.strip.empty?
|
|
257
|
-
|
|
258
|
-
begin
|
|
259
|
-
json_obj = JSON.parse(line)
|
|
260
|
-
|
|
261
|
-
# Look for content in various possible structures
|
|
262
|
-
if json_obj["type"] == "content_block_delta" && json_obj["delta"] && json_obj["delta"]["text"]
|
|
263
|
-
content_parts << json_obj["delta"]["text"]
|
|
264
|
-
elsif json_obj["content"]&.is_a?(Array)
|
|
265
|
-
json_obj["content"].each do |content_item|
|
|
266
|
-
content_parts << content_item["text"] if content_item["text"]
|
|
267
|
-
end
|
|
268
|
-
elsif json_obj["message"] && json_obj["message"]["content"]
|
|
269
|
-
if json_obj["message"]["content"].is_a?(Array)
|
|
270
|
-
json_obj["message"]["content"].each do |content_item|
|
|
271
|
-
content_parts << content_item["text"] if content_item["text"]
|
|
272
|
-
end
|
|
273
|
-
elsif json_obj["message"]["content"].is_a?(String)
|
|
274
|
-
content_parts << json_obj["message"]["content"]
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
rescue JSON::ParserError => e
|
|
278
|
-
debug_log("ā ļø Failed to parse JSON line: #{e.message}", level: :warn, data: {line: line})
|
|
279
|
-
# If JSON parsing fails, treat as plain text
|
|
280
|
-
content_parts << line
|
|
281
|
-
end
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
result = content_parts.join
|
|
285
|
-
|
|
286
|
-
# Fallback: if no content found in JSON, return original output
|
|
287
|
-
result.empty? ? output : result
|
|
288
|
-
rescue => e
|
|
289
|
-
debug_log("ā ļø Failed to parse stream-json output: #{e.message}", level: :warn)
|
|
290
|
-
# Return original output if parsing fails
|
|
291
|
-
output
|
|
292
|
-
end
|
|
293
|
-
|
|
294
307
|
# Parse Claude MCP server list output
|
|
295
308
|
def parse_claude_mcp_output(output)
|
|
296
309
|
servers = []
|
data/lib/aidp/providers/base.rb
CHANGED
|
@@ -324,6 +324,42 @@ module Aidp
|
|
|
324
324
|
end
|
|
325
325
|
end
|
|
326
326
|
|
|
327
|
+
# Helper method for registry-based model discovery
|
|
328
|
+
#
|
|
329
|
+
# Providers that use the model registry can call this method to discover models
|
|
330
|
+
# based on a model family pattern.
|
|
331
|
+
#
|
|
332
|
+
# @param model_pattern [Regexp] Pattern to match model families
|
|
333
|
+
# @param provider_name [String] Name of the provider
|
|
334
|
+
# @return [Array<Hash>] Array of discovered models
|
|
335
|
+
def self.discover_models_from_registry(model_pattern, provider_name)
|
|
336
|
+
require_relative "../harness/model_registry"
|
|
337
|
+
registry = Aidp::Harness::ModelRegistry.new
|
|
338
|
+
|
|
339
|
+
# Get all models from registry that match the pattern
|
|
340
|
+
models = registry.all_families.filter_map do |family|
|
|
341
|
+
next unless model_pattern.match?(family)
|
|
342
|
+
|
|
343
|
+
info = registry.get_model_info(family)
|
|
344
|
+
next unless info
|
|
345
|
+
|
|
346
|
+
{
|
|
347
|
+
name: family,
|
|
348
|
+
family: family,
|
|
349
|
+
tier: info["tier"],
|
|
350
|
+
capabilities: info["capabilities"] || [],
|
|
351
|
+
context_window: info["context_window"],
|
|
352
|
+
provider: provider_name
|
|
353
|
+
}
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
Aidp.log_info("#{provider_name}_provider", "using registry models", count: models.size)
|
|
357
|
+
models
|
|
358
|
+
rescue => e
|
|
359
|
+
Aidp.log_debug("#{provider_name}_provider", "discovery failed", error: e.message)
|
|
360
|
+
[]
|
|
361
|
+
end
|
|
362
|
+
|
|
327
363
|
protected
|
|
328
364
|
|
|
329
365
|
# Log message to job if in background mode
|
|
@@ -371,8 +407,6 @@ module Aidp
|
|
|
371
407
|
(success_rate * 50) + ((1 - rate_limit_ratio) * 30) + (response_time_score * 0.2)
|
|
372
408
|
end
|
|
373
409
|
|
|
374
|
-
protected
|
|
375
|
-
|
|
376
410
|
# Update spinner status with elapsed time
|
|
377
411
|
# This is a shared method used by all providers to display progress
|
|
378
412
|
def update_spinner_status(spinner, elapsed, provider_name)
|
|
@@ -419,6 +453,68 @@ module Aidp
|
|
|
419
453
|
false # Base implementation returns false, subclasses should override
|
|
420
454
|
end
|
|
421
455
|
|
|
456
|
+
# Calculate timeout for provider operations
|
|
457
|
+
#
|
|
458
|
+
# Priority order:
|
|
459
|
+
# 1. Quick mode (for testing)
|
|
460
|
+
# 2. Provider-specific environment variable override
|
|
461
|
+
# 3. Adaptive timeout based on step type
|
|
462
|
+
# 4. Default timeout
|
|
463
|
+
#
|
|
464
|
+
# Override provider_env_var to customize the environment variable name
|
|
465
|
+
def calculate_timeout
|
|
466
|
+
if ENV["AIDP_QUICK_MODE"]
|
|
467
|
+
display_message("ā” Quick mode enabled - #{TIMEOUT_QUICK_MODE / 60} minute timeout", type: :highlight)
|
|
468
|
+
return TIMEOUT_QUICK_MODE
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
provider_env_var = "AIDP_#{name.upcase}_TIMEOUT"
|
|
472
|
+
return ENV[provider_env_var].to_i if ENV[provider_env_var]
|
|
473
|
+
|
|
474
|
+
step_timeout = adaptive_timeout
|
|
475
|
+
if step_timeout
|
|
476
|
+
display_message("š§ Using adaptive timeout: #{step_timeout} seconds", type: :info)
|
|
477
|
+
return step_timeout
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# Default timeout
|
|
481
|
+
display_message("š Using default timeout: #{TIMEOUT_DEFAULT / 60} minutes", type: :info)
|
|
482
|
+
TIMEOUT_DEFAULT
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# Get adaptive timeout based on step type
|
|
486
|
+
#
|
|
487
|
+
# This method returns different timeout values based on the type of operation
|
|
488
|
+
# being performed, as indicated by the AIDP_CURRENT_STEP environment variable.
|
|
489
|
+
# Returns nil for unknown steps to allow calculate_timeout to use the default.
|
|
490
|
+
def adaptive_timeout
|
|
491
|
+
@adaptive_timeout ||= begin
|
|
492
|
+
# Timeout recommendations based on step type patterns
|
|
493
|
+
step_name = ENV["AIDP_CURRENT_STEP"] || ""
|
|
494
|
+
|
|
495
|
+
case step_name
|
|
496
|
+
when /REPOSITORY_ANALYSIS/
|
|
497
|
+
TIMEOUT_REPOSITORY_ANALYSIS
|
|
498
|
+
when /ARCHITECTURE_ANALYSIS/
|
|
499
|
+
TIMEOUT_ARCHITECTURE_ANALYSIS
|
|
500
|
+
when /TEST_ANALYSIS/
|
|
501
|
+
TIMEOUT_TEST_ANALYSIS
|
|
502
|
+
when /FUNCTIONALITY_ANALYSIS/
|
|
503
|
+
TIMEOUT_FUNCTIONALITY_ANALYSIS
|
|
504
|
+
when /DOCUMENTATION_ANALYSIS/
|
|
505
|
+
TIMEOUT_DOCUMENTATION_ANALYSIS
|
|
506
|
+
when /STATIC_ANALYSIS/
|
|
507
|
+
TIMEOUT_STATIC_ANALYSIS
|
|
508
|
+
when /REFACTORING_RECOMMENDATIONS/
|
|
509
|
+
TIMEOUT_REFACTORING_RECOMMENDATIONS
|
|
510
|
+
when /IMPLEMENTATION/
|
|
511
|
+
TIMEOUT_IMPLEMENTATION
|
|
512
|
+
else
|
|
513
|
+
nil # Return nil for unknown steps
|
|
514
|
+
end
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
|
|
422
518
|
private
|
|
423
519
|
end
|
|
424
520
|
end
|
|
@@ -16,7 +16,6 @@ module Aidp
|
|
|
16
16
|
:supports_tool_use, # Boolean: supports tool/function calling
|
|
17
17
|
:supports_vision, # Boolean: supports image/vision inputs
|
|
18
18
|
:supports_file_upload, # Boolean: supports file uploads
|
|
19
|
-
:streaming, # Boolean: supports streaming responses
|
|
20
19
|
:supports_mcp, # Boolean: supports Model Context Protocol
|
|
21
20
|
:max_tokens, # Maximum tokens per response
|
|
22
21
|
:supports_dangerous_mode # Boolean: supports elevated permissions mode
|
data/lib/aidp/providers/codex.rb
CHANGED
|
@@ -10,10 +10,35 @@ module Aidp
|
|
|
10
10
|
class Codex < Base
|
|
11
11
|
include Aidp::DebugMixin
|
|
12
12
|
|
|
13
|
+
# Model name pattern for OpenAI models (since Codex uses OpenAI)
|
|
14
|
+
MODEL_PATTERN = /^gpt-[\d.o-]+(?:-turbo)?(?:-mini)?$/i
|
|
15
|
+
LONG_PROMPT_THRESHOLD = 8000
|
|
16
|
+
LONG_PROMPT_TIMEOUT = 900 # 15 minutes for large prompts
|
|
17
|
+
|
|
13
18
|
def self.available?
|
|
14
19
|
!!Aidp::Util.which("codex")
|
|
15
20
|
end
|
|
16
21
|
|
|
22
|
+
# Check if this provider supports a given model family
|
|
23
|
+
#
|
|
24
|
+
# @param family_name [String] The model family name
|
|
25
|
+
# @return [Boolean] True if it matches OpenAI model pattern
|
|
26
|
+
def self.supports_model_family?(family_name)
|
|
27
|
+
MODEL_PATTERN.match?(family_name)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Discover available models from registry
|
|
31
|
+
#
|
|
32
|
+
# Note: Codex CLI doesn't have a standard model listing command
|
|
33
|
+
# Returns registry-based models that match OpenAI patterns
|
|
34
|
+
#
|
|
35
|
+
# @return [Array<Hash>] Array of discovered models
|
|
36
|
+
def self.discover_models
|
|
37
|
+
return [] unless available?
|
|
38
|
+
|
|
39
|
+
discover_models_from_registry(MODEL_PATTERN, "codex")
|
|
40
|
+
end
|
|
41
|
+
|
|
17
42
|
def name
|
|
18
43
|
"codex"
|
|
19
44
|
end
|
|
@@ -37,18 +62,13 @@ module Aidp
|
|
|
37
62
|
def send_message(prompt:, session: nil)
|
|
38
63
|
raise "codex CLI not available" unless self.class.available?
|
|
39
64
|
|
|
40
|
-
# Smart timeout calculation
|
|
65
|
+
# Smart timeout calculation (store prompt length for adaptive logic)
|
|
66
|
+
@current_codex_prompt_length = prompt.length
|
|
41
67
|
timeout_seconds = calculate_timeout
|
|
42
68
|
|
|
43
69
|
debug_provider("codex", "Starting execution", {timeout: timeout_seconds})
|
|
44
70
|
debug_log("š Sending prompt to codex (length: #{prompt.length})", level: :info)
|
|
45
71
|
|
|
46
|
-
# Check if streaming mode is enabled
|
|
47
|
-
streaming_enabled = ENV["AIDP_STREAMING"] == "1" || ENV["DEBUG"] == "1"
|
|
48
|
-
if streaming_enabled
|
|
49
|
-
display_message("šŗ Streaming mode enabled - output will appear in real-time", type: :info)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
72
|
# Set up activity monitoring
|
|
53
73
|
setup_activity_monitoring("codex", method(:activity_callback))
|
|
54
74
|
record_activity("Starting codex execution")
|
|
@@ -94,7 +114,7 @@ module Aidp
|
|
|
94
114
|
end
|
|
95
115
|
|
|
96
116
|
# Use debug_execute_command for better debugging
|
|
97
|
-
result = debug_execute_command("codex", args: args, timeout: timeout_seconds
|
|
117
|
+
result = debug_execute_command("codex", args: args, timeout: timeout_seconds)
|
|
98
118
|
|
|
99
119
|
# Log the results
|
|
100
120
|
debug_command("codex", args: args, input: prompt, output: result.out, error: result.err, exit_code: result.exit_status)
|
|
@@ -116,6 +136,7 @@ module Aidp
|
|
|
116
136
|
raise
|
|
117
137
|
ensure
|
|
118
138
|
cleanup_activity_display(activity_display_thread, spinner)
|
|
139
|
+
@current_codex_prompt_length = nil
|
|
119
140
|
end
|
|
120
141
|
end
|
|
121
142
|
|
|
@@ -161,22 +182,17 @@ module Aidp
|
|
|
161
182
|
|
|
162
183
|
# Internal helper for send_with_options - executes with custom arguments
|
|
163
184
|
def send_with_custom_args(prompt:, args:)
|
|
185
|
+
@current_codex_prompt_length = prompt.length
|
|
164
186
|
timeout_seconds = calculate_timeout
|
|
165
187
|
|
|
166
188
|
debug_provider("codex", "Starting execution", {timeout: timeout_seconds, args: args})
|
|
167
189
|
debug_log("š Sending prompt to codex with custom args", level: :info)
|
|
168
190
|
|
|
169
|
-
# Check if streaming mode is enabled
|
|
170
|
-
streaming_enabled = ENV["AIDP_STREAMING"] == "1" || ENV["DEBUG"] == "1"
|
|
171
|
-
if streaming_enabled
|
|
172
|
-
display_message("šŗ Display streaming enabled - output buffering reduced (codex CLI does not support true streaming)", type: :info)
|
|
173
|
-
end
|
|
174
|
-
|
|
175
191
|
setup_activity_monitoring("codex", method(:activity_callback))
|
|
176
192
|
record_activity("Starting codex execution with custom args")
|
|
177
193
|
|
|
178
194
|
begin
|
|
179
|
-
result = debug_execute_command("codex", args: args, timeout: timeout_seconds
|
|
195
|
+
result = debug_execute_command("codex", args: args, timeout: timeout_seconds)
|
|
180
196
|
debug_command("codex", args: args, output: result.out, error: result.err, exit_code: result.exit_status)
|
|
181
197
|
|
|
182
198
|
if result.exit_status == 0
|
|
@@ -191,58 +207,8 @@ module Aidp
|
|
|
191
207
|
mark_failed("codex execution failed: #{e.message}")
|
|
192
208
|
debug_error(e, {provider: "codex", prompt_length: prompt.length})
|
|
193
209
|
raise
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def calculate_timeout
|
|
198
|
-
# Priority order for timeout calculation:
|
|
199
|
-
# 1. Quick mode (for testing)
|
|
200
|
-
# 2. Environment variable override
|
|
201
|
-
# 3. Adaptive timeout based on step type
|
|
202
|
-
# 4. Default timeout
|
|
203
|
-
|
|
204
|
-
if ENV["AIDP_QUICK_MODE"]
|
|
205
|
-
display_message("ā” Quick mode enabled - #{TIMEOUT_QUICK_MODE / 60} minute timeout", type: :highlight)
|
|
206
|
-
return TIMEOUT_QUICK_MODE
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
if ENV["AIDP_CODEX_TIMEOUT"]
|
|
210
|
-
return ENV["AIDP_CODEX_TIMEOUT"].to_i
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
if adaptive_timeout
|
|
214
|
-
display_message("š§ Using adaptive timeout: #{adaptive_timeout} seconds", type: :info)
|
|
215
|
-
return adaptive_timeout
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
# Default timeout
|
|
219
|
-
display_message("š Using default timeout: #{TIMEOUT_DEFAULT / 60} minutes", type: :info)
|
|
220
|
-
TIMEOUT_DEFAULT
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
def adaptive_timeout
|
|
224
|
-
@adaptive_timeout ||= begin
|
|
225
|
-
# Timeout recommendations based on step type patterns
|
|
226
|
-
step_name = ENV["AIDP_CURRENT_STEP"] || ""
|
|
227
|
-
|
|
228
|
-
case step_name
|
|
229
|
-
when /REPOSITORY_ANALYSIS/
|
|
230
|
-
TIMEOUT_REPOSITORY_ANALYSIS
|
|
231
|
-
when /ARCHITECTURE_ANALYSIS/
|
|
232
|
-
TIMEOUT_ARCHITECTURE_ANALYSIS
|
|
233
|
-
when /TEST_ANALYSIS/
|
|
234
|
-
TIMEOUT_TEST_ANALYSIS
|
|
235
|
-
when /FUNCTIONALITY_ANALYSIS/
|
|
236
|
-
TIMEOUT_FUNCTIONALITY_ANALYSIS
|
|
237
|
-
when /DOCUMENTATION_ANALYSIS/
|
|
238
|
-
TIMEOUT_DOCUMENTATION_ANALYSIS
|
|
239
|
-
when /STATIC_ANALYSIS/
|
|
240
|
-
TIMEOUT_STATIC_ANALYSIS
|
|
241
|
-
when /REFACTORING_RECOMMENDATIONS/
|
|
242
|
-
TIMEOUT_REFACTORING_RECOMMENDATIONS
|
|
243
|
-
else
|
|
244
|
-
nil # Use default
|
|
245
|
-
end
|
|
210
|
+
ensure
|
|
211
|
+
@current_codex_prompt_length = nil
|
|
246
212
|
end
|
|
247
213
|
end
|
|
248
214
|
|
|
@@ -257,6 +223,22 @@ module Aidp
|
|
|
257
223
|
display_message("\nā Codex CLI failed: #{message}", type: :error)
|
|
258
224
|
end
|
|
259
225
|
end
|
|
226
|
+
|
|
227
|
+
def calculate_timeout
|
|
228
|
+
env_override = ENV["AIDP_CODEX_TIMEOUT"]
|
|
229
|
+
return env_override.to_i if env_override&.match?(/^\d+$/)
|
|
230
|
+
|
|
231
|
+
base_timeout = super
|
|
232
|
+
|
|
233
|
+
prompt_length = @current_codex_prompt_length
|
|
234
|
+
return base_timeout unless prompt_length && prompt_length >= LONG_PROMPT_THRESHOLD
|
|
235
|
+
|
|
236
|
+
extended_timeout = [base_timeout, LONG_PROMPT_TIMEOUT].max
|
|
237
|
+
if extended_timeout > base_timeout
|
|
238
|
+
display_message("ā±ļø Codex prompt length #{prompt_length} detected - extending timeout to #{extended_timeout} seconds", type: :info)
|
|
239
|
+
end
|
|
240
|
+
extended_timeout
|
|
241
|
+
end
|
|
260
242
|
end
|
|
261
243
|
end
|
|
262
244
|
end
|