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"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(token = nil) ⇒ Anthropic

Returns a new instance of Anthropic.



57
58
59
# File 'lib/providers/anthropic.rb', line 57

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

Instance Attribute Details

#tokenObject (readonly)

Returns the value of attribute token.



55
56
57
# File 'lib/providers/anthropic.rb', line 55

def token
  @token
end

Class Method Details

.fetch_tokenObject



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

def fetch_token
  token = CredentialStore.read("anthropic", "subscription_token")
  raise AuthenticationError, <<~MSG.strip if token.blank?
    No Anthropic subscription token found in credentials.
    Use the TUI token setup (Ctrl+a → a) to configure your token.
  MSG
  token
end

.validate_token_api!(token) ⇒ Object



49
50
51
52
# File 'lib/providers/anthropic.rb', line 49

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

.validate_token_format!(token) ⇒ Object



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

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



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

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,
    timeout: Anima::Settings.api_timeout
  )

  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



61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/providers/anthropic.rb', line 61

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,
    timeout: Anima::Settings.api_timeout
  )

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

#validate_credentials!Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/providers/anthropic.rb', line 100

def validate_credentials!
  response = self.class.post(
    "/v1/messages",
    body: {
      model: Anima::Settings.model,
      messages: [{role: "user", content: "Hi"}],
      max_tokens: 1
    }.to_json,
    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
end