Class: Clacky::ClackyAuthClient

Inherits:
Object
  • Object
show all
Defined in:
lib/clacky/clacky_auth_client.rb

Overview

ClackyAuthClient - Fetches LLM keys from a Clacky workspace via API

Usage:

client = ClackyAuthClient.new("clacky_ak_xxx", base_url: "https://api.example.com")
result = client.fetch_workspace_keys
# => { success: true, llm_key: "ABSK...", model_name: "jp.anthropic.claude-sonnet-4-6",
#      base_url: "https://...", anthropic_format: false }

Constant Summary collapse

WORKSPACE_KEYS_PATH =
"/openclacky/v1/workspace/keys"
REQUEST_TIMEOUT =

seconds

15
OPEN_TIMEOUT =

seconds

5
DEFAULT_MODEL =

Default model to use when the workspace/keys response does not specify one

"jp.anthropic.claude-sonnet-4-6"

Instance Method Summary collapse

Constructor Details

#initialize(workspace_api_key, base_url:) ⇒ ClackyAuthClient

Returns a new instance of ClackyAuthClient.



22
23
24
25
# File 'lib/clacky/clacky_auth_client.rb', line 22

def initialize(workspace_api_key, base_url:)
  @workspace_api_key = workspace_api_key.to_s.strip
  @base_url          = base_url.to_s.strip.sub(%r{/+$}, "")
end

Instance Method Details

#fetch_workspace_keysHash

Fetch workspace keys from the Clacky backend.

Returns:

  • (Hash)

    On success:

    { success: true,
      llm_key: "...",          # raw LLM key string returned by the API (ABSK prefix = Bedrock)
      model_name: "...",       # model to configure (provider default or our default)
      base_url: "...",         # LLM proxy base URL (clean host, no path suffix)
      anthropic_format: false  # ABSK keys use Bedrock Converse format, not Anthropic wire format
    }
    

    On failure:

    { success: false, error: "..." }
    


39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/clacky/clacky_auth_client.rb', line 39

def fetch_workspace_keys
  validate_inputs!

  response = connection.get(WORKSPACE_KEYS_PATH)

  unless response.status == 200
    error_msg = extract_error(response)
    return { success: false, error: "HTTP #{response.status}: #{error_msg}" }
  end

  body = JSON.parse(response.body)

  unless body["code"].to_i == 200
    return { success: false, error: "API error: #{body["msg"] || body["message"]}" }
  end

  llm_key_data = body.dig("data", "llm_key")
  if llm_key_data.nil?
    return { success: false, error: "No LLM key available for this workspace" }
  end

  # Extract key value – the API returns a hash with fields:
  #   raw_key  – plaintext secret (primary field since 2026-03-30)
  #   key      – alias used by some gateway endpoints
  #   key_id   – legacy identifier (kept for forward-compat)
  # Priority: raw_key > key > key_id > value
  # We also accept a plain string form for forward-compat.
  llm_key = case llm_key_data
            when String then llm_key_data
            when Hash
              llm_key_data["raw_key"] || llm_key_data["key"] ||
                llm_key_data["key_id"] || llm_key_data["value"]
            end

  if llm_key.nil? || llm_key.to_s.strip.empty?
    return { success: false, error: "LLM key value is empty or missing in response" }
  end

  # base_url comes from the `host` field in the API response (set per environment by backend config).
  # Fallback to @base_url (the backend URL the user entered).
  # No path suffix is appended — the LLM key has ABSK prefix (Bedrock), so client.rb will
  # automatically build the correct endpoint: /model/{model}/converse
  host = llm_key_data.is_a?(Hash) ? llm_key_data["host"].to_s.strip : ""
  llm_base_url = if host.start_with?("http://", "https://")
                   host
                 else
                   @base_url
                 end

  {
    success:          true,
    llm_key:          llm_key.to_s.strip,
    model_name:       DEFAULT_MODEL,
    base_url:         llm_base_url,
    anthropic_format: false
  }
rescue Faraday::ConnectionFailed => e
  { success: false, error: "Connection failed: #{e.message}" }
rescue Faraday::TimeoutError
  { success: false, error: "Request timed out (#{REQUEST_TIMEOUT}s)" }
rescue Faraday::Error => e
  { success: false, error: "Network error: #{e.message}" }
rescue JSON::ParserError => e
  { success: false, error: "Invalid JSON response: #{e.message}" }
rescue ArgumentError => e
  { success: false, error: e.message }
rescue => e
  { success: false, error: "Unexpected error: #{e.message}" }
end