Class: Providers::Anthropic

Inherits:
Object
  • Object
show all
Includes:
HTTParty
Defined in:
lib/providers/anthropic.rb

Defined Under Namespace

Classes: AuthenticationError, Error, RateLimitError, ServerError, TokenFormatError, TransientError

Constant Summary collapse

TOKEN_PREFIX =
"sk-ant-oat01-"
TOKEN_MIN_LENGTH =
80
API_VERSION =
"2023-06-01"
REQUIRED_BETA =
"oauth-2025-04-20"
VALIDATION_MODEL =
"claude-sonnet-4-20250514"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(token = nil) ⇒ Anthropic

Returns a new instance of Anthropic.



69
70
71
# File 'lib/providers/anthropic.rb', line 69

def initialize(token = nil)
  @token = token || self.class.fetch_token
end

Instance Attribute Details

#tokenObject (readonly)

Returns the value of attribute token.



67
68
69
# File 'lib/providers/anthropic.rb', line 67

def token
  @token
end

Class Method Details

.fetch_tokenObject



35
36
37
38
39
40
41
42
43
44
45
# File 'lib/providers/anthropic.rb', line 35

def fetch_token
  token = Rails.application.credentials.dig(:anthropic, :subscription_token)
  raise AuthenticationError, <<~MSG.strip if token.blank?
    No Anthropic subscription token found in credentials.
    Run: bin/rails credentials:edit
    Add:
      anthropic:
        subscription_token: sk-ant-oat01-YOUR_TOKEN_HERE
  MSG
  token
end

.validate!Object



28
29
30
31
32
33
# File 'lib/providers/anthropic.rb', line 28

def validate!
  token = fetch_token
  validate_token_format!(token)
  validate_token_api!(token)
  true
end

.validate_token_api!(token) ⇒ Object



61
62
63
64
# File 'lib/providers/anthropic.rb', line 61

def validate_token_api!(token)
  provider = new(token)
  provider.validate_credentials!
end

.validate_token_format!(token) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/providers/anthropic.rb', line 47

def validate_token_format!(token)
  unless token.start_with?(TOKEN_PREFIX)
    raise TokenFormatError,
      "Token must start with '#{TOKEN_PREFIX}'. Got: '#{token[0..12]}...'"
  end

  unless token.length >= TOKEN_MIN_LENGTH
    raise TokenFormatError,
      "Token must be at least #{TOKEN_MIN_LENGTH} characters (got #{token.length})"
  end

  true
end

Instance Method Details

#count_tokens(model:, messages:, **options) ⇒ Integer

Count tokens in a message payload without creating a message. Uses the free Anthropic token counting endpoint.

Parameters:

  • model (String)

    Anthropic model identifier

  • messages (Array<Hash>)

    conversation messages

  • options (Hash)

    additional parameters (e.g. system:, tools:)

Returns:

  • (Integer)

    estimated input token count

Raises:

  • (Error)

    on API errors



95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/providers/anthropic.rb', line 95

def count_tokens(model:, messages:, **options)
  body = {model: model, messages: messages}.merge(options)

  response = self.class.post(
    "/v1/messages/count_tokens",
    body: body.to_json,
    headers: request_headers
  )

  result = handle_response(response)
  result["input_tokens"]
rescue Errno::ECONNRESET, Net::ReadTimeout, Net::OpenTimeout, SocketError, EOFError => e
  raise TransientError, "#{e.class}: #{e.message}"
end

#create_message(model:, messages:, max_tokens:, **options) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/providers/anthropic.rb', line 73

def create_message(model:, messages:, max_tokens:, **options)
  body = {model: model, messages: messages, max_tokens: max_tokens}.merge(options)

  response = self.class.post(
    "/v1/messages",
    body: body.to_json,
    headers: request_headers
  )

  handle_response(response)
rescue Errno::ECONNRESET, Net::ReadTimeout, Net::OpenTimeout, SocketError, EOFError => e
  raise TransientError, "#{e.class}: #{e.message}"
end

#validate_credentials!Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/providers/anthropic.rb', line 110

def validate_credentials!
  response = self.class.post(
    "/v1/messages",
    body: {
      model: VALIDATION_MODEL,
      messages: [{role: "user", content: "Hi"}],
      max_tokens: 1
    }.to_json,
    headers: request_headers
  )

  case response.code
  when 200
    true
  when 401
    raise AuthenticationError,
      "Token rejected by Anthropic API (401). Re-run `claude setup-token` and update credentials."
  when 403
    raise AuthenticationError,
      "Token not authorized for API access (403). This credential may be restricted to Claude Code only."
  else
    handle_response(response)
  end
end