From ba2b4da723fe7d1574c056bc2c8caa3455252543 Mon Sep 17 00:00:00 2001 From: omrikiei Date: Thu, 26 Feb 2026 17:24:45 -0600 Subject: [PATCH] feat: add OAuth support for Claude Code tokens Add support for Claude Code OAuth tokens (sk-ant-oat01-*) in the Anthropic provider. Claude Code generates OAuth tokens that require different authentication headers than standard Anthropic API keys: - Standard API keys use: x-api-key header - OAuth tokens require: Authorization: Bearer header + anthropic-beta: oauth-2025-04-20 header Changes: - Add isOAuthToken() to detect Claude Code OAuth tokens by sk-ant-oat01- prefix - Update NewProviderWithBaseURL() to use proper headers for OAuth tokens - Update Chat() method to handle OAuth tokens when using tokenSource This implementation matches the approach used in zeroclaw (Rust implementation). Fixes authentication errors when using Claude Code OAuth tokens: - Before: "OAuth authentication is currently not supported" - After: OAuth tokens work correctly with proper headers Co-Authored-By: Claude Sonnet 4.5 --- pkg/providers/anthropic/provider.go | 36 +++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/pkg/providers/anthropic/provider.go b/pkg/providers/anthropic/provider.go index 9162174c9..bbd67feb9 100644 --- a/pkg/providers/anthropic/provider.go +++ b/pkg/providers/anthropic/provider.go @@ -25,6 +25,11 @@ type ( const defaultBaseURL = "https://api.anthropic.com" +// isOAuthToken detects Claude Code OAuth tokens by their prefix +func isOAuthToken(token string) bool { + return strings.HasPrefix(strings.TrimSpace(token), "sk-ant-oat01-") +} + type Provider struct { client *anthropic.Client tokenSource func() (string, error) @@ -37,10 +42,22 @@ func NewProvider(token string) *Provider { func NewProviderWithBaseURL(token, apiBase string) *Provider { baseURL := normalizeBaseURL(apiBase) - client := anthropic.NewClient( - option.WithAuthToken(token), - option.WithBaseURL(baseURL), - ) + + var opts []option.RequestOption + + // OAuth tokens (Claude Code) require special handling + if isOAuthToken(token) { + opts = append(opts, + option.WithHeader("Authorization", "Bearer "+token), + option.WithHeader("anthropic-beta", "oauth-2025-04-20"), + ) + } else { + opts = append(opts, option.WithAuthToken(token)) + } + + opts = append(opts, option.WithBaseURL(baseURL)) + + client := anthropic.NewClient(opts...) return &Provider{ client: &client, baseURL: baseURL, @@ -77,7 +94,16 @@ func (p *Provider) Chat( if err != nil { return nil, fmt.Errorf("refreshing token: %w", err) } - opts = append(opts, option.WithAuthToken(tok)) + + // OAuth tokens (Claude Code) require special handling + if isOAuthToken(tok) { + opts = append(opts, + option.WithHeader("Authorization", "Bearer "+tok), + option.WithHeader("anthropic-beta", "oauth-2025-04-20"), + ) + } else { + opts = append(opts, option.WithAuthToken(tok)) + } } params, err := buildParams(messages, tools, model, options)