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"
OAUTH_PASSPHRASE =

Anthropic requires this exact string as the first system block for OAuth subscription tokens on Sonnet/Opus. Without it, /v1/messages returns 400.

"You are Claude Code, Anthropic's official CLI for Claude."

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(token = nil) ⇒ Anthropic

Returns a new instance of Anthropic.



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

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

Instance Attribute Details

#tokenObject (readonly)

Returns the value of attribute token.



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

def token
  @token
end

Class Method Details

.fetch_tokenObject



30
31
32
33
34
35
36
37
38
39
# File 'lib/providers/anthropic.rb', line 30

def fetch_token
  token = CredentialStore.read("anthropic", "subscription_token")
  return token if token.present?
  return "sk-ant-oat01-#{"0" * 68}" if ENV["CI"]

  raise AuthenticationError, <<~MSG.strip
    No Anthropic subscription token found in credentials.
    Use the TUI token setup (Ctrl+a → a) to configure your token.
  MSG
end

.validate_token_api!(token) ⇒ true

Validate a token against the live Anthropic API. Delegates to #validate_credentials! on a throwaway instance.

Parameters:

  • token (String)

    Anthropic API token to validate

Returns:

  • (true)

    when the API accepts the token

Raises:



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

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

.validate_token_format!(token) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/providers/anthropic.rb', line 41

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



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/providers/anthropic.rb', line 108

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

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

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

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

Send a message to the Anthropic API and return the parsed response.

Parameters:

  • model (String)

    Anthropic model identifier

  • messages (Array<Hash>)

    conversation messages

  • max_tokens (Integer)

    maximum tokens in the response

  • options (Hash)

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

Returns:

  • (Hash)

    parsed API response

Raises:



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/providers/anthropic.rb', line 84

def create_message(model:, messages:, max_tokens:, **options)
  wrap_system_prompt!(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,
    timeout: Anima::Settings.api_timeout
  )

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

#validate_credentials!true

Verify the token is accepted by Anthropic using the free models endpoint. Returns true on success; raises typed exceptions on failure so callers can distinguish permanent auth problems from transient outages.

Returns:

  • (true)

    when the API accepts the token

Raises:



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/providers/anthropic.rb', line 134

def validate_credentials!
  response = self.class.get(
    "/v1/models",
    headers: request_headers,
    timeout: Anima::Settings.api_timeout
  )

  case response.code
  when 200
    true
  when 401
    raise AuthenticationError,
      "Token rejected by Anthropic API (401). Re-run `claude setup-token` and use the TUI token setup (Ctrl+a → a)."
  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
rescue Errno::ECONNRESET, Net::ReadTimeout, Net::OpenTimeout, SocketError, EOFError => network_error
  raise TransientError, "#{network_error.class}: #{network_error.message}"
end