Class: Aidp::Providers::Anthropic

Inherits:
Base
  • Object
show all
Includes:
DebugMixin
Defined in:
lib/aidp/providers/anthropic.rb

Constant Summary collapse

MODEL_PATTERN =

Model name pattern for Anthropic Claude models

/^claude-[\d.-]+-(?:opus|sonnet|haiku)(?:-\d{8})?$/i

Constants included from DebugMixin

DebugMixin::DEBUG_BASIC, DebugMixin::DEBUG_OFF, DebugMixin::DEBUG_VERBOSE

Constants inherited from Base

Base::ACTIVITY_STATES, Base::DEFAULT_STUCK_TIMEOUT, Base::TIMEOUT_ARCHITECTURE_ANALYSIS, Base::TIMEOUT_DEFAULT, Base::TIMEOUT_DOCUMENTATION_ANALYSIS, Base::TIMEOUT_FUNCTIONALITY_ANALYSIS, Base::TIMEOUT_IMPLEMENTATION, Base::TIMEOUT_QUICK_MODE, Base::TIMEOUT_REFACTORING_RECOMMENDATIONS, Base::TIMEOUT_REPOSITORY_ANALYSIS, Base::TIMEOUT_STATIC_ANALYSIS, Base::TIMEOUT_TEST_ANALYSIS

Constants included from MessageDisplay

MessageDisplay::COLOR_MAP

Instance Attribute Summary

Attributes inherited from Base

#activity_state, #last_activity_time, #model, #start_time, #step_name, #stuck_timeout

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DebugMixin

#debug_basic?, #debug_command, #debug_enabled?, #debug_error, #debug_execute_command, #debug_level, #debug_log, #debug_logger, #debug_provider, #debug_step, #debug_timing, #debug_verbose?, included, shared_logger

Methods inherited from Base

#activity_summary, #configure, discover_models_from_registry, #execution_time, #harness_config, #harness_health_status, #harness_healthy?, #harness_metrics, #harness_mode?, #initialize, #mark_completed, #mark_failed, #record_activity, #record_harness_request, #send_with_harness, #set_harness_context, #set_job_context, #setup_activity_monitoring, #stuck?, #supports_activity_monitoring?, #time_since_last_activity, #update_activity_state

Methods included from Adapter

#classify_error, #dangerous_mode=, #dangerous_mode_enabled?, #error_metadata, #health_status, #logging_metadata, #redact_secrets, #retryable_error?, #validate_config

Methods included from MessageDisplay

#display_message, included, #message_display_prompt

Constructor Details

This class inherits a constructor from Aidp::Providers::Base

Class Method Details

.available?Boolean

Returns:

  • (Boolean)


15
16
17
# File 'lib/aidp/providers/anthropic.rb', line 15

def self.available?
  !!Aidp::Util.which("claude")
end

.discover_modelsArray<Hash>

Discover available models from Claude CLI

Returns:

  • (Array<Hash>)

    Array of discovered models



52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/aidp/providers/anthropic.rb', line 52

def self.discover_models
  return [] unless available?

  begin
    require "open3"
    output, _, status = Open3.capture3("claude", "models", "list", {timeout: 10})
    return [] unless status.success?

    parse_models_list(output)
  rescue => e
    Aidp.log_debug("anthropic_provider", "discovery failed", error: e.message)
    []
  end
end

.firewall_requirementsObject

Get firewall requirements for Anthropic provider



68
69
70
71
72
73
74
75
76
77
# File 'lib/aidp/providers/anthropic.rb', line 68

def self.firewall_requirements
  {
    domains: [
      "api.anthropic.com",
      "claude.ai",
      "console.anthropic.com"
    ],
    ip_ranges: []
  }
end

.model_family(provider_model_name) ⇒ String

Normalize a provider-specific model name to its model family

Anthropic uses date-versioned models (e.g., “claude-3-5-sonnet-20241022”). This method strips the date suffix to get the family name.

Parameters:

  • provider_model_name (String)

    The versioned model name

Returns:

  • (String)

    The model family name (e.g., “claude-3-5-sonnet”)



26
27
28
29
# File 'lib/aidp/providers/anthropic.rb', line 26

def self.model_family(provider_model_name)
  # Strip date suffix: "claude-3-5-sonnet-20241022" → "claude-3-5-sonnet"
  provider_model_name.sub(/-\d{8}$/, "")
end

.provider_model_name(family_name) ⇒ String

Convert a model family name to the provider’s preferred model name

Returns the family name as-is. Users can configure specific versions in aidp.yml.

Parameters:

  • family_name (String)

    The model family name

Returns:

  • (String)

    The model name (same as family for flexibility)



37
38
39
# File 'lib/aidp/providers/anthropic.rb', line 37

def self.provider_model_name(family_name)
  family_name
end

.supports_model_family?(family_name) ⇒ Boolean

Check if this provider supports a given model family

Parameters:

  • family_name (String)

    The model family name

Returns:

  • (Boolean)

    True if it matches Claude model pattern



45
46
47
# File 'lib/aidp/providers/anthropic.rb', line 45

def self.supports_model_family?(family_name)
  MODEL_PATTERN.match?(family_name)
end

Instance Method Details

#available?Boolean

Returns:

  • (Boolean)


188
189
190
# File 'lib/aidp/providers/anthropic.rb', line 188

def available?
  self.class.available?
end

#capabilitiesObject

ProviderAdapter interface methods



194
195
196
197
198
199
200
201
202
203
# File 'lib/aidp/providers/anthropic.rb', line 194

def capabilities
  {
    reasoning_tiers: ["mini", "standard", "thinking"],
    context_window: 200_000,
    supports_json_mode: true,
    supports_tool_use: true,
    supports_vision: false,
    supports_file_upload: true
  }
end

#dangerous_mode_flagsObject



209
210
211
# File 'lib/aidp/providers/anthropic.rb', line 209

def dangerous_mode_flags
  ["--dangerously-skip-permissions"]
end

#display_nameObject



165
166
167
# File 'lib/aidp/providers/anthropic.rb', line 165

def display_name
  "Anthropic Claude CLI"
end

#error_patternsObject



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/aidp/providers/anthropic.rb', line 213

def error_patterns
  {
    rate_limited: [
      /rate.?limit/i,
      /too.?many.?requests/i,
      /429/,
      /overloaded/i
    ],
    auth_expired: [
      /oauth.*token.*expired/i,
      /authentication.*error/i,
      /invalid.*api.*key/i,
      /unauthorized/i,
      /401/
    ],
    quota_exceeded: [
      /quota.*exceeded/i,
      /usage.*limit/i,
      /credit.*exhausted/i
    ],
    transient: [
      /timeout/i,
      /connection.*reset/i,
      /temporary.*error/i,
      /service.*unavailable/i,
      /503/,
      /502/,
      /504/
    ],
    permanent: [
      /invalid.*model/i,
      /unsupported.*operation/i,
      /not.*found/i,
      /404/,
      /bad.*request/i,
      /400/
    ]
  }
end

#fetch_mcp_serversObject



173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/aidp/providers/anthropic.rb', line 173

def fetch_mcp_servers
  return [] unless self.class.available?

  begin
    # Use claude mcp list command
    result = debug_execute_command("claude", args: ["mcp", "list"], timeout: 5)
    return [] unless result.exit_status == 0

    parse_claude_mcp_output(result.out)
  rescue => e
    debug_log("Failed to fetch MCP servers via Claude CLI: #{e.message}", level: :debug)
    []
  end
end

#nameObject



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

def name
  "anthropic"
end

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



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
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
# File 'lib/aidp/providers/anthropic.rb', line 253

def send_message(prompt:, session: nil)
  raise "claude CLI not available" unless self.class.available?

  # Smart timeout calculation
  timeout_seconds = calculate_timeout

  debug_provider("claude", "Starting execution", {timeout: timeout_seconds})
  debug_log("📝 Sending prompt to claude...", level: :info)

  # Build command arguments
  args = ["--print", "--output-format=text"]

  # Add model if specified
  if @model && !@model.empty?
    args << "--model" << @model
  end

  # Check if we should skip permissions (devcontainer support)
  if should_skip_permissions?
    args << "--dangerously-skip-permissions"
    debug_log("🔓 Running with elevated permissions (devcontainer mode)", level: :info)
  end

  begin
    result = debug_execute_command("claude", args: args, input: prompt, timeout: timeout_seconds)

    # Log the results
    debug_command("claude", args: args, input: prompt, output: result.out, error: result.err, exit_code: result.exit_status)

    if result.exit_status == 0
      result.out
    else
      # Detect issues in stdout/stderr (Claude sometimes prints to stdout)
      combined = [result.out, result.err].compact.join("\n")

      # Check for rate limit (Session limit reached)
      if combined.match?(/session limit reached/i)
        Aidp.log_debug("anthropic_provider", "rate_limit_detected",
          exit_code: result.exit_status,
          message: combined)
        notify_rate_limit(combined)
        error_message = "Rate limit reached for Claude CLI.\n#{combined}"
        debug_error(StandardError.new(error_message), {exit_code: result.exit_status, stdout: result.out, stderr: result.err})
        raise error_message
      end

      # Check for auth issues
      if combined.downcase.include?("oauth token has expired") || combined.downcase.include?("authentication_error")
        error_message = "Authentication error from Claude CLI: token expired or invalid.\n" \
                        "Run 'claude /login' or refresh credentials.\n" \
                        "Note: Model discovery requires valid authentication."
        debug_error(StandardError.new(error_message), {exit_code: result.exit_status, stdout: result.out, stderr: result.err})
        raise error_message
      end

      debug_error(StandardError.new("claude failed"), {exit_code: result.exit_status, stderr: result.err})
      raise "claude failed with exit code #{result.exit_status}: #{result.err}"
    end
  rescue => e
    debug_error(e, {provider: "claude", prompt_length: prompt.length})
    raise
  end
end

#supports_dangerous_mode?Boolean

Returns:

  • (Boolean)


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

def supports_dangerous_mode?
  true
end

#supports_mcp?Boolean

Returns:

  • (Boolean)


169
170
171
# File 'lib/aidp/providers/anthropic.rb', line 169

def supports_mcp?
  true
end