|
6 | 6 | [eca.llm-util :as llm-util] |
7 | 7 | [eca.logger :as logger] |
8 | 8 | [eca.shared :as shared :refer [assoc-some]] |
9 | | - [hato.client :as http])) |
| 9 | + [hato.client :as http] |
| 10 | + [ring.util.codec :as ring.util])) |
10 | 11 |
|
11 | 12 | (set! *warn-on-reflection* true) |
12 | 13 |
|
|
107 | 108 | :temperature temperature |
108 | 109 | :stream true |
109 | 110 | :tools (->tools tools web-search) |
110 | | - :system [{:type "text" :text instructions :cache_control {:type "ephemeral"}}]} |
| 111 | + :system [{:type "text" :text "You are Claude Code, Anthropic's official CLI for Claude."} |
| 112 | + {:type "text" :text instructions :cache_control {:type "ephemeral"}}]} |
111 | 113 | :thinking (when (and reason? thinking) |
112 | 114 | thinking)) |
113 | 115 | extra-payload) |
|
194 | 196 | :content-block* (atom nil) |
195 | 197 | :on-error on-error |
196 | 198 | :on-response on-response-fn}))) |
| 199 | + |
| 200 | +(def client-id "9d1c250a-e61b-44d9-88ed-5944d1962f5e") |
| 201 | + |
| 202 | +(defn oauth-url [mode] |
| 203 | + (let [url (str (if (= :console mode) "https://console.anthropic.com" "https://claude.ai") "/oauth/authorize") |
| 204 | + {:keys [challenge verifier]} (llm-util/generate-pkce)] |
| 205 | + {:verifier verifier |
| 206 | + :url (str url "?" (ring.util/form-encode {:code true |
| 207 | + :client_id client-id |
| 208 | + :response_type "code" |
| 209 | + :redirect_uri "https://console.anthropic.com/oauth/code/callback" |
| 210 | + :scope "org:create_api_key user:profile user:inference" |
| 211 | + :code_challenge challenge |
| 212 | + :code_challenge_method "S256" |
| 213 | + :state verifier}))})) |
| 214 | + |
| 215 | +(defn oauth-credentials [code verifier] |
| 216 | + (let [[code state] (string/split code #"#") |
| 217 | + url "https://console.anthropic.com/v1/oauth/token" |
| 218 | + body {:grant_type "authorization_code" |
| 219 | + :code code |
| 220 | + :state state |
| 221 | + :client_id client-id |
| 222 | + :redirect_uri "https://console.anthropic.com/oauth/code/callback" |
| 223 | + :code_verifier verifier} |
| 224 | + {:keys [status body]} (http/post |
| 225 | + url |
| 226 | + {:headers {"Content-Type" "application/json"} |
| 227 | + :body (json/generate-string body) |
| 228 | + :as :json})] |
| 229 | + (if (= 200 status) |
| 230 | + {:refresh-token (:refresh_token body) |
| 231 | + :access-token (:access_token body) |
| 232 | + :expires-at (+ (System/currentTimeMillis) (* 1000 (:expires_in body)))} |
| 233 | + (throw (ex-info (format "Anthropic token exchange failed: %s" (pr-str body)) |
| 234 | + {:status status |
| 235 | + :body body}))))) |
| 236 | + |
| 237 | +(defn create-api-key [access-token] |
| 238 | + (let [url "https://api.anthropic.com/api/oauth/claude_cli/create_api_key" |
| 239 | + {:keys [status body]} (http/post |
| 240 | + url |
| 241 | + {:headers {"Authorization" (str "Bearer " access-token) |
| 242 | + "Content-Type" "application/x-www-form-urlencoded" |
| 243 | + "Accept" "application/json, text/plain, */*"} |
| 244 | + :as :json})] |
| 245 | + (if (= 200 status) |
| 246 | + (let [raw-key (:raw_key body)] |
| 247 | + (when-not (string/blank? raw-key) |
| 248 | + raw-key)) |
| 249 | + (throw (ex-info (format "Anthropic create API token failed: %s" (pr-str body)) |
| 250 | + {:status status |
| 251 | + :body body}))))) |
0 commit comments