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.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -14
  3. data/lib/aidp/agent_harness_adapter.rb +309 -0
  4. data/lib/aidp/cli/issue_importer.rb +20 -6
  5. data/lib/aidp/cli/jobs_command.rb +1 -1
  6. data/lib/aidp/cli/models_command.rb +9 -16
  7. data/lib/aidp/cli/prompts_command.rb +253 -0
  8. data/lib/aidp/cli/security_command.rb +4 -10
  9. data/lib/aidp/cli/storage_command.rb +277 -0
  10. data/lib/aidp/cli/temporal_command.rb +460 -0
  11. data/lib/aidp/cli.rb +24 -2
  12. data/lib/aidp/config.rb +15 -0
  13. data/lib/aidp/config_paths.rb +12 -2
  14. data/lib/aidp/database/migrations.rb +114 -0
  15. data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
  16. data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
  17. data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
  18. data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
  19. data/lib/aidp/database/repositories/job_repository.rb +278 -0
  20. data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
  21. data/lib/aidp/database/repositories/progress_repository.rb +210 -0
  22. data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
  23. data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
  24. data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
  25. data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
  26. data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
  27. data/lib/aidp/database/repositories/task_repository.rb +231 -0
  28. data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
  29. data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
  30. data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
  31. data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
  32. data/lib/aidp/database/repository.rb +166 -0
  33. data/lib/aidp/database/schema.rb +347 -0
  34. data/lib/aidp/database/storage_migrator.rb +693 -0
  35. data/lib/aidp/database.rb +139 -0
  36. data/lib/aidp/debug_mixin.rb +2 -2
  37. data/lib/aidp/execute/checkpoint.rb +26 -0
  38. data/lib/aidp/execute/checkpoint_display.rb +16 -0
  39. data/lib/aidp/execute/prompt_evaluator.rb +30 -57
  40. data/lib/aidp/execute/work_loop_runner.rb +294 -46
  41. data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
  42. data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
  43. data/lib/aidp/harness/ai_decision_engine.rb +78 -296
  44. data/lib/aidp/harness/config_schema.rb +59 -0
  45. data/lib/aidp/harness/configuration.rb +229 -10
  46. data/lib/aidp/harness/deprecation_cache.rb +53 -11
  47. data/lib/aidp/harness/enhanced_runner.rb +1 -1
  48. data/lib/aidp/harness/error_handler.rb +11 -5
  49. data/lib/aidp/harness/provider_config.rb +36 -0
  50. data/lib/aidp/harness/provider_factory.rb +26 -24
  51. data/lib/aidp/harness/provider_info.rb +9 -5
  52. data/lib/aidp/harness/provider_manager.rb +2 -2
  53. data/lib/aidp/harness/provider_metrics.rb +103 -1
  54. data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
  55. data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
  56. data/lib/aidp/harness/runner.rb +7 -2
  57. data/lib/aidp/harness/state_manager.rb +1 -1
  58. data/lib/aidp/harness/test_runner.rb +167 -11
  59. data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
  60. data/lib/aidp/harness/usage_limit.rb +179 -0
  61. data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
  62. data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
  63. data/lib/aidp/harness/usage_period.rb +234 -0
  64. data/lib/aidp/init/agent_instructions_generator.rb +317 -0
  65. data/lib/aidp/init/runner.rb +25 -4
  66. data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
  67. data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
  68. data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
  69. data/lib/aidp/interfaces/logger_interface.rb +150 -0
  70. data/lib/aidp/interfaces/ui_interface.rb +254 -0
  71. data/lib/aidp/logger.rb +25 -7
  72. data/lib/aidp/orchestration_adapter.rb +279 -0
  73. data/lib/aidp/pr_worktree_manager.rb +4 -15
  74. data/lib/aidp/prompts/feedback_collector.rb +198 -0
  75. data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
  76. data/lib/aidp/prompts/template_evolver.rb +305 -0
  77. data/lib/aidp/prompts/template_version_manager.rb +325 -0
  78. data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
  79. data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
  80. data/lib/aidp/setup/wizard.rb +616 -176
  81. data/lib/aidp/shell_executor.rb +58 -0
  82. data/lib/aidp/style_guide/selector.rb +56 -1
  83. data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
  84. data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
  85. data/lib/aidp/temporal/activities/base_activity.rb +78 -0
  86. data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
  87. data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
  88. data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
  89. data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
  90. data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
  91. data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
  92. data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
  93. data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
  94. data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
  95. data/lib/aidp/temporal/configuration.rb +169 -0
  96. data/lib/aidp/temporal/connection.rb +93 -0
  97. data/lib/aidp/temporal/worker.rb +119 -0
  98. data/lib/aidp/temporal/workflow_client.rb +171 -0
  99. data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
  100. data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
  101. data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
  102. data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
  103. data/lib/aidp/temporal.rb +120 -0
  104. data/lib/aidp/version.rb +1 -1
  105. data/lib/aidp/watch/base_processor.rb +35 -0
  106. data/lib/aidp/watch/build_processor.rb +132 -0
  107. data/lib/aidp/watch/github_state_extractor.rb +0 -8
  108. data/lib/aidp/watch/rebase_processor.rb +245 -0
  109. data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
  110. data/lib/aidp/watch/runner.rb +366 -285
  111. data/lib/aidp/watch/state_store.rb +80 -0
  112. data/lib/aidp/watch/work_item.rb +96 -0
  113. data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
  114. data/lib/aidp/watch/worktree_reconciler.rb +643 -0
  115. data/lib/aidp/workflows/guided_agent.rb +1 -1
  116. data/lib/aidp/worktree.rb +62 -0
  117. data/templates/aidp.yml.example +70 -0
  118. metadata +117 -16
  119. data/lib/aidp/analyze/json_file_storage.rb +0 -292
  120. data/lib/aidp/execute/future_work_backlog.rb +0 -411
  121. data/lib/aidp/providers/adapter.rb +0 -239
  122. data/lib/aidp/providers/aider.rb +0 -264
  123. data/lib/aidp/providers/anthropic.rb +0 -614
  124. data/lib/aidp/providers/base.rb +0 -617
  125. data/lib/aidp/providers/capability_registry.rb +0 -204
  126. data/lib/aidp/providers/codex.rb +0 -267
  127. data/lib/aidp/providers/cursor.rb +0 -304
  128. data/lib/aidp/providers/error_taxonomy.rb +0 -195
  129. data/lib/aidp/providers/gemini.rb +0 -110
  130. data/lib/aidp/providers/github_copilot.rb +0 -227
  131. data/lib/aidp/providers/kilocode.rb +0 -157
  132. data/lib/aidp/providers/opencode.rb +0 -139
@@ -1,304 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "timeout"
4
- require_relative "base"
5
- require_relative "../util"
6
- require_relative "../debug_mixin"
7
-
8
- module Aidp
9
- module Providers
10
- class Cursor < Base
11
- include Aidp::DebugMixin
12
-
13
- def self.available?
14
- !!Aidp::Util.which("cursor-agent")
15
- end
16
-
17
- # Normalize Cursor's model name to family name
18
- #
19
- # Cursor may use different naming conventions (e.g., dots vs hyphens)
20
- #
21
- # @param provider_model_name [String] Cursor's model name
22
- # @return [String] The normalized family name
23
- def self.model_family(provider_model_name)
24
- # Normalize cursor naming to standard family names
25
- # cursor uses dots: "claude-3.5-sonnet" -> "claude-3-5-sonnet"
26
- provider_model_name.gsub(/(\d)\.(\d)/, '\1-\2')
27
- end
28
-
29
- # Convert family name to Cursor's naming convention
30
- #
31
- # @param family_name [String] The model family name
32
- # @return [String] Cursor's model name
33
- def self.provider_model_name(family_name)
34
- # Cursor uses dots for version numbers
35
- # "claude-3-5-sonnet" -> "claude-3.5-sonnet"
36
- family_name.gsub(/(\d)-(\d)/, '\1.\2')
37
- end
38
-
39
- # Check if this provider supports a given model family
40
- #
41
- # Cursor supports Claude, GPT, and Cursor-specific models
42
- #
43
- # @param family_name [String] The model family name
44
- # @return [Boolean] True if likely supported
45
- def self.supports_model_family?(family_name)
46
- family_name.match?(/^(claude|gpt|cursor)-/)
47
- end
48
-
49
- # Get firewall requirements for Cursor provider
50
- def self.firewall_requirements
51
- {
52
- domains: [
53
- "cursor.com",
54
- "www.cursor.com",
55
- "downloads.cursor.com",
56
- "api.cursor.sh",
57
- "cursor.sh",
58
- "app.cursor.sh",
59
- "www.cursor.sh",
60
- # Authentication (Auth0)
61
- "auth.cursor.sh",
62
- "auth0.com",
63
- "*.auth0.com",
64
- "a0core.net",
65
- "*.a0core.net"
66
- ],
67
- ip_ranges: []
68
- }
69
- end
70
-
71
- # Discover available models from Cursor
72
- #
73
- # Note: Cursor doesn't have a public model listing API
74
- # Returns registry-based models that match Cursor patterns
75
- #
76
- # @return [Array<Hash>] Array of discovered models
77
- def self.discover_models
78
- return [] unless available?
79
-
80
- begin
81
- require_relative "../harness/model_registry"
82
- registry = Aidp::Harness::ModelRegistry.new
83
-
84
- # Get all models from registry that Cursor might support
85
- models = registry.all_families.filter_map do |family|
86
- next unless supports_model_family?(family)
87
-
88
- info = registry.get_model_info(family)
89
- next unless info
90
-
91
- {
92
- name: provider_model_name(family),
93
- family: family,
94
- tier: info["tier"],
95
- capabilities: info["capabilities"] || [],
96
- context_window: info["context_window"],
97
- provider: "cursor"
98
- }
99
- end
100
-
101
- Aidp.log_info("cursor_provider", "using registry models", count: models.size)
102
- models
103
- rescue => e
104
- Aidp.log_debug("cursor_provider", "discovery failed", error: e.message)
105
- []
106
- end
107
- end
108
-
109
- def name
110
- "cursor"
111
- end
112
-
113
- def display_name
114
- "Cursor AI"
115
- end
116
-
117
- def supports_mcp?
118
- true
119
- end
120
-
121
- def fetch_mcp_servers
122
- # Try cursor-agent CLI first, then fallback to config file
123
- fetch_mcp_servers_cli || fetch_mcp_servers_config
124
- end
125
-
126
- def send_message(prompt:, session: nil, options: {})
127
- raise "cursor-agent not available" unless self.class.available?
128
-
129
- # Smart timeout calculation
130
- timeout_seconds = calculate_timeout
131
-
132
- debug_provider("cursor", "Starting execution", {timeout: timeout_seconds})
133
- debug_log("📝 Sending prompt to cursor-agent (length: #{prompt.length})", level: :info)
134
-
135
- # Set up activity monitoring
136
- setup_activity_monitoring("cursor-agent", method(:activity_callback))
137
- record_activity("Starting cursor-agent execution")
138
-
139
- # Create a spinner for activity display
140
- spinner = TTY::Spinner.new("[:spinner] :title", format: :dots, hide_cursor: true)
141
- spinner.auto_spin
142
-
143
- activity_display_thread = Thread.new do
144
- start_time = Time.now
145
- loop do
146
- sleep 0.5 # Update every 500ms to reduce spam
147
- elapsed = Time.now - start_time
148
-
149
- # Break if we've been running too long or state changed
150
- break if elapsed > timeout_seconds || @activity_state == :completed || @activity_state == :failed
151
-
152
- update_spinner_status(spinner, elapsed, "🔄 cursor-agent")
153
- end
154
- end
155
-
156
- begin
157
- # Use debug_execute_command for better debugging
158
- # Use -p mode (designed for non-interactive/script use)
159
- # No fallback to interactive modes - they would hang AIDP's automation workflow
160
- result = debug_execute_command("cursor-agent", args: ["-p"], input: prompt, timeout: timeout_seconds)
161
-
162
- # Log the results
163
- debug_command("cursor-agent", args: ["-p"], input: prompt, output: result.out, error: result.err, exit_code: result.exit_status)
164
-
165
- if result.exit_status == 0
166
- spinner.success("✓")
167
- mark_completed
168
- result.out
169
- else
170
- spinner.error("✗")
171
- mark_failed("cursor-agent failed with exit code #{result.exit_status}")
172
- debug_error(StandardError.new("cursor-agent failed"), {exit_code: result.exit_status, stderr: result.err})
173
- raise "cursor-agent failed with exit code #{result.exit_status}: #{result.err}"
174
- end
175
- rescue => e
176
- spinner&.error("✗")
177
- mark_failed("cursor-agent execution failed: #{e.message}")
178
- debug_error(e, {provider: "cursor", prompt_length: prompt.length})
179
- raise
180
- ensure
181
- cleanup_activity_display(activity_display_thread, spinner)
182
- end
183
- end
184
-
185
- private
186
-
187
- def activity_callback(state, message, provider)
188
- # This is now handled by the animated display thread
189
- # Only print static messages for state changes
190
- case state
191
- when :stuck
192
- display_message("\n⚠️ cursor appears stuck: #{message}", type: :warning)
193
- when :completed
194
- display_message("\n✅ cursor completed: #{message}", type: :success)
195
- when :failed
196
- display_message("\n❌ cursor failed: #{message}", type: :error)
197
- end
198
- end
199
-
200
- # Try to get MCP servers via cursor-agent CLI
201
- def fetch_mcp_servers_cli
202
- return nil unless self.class.available?
203
-
204
- begin
205
- # Try cursor-agent mcp list (if such command exists)
206
- result = debug_execute_command("cursor-agent", args: ["mcp", "list"], timeout: 5)
207
- return nil unless result.exit_status == 0
208
-
209
- parse_mcp_servers_output(result.out)
210
- rescue => e
211
- debug_log("Failed to fetch MCP servers via CLI: #{e.message}", level: :debug)
212
- nil
213
- end
214
- end
215
-
216
- # Fallback to reading Cursor's config file
217
- def fetch_mcp_servers_config
218
- cursor_config_path = File.expand_path("~/.cursor/mcp.json")
219
- return [] unless File.exist?(cursor_config_path)
220
-
221
- begin
222
- require "json"
223
- config_content = File.read(cursor_config_path)
224
- config = JSON.parse(config_content)
225
-
226
- servers = []
227
- mcp_servers = config["mcpServers"] || {}
228
-
229
- mcp_servers.each do |name, server_config|
230
- # Build command description
231
- command_parts = [server_config["command"]]
232
- command_parts.concat(server_config["args"]) if server_config["args"]
233
- command_description = command_parts.join(" ")
234
-
235
- servers << {
236
- name: name,
237
- status: "configured",
238
- description: command_description,
239
- enabled: true,
240
- source: "cursor_config",
241
- env_vars: server_config["env"] || {}
242
- }
243
- end
244
-
245
- servers
246
- rescue JSON::ParserError => e
247
- debug_log("Failed to parse Cursor MCP configuration: #{e.message}", level: :debug)
248
- []
249
- rescue => e
250
- debug_log("Failed to parse Cursor MCP configuration: #{e.message}", level: :debug)
251
- []
252
- end
253
- end
254
-
255
- # Parse MCP server output from CLI commands
256
- def parse_mcp_servers_output(output)
257
- servers = []
258
- return servers unless output
259
-
260
- lines = output.lines
261
- lines.reject! { |line| /checking mcp server health/i.match?(line) }
262
-
263
- lines.each do |line|
264
- line = line.strip
265
- next if line.empty?
266
-
267
- # Try to parse cursor-agent format: "name: status"
268
- if line =~ /^([^:]+):\s*(.+)$/
269
- name = Regexp.last_match(1).strip
270
- status = Regexp.last_match(2).strip
271
-
272
- servers << {
273
- name: name,
274
- status: status,
275
- description: "MCP server managed by cursor-agent",
276
- enabled: status == "ready" || status == "connected",
277
- source: "cursor_cli"
278
- }
279
- next
280
- end
281
-
282
- # Also try to parse extended format: "name: command - ✓ Connected" (for future compatibility)
283
- if line =~ /^([^:]+):\s*(.+?)\s*-\s*(✓|✗)\s*(.+)$/
284
- name = Regexp.last_match(1).strip
285
- command = Regexp.last_match(2).strip
286
- status_symbol = Regexp.last_match(3)
287
- status_text = Regexp.last_match(4).strip
288
-
289
- servers << {
290
- name: name,
291
- status: (status_symbol == "✓") ? "connected" : "error",
292
- description: command,
293
- enabled: status_symbol == "✓",
294
- error: (status_symbol == "✗") ? status_text : nil,
295
- source: "cursor_cli"
296
- }
297
- end
298
- end
299
-
300
- servers
301
- end
302
- end
303
- end
304
- end
@@ -1,195 +0,0 @@
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
@@ -1,110 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base"
4
- require_relative "../debug_mixin"
5
-
6
- module Aidp
7
- module Providers
8
- class Gemini < Base
9
- include Aidp::DebugMixin
10
-
11
- # Model name pattern for Gemini models
12
- MODEL_PATTERN = /^gemini-[\d.]+-(?:pro|flash|ultra)(?:-\d+)?$/i
13
-
14
- def self.available?
15
- !!Aidp::Util.which("gemini")
16
- end
17
-
18
- # Normalize a provider-specific model name to its model family
19
- #
20
- # Gemini may use version suffixes (e.g., "gemini-1.5-pro-001").
21
- # This method strips version suffixes to get the family name.
22
- #
23
- # @param provider_model_name [String] The model name
24
- # @return [String] The model family name
25
- def self.model_family(provider_model_name)
26
- # Strip version suffix: "gemini-1.5-pro-001" → "gemini-1.5-pro"
27
- provider_model_name.sub(/-\d+$/, "")
28
- end
29
-
30
- # Convert a model family name to the provider's preferred model name
31
- #
32
- # @param family_name [String] The model family name
33
- # @return [String] The provider-specific model name (same as family)
34
- def self.provider_model_name(family_name)
35
- family_name
36
- end
37
-
38
- # Check if this provider supports a given model family
39
- #
40
- # @param family_name [String] The model family name
41
- # @return [Boolean] True if it matches Gemini model pattern
42
- def self.supports_model_family?(family_name)
43
- MODEL_PATTERN.match?(family_name)
44
- end
45
-
46
- # Discover available models from Gemini
47
- #
48
- # Note: Gemini CLI doesn't have a standard model listing command
49
- # Returns registry-based models that match Gemini patterns
50
- #
51
- # @return [Array<Hash>] Array of discovered models
52
- def self.discover_models
53
- return [] unless available?
54
-
55
- discover_models_from_registry(MODEL_PATTERN, "gemini")
56
- end
57
-
58
- # Get firewall requirements for Gemini provider
59
- def self.firewall_requirements
60
- {
61
- domains: [
62
- "generativelanguage.googleapis.com",
63
- "oauth2.googleapis.com",
64
- "accounts.google.com",
65
- "www.googleapis.com"
66
- ],
67
- ip_ranges: []
68
- }
69
- end
70
-
71
- def name
72
- "gemini"
73
- end
74
-
75
- def display_name
76
- "Google Gemini"
77
- end
78
-
79
- def send_message(prompt:, session: nil, options: {})
80
- raise "gemini CLI not available" unless self.class.available?
81
-
82
- # Smart timeout calculation
83
- timeout_seconds = calculate_timeout
84
-
85
- debug_provider("gemini", "Starting execution", {timeout: timeout_seconds})
86
- debug_log("📝 Sending prompt to gemini...", level: :info)
87
-
88
- begin
89
- command_args = ["--prompt", prompt]
90
- result = debug_execute_command("gemini", args: command_args, timeout: timeout_seconds)
91
-
92
- # Log the results
93
- debug_command("gemini", args: command_args, input: nil, output: result.out, error: result.err, exit_code: result.exit_status)
94
-
95
- if result.exit_status == 0
96
- result.out
97
- else
98
- debug_error(StandardError.new("gemini failed"), {exit_code: result.exit_status, stderr: result.err})
99
- raise "gemini failed with exit code #{result.exit_status}: #{result.err}"
100
- end
101
- rescue => e
102
- debug_error(e, {provider: "gemini", prompt_length: prompt.length})
103
- raise
104
- end
105
- end
106
-
107
- private
108
- end
109
- end
110
- end