Add Anthropic option to pass API Token as Authorization header instead of X-Api-Key for the custom base URL (#2531)

Add Anthropic option to use authToken over apiKey
This commit is contained in:
mecab 2025-04-13 03:20:03 +01:00 committed by GitHub
parent 294b52ef6a
commit 37cfd75ebf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 79 additions and 8 deletions

View file

@ -301,6 +301,7 @@ export const providerSettingsSchema = z.object({
apiModelId: z.string().optional(),
apiKey: z.string().optional(),
anthropicBaseUrl: z.string().optional(),
anthropicUseAuthToken: z.boolean().optional(),
// Glama
glamaModelId: z.string().optional(),
glamaModelInfo: modelInfoSchema.optional(),

View file

@ -2,8 +2,10 @@
import { AnthropicHandler } from "../anthropic"
import { ApiHandlerOptions } from "../../../shared/api"
import Anthropic from "@anthropic-ai/sdk"
const mockCreate = jest.fn()
const mockAnthropicConstructor = Anthropic.Anthropic as unknown as jest.Mock
jest.mock("@anthropic-ai/sdk", () => {
return {
@ -69,6 +71,7 @@ describe("AnthropicHandler", () => {
}
handler = new AnthropicHandler(mockOptions)
mockCreate.mockClear()
mockAnthropicConstructor.mockClear()
})
describe("constructor", () => {
@ -94,6 +97,40 @@ describe("AnthropicHandler", () => {
})
expect(handlerWithCustomUrl).toBeInstanceOf(AnthropicHandler)
})
it("use apiKey for passing token if anthropicUseAuthToken is not set", () => {
const handlerWithCustomUrl = new AnthropicHandler({
...mockOptions,
})
expect(handlerWithCustomUrl).toBeInstanceOf(AnthropicHandler)
expect(mockAnthropicConstructor).toHaveBeenCalledTimes(1)
expect(mockAnthropicConstructor.mock.lastCall[0].apiKey).toEqual("test-api-key")
expect(mockAnthropicConstructor.mock.lastCall[0].authToken).toBeUndefined()
})
it("use apiKey for passing token if anthropicUseAuthToken is set but custom base URL is not given", () => {
const handlerWithCustomUrl = new AnthropicHandler({
...mockOptions,
anthropicUseAuthToken: true,
})
expect(handlerWithCustomUrl).toBeInstanceOf(AnthropicHandler)
expect(mockAnthropicConstructor).toHaveBeenCalledTimes(1)
expect(mockAnthropicConstructor.mock.lastCall[0].apiKey).toEqual("test-api-key")
expect(mockAnthropicConstructor.mock.lastCall[0].authToken).toBeUndefined()
})
it("use authToken for passing token if both of anthropicBaseUrl and anthropicUseAuthToken are set", () => {
const customBaseUrl = "https://custom.anthropic.com"
const handlerWithCustomUrl = new AnthropicHandler({
...mockOptions,
anthropicBaseUrl: customBaseUrl,
anthropicUseAuthToken: true,
})
expect(handlerWithCustomUrl).toBeInstanceOf(AnthropicHandler)
expect(mockAnthropicConstructor).toHaveBeenCalledTimes(1)
expect(mockAnthropicConstructor.mock.lastCall[0].authToken).toEqual("test-api-key")
expect(mockAnthropicConstructor.mock.lastCall[0].apiKey).toBeUndefined()
})
})
describe("createMessage", () => {

View file

@ -20,9 +20,12 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
constructor(options: ApiHandlerOptions) {
super()
this.options = options
const apiKeyFieldName =
this.options.anthropicBaseUrl && this.options.anthropicUseAuthToken ? "authToken" : "apiKey"
this.client = new Anthropic({
apiKey: this.options.apiKey,
baseURL: this.options.anthropicBaseUrl || undefined,
[apiKeyFieldName]: this.options.apiKey,
})
}

View file

@ -25,6 +25,7 @@ type ProviderSettings = {
apiModelId?: string | undefined
apiKey?: string | undefined
anthropicBaseUrl?: string | undefined
anthropicUseAuthToken?: boolean | undefined
glamaModelId?: string | undefined
glamaModelInfo?:
| ({

View file

@ -26,6 +26,7 @@ type ProviderSettings = {
apiModelId?: string | undefined
apiKey?: string | undefined
anthropicBaseUrl?: string | undefined
anthropicUseAuthToken?: boolean | undefined
glamaModelId?: string | undefined
glamaModelInfo?:
| ({

View file

@ -318,6 +318,7 @@ export const providerSettingsSchema = z.object({
apiModelId: z.string().optional(),
apiKey: z.string().optional(),
anthropicBaseUrl: z.string().optional(),
anthropicUseAuthToken: z.boolean().optional(),
// Glama
glamaModelId: z.string().optional(),
glamaModelInfo: modelInfoSchema.nullish(),
@ -414,6 +415,7 @@ const providerSettingsRecord: ProviderSettingsRecord = {
apiModelId: undefined,
apiKey: undefined,
anthropicBaseUrl: undefined,
anthropicUseAuthToken: undefined,
// Glama
glamaModelId: undefined,
glamaModelInfo: undefined,

View file

@ -414,18 +414,29 @@ const ApiOptions = ({
if (!checked) {
setApiConfigurationField("anthropicBaseUrl", "")
setApiConfigurationField("anthropicUseAuthToken", false) // added
}
}}>
{t("settings:providers.useCustomBaseUrl")}
</Checkbox>
{anthropicBaseUrlSelected && (
<VSCodeTextField
value={apiConfiguration?.anthropicBaseUrl || ""}
type="url"
onInput={handleInputChange("anthropicBaseUrl")}
placeholder="https://api.anthropic.com"
className="w-full mt-1"
/>
<>
<VSCodeTextField
value={apiConfiguration?.anthropicBaseUrl || ""}
type="url"
onInput={handleInputChange("anthropicBaseUrl")}
placeholder="https://api.anthropic.com"
className="w-full mt-1"
/>
{/* added */}
<Checkbox
checked={apiConfiguration?.anthropicUseAuthToken ?? false}
onChange={handleInputChange("anthropicUseAuthToken", noTransform)}
className="w-full mt-1">
{t("settings:providers.anthropicUseAuthToken")}
</Checkbox>
</>
)}
</div>
</>

View file

@ -116,6 +116,7 @@
"getRequestyApiKey": "Obtenir clau API de Requesty",
"anthropicApiKey": "Clau API d'Anthropic",
"getAnthropicApiKey": "Obtenir clau API d'Anthropic",
"anthropicUseAuthToken": "Passar la clau API d'Anthropic com a capçalera d'autorització en lloc de X-Api-Key",
"deepSeekApiKey": "Clau API de DeepSeek",
"getDeepSeekApiKey": "Obtenir clau API de DeepSeek",
"geminiApiKey": "Clau API de Gemini",

View file

@ -116,6 +116,7 @@
"openRouterTransformsText": "Prompts und Nachrichtenketten auf Kontextgröße komprimieren (<a>OpenRouter Transformationen</a>)",
"anthropicApiKey": "Anthropic API-Schlüssel",
"getAnthropicApiKey": "Anthropic API-Schlüssel erhalten",
"anthropicUseAuthToken": "Anthropic API-Schlüssel als Authorization-Header anstelle von X-Api-Key übergeben",
"deepSeekApiKey": "DeepSeek API-Schlüssel",
"getDeepSeekApiKey": "DeepSeek API-Schlüssel erhalten",
"geminiApiKey": "Gemini API-Schlüssel",

View file

@ -116,6 +116,7 @@
"openRouterTransformsText": "Compress prompts and message chains to the context size (<a>OpenRouter Transforms</a>)",
"anthropicApiKey": "Anthropic API Key",
"getAnthropicApiKey": "Get Anthropic API Key",
"anthropicUseAuthToken": "Pass Anthropic API Key as Authorization header instead of X-Api-Key",
"deepSeekApiKey": "DeepSeek API Key",
"getDeepSeekApiKey": "Get DeepSeek API Key",
"geminiApiKey": "Gemini API Key",

View file

@ -116,6 +116,7 @@
"openRouterTransformsText": "Comprimir prompts y cadenas de mensajes al tamaño del contexto (<a>Transformaciones de OpenRouter</a>)",
"anthropicApiKey": "Clave API de Anthropic",
"getAnthropicApiKey": "Obtener clave API de Anthropic",
"anthropicUseAuthToken": "Pasar la clave API de Anthropic como encabezado de autorización en lugar de X-Api-Key",
"deepSeekApiKey": "Clave API de DeepSeek",
"getDeepSeekApiKey": "Obtener clave API de DeepSeek",
"geminiApiKey": "Clave API de Gemini",

View file

@ -116,6 +116,7 @@
"openRouterTransformsText": "Compresser les prompts et chaînes de messages à la taille du contexte (<a>Transformations OpenRouter</a>)",
"anthropicApiKey": "Clé API Anthropic",
"getAnthropicApiKey": "Obtenir la clé API Anthropic",
"anthropicUseAuthToken": "Passer la clé API Anthropic comme en-tête d'autorisation au lieu de X-Api-Key",
"deepSeekApiKey": "Clé API DeepSeek",
"getDeepSeekApiKey": "Obtenir la clé API DeepSeek",
"geminiApiKey": "Clé API Gemini",

View file

@ -116,6 +116,7 @@
"openRouterTransformsText": "संदर्भ आकार के लिए प्रॉम्प्ट और संदेश श्रृंखलाओं को संपीड़ित करें (<a>OpenRouter ट्रांसफॉर्म</a>)",
"anthropicApiKey": "Anthropic API कुंजी",
"getAnthropicApiKey": "Anthropic API कुंजी प्राप्त करें",
"anthropicUseAuthToken": "X-Api-Key के बजाय Anthropic API कुंजी को Authorization हेडर के रूप में पास करें",
"deepSeekApiKey": "DeepSeek API कुंजी",
"getDeepSeekApiKey": "DeepSeek API कुंजी प्राप्त करें",
"geminiApiKey": "Gemini API कुंजी",

View file

@ -116,6 +116,7 @@
"openRouterTransformsText": "Comprimi prompt e catene di messaggi alla dimensione del contesto (<a>Trasformazioni OpenRouter</a>)",
"anthropicApiKey": "Chiave API Anthropic",
"getAnthropicApiKey": "Ottieni chiave API Anthropic",
"anthropicUseAuthToken": "Passa la chiave API Anthropic come header di autorizzazione invece di X-Api-Key",
"deepSeekApiKey": "Chiave API DeepSeek",
"getDeepSeekApiKey": "Ottieni chiave API DeepSeek",
"geminiApiKey": "Chiave API Gemini",

View file

@ -116,6 +116,7 @@
"openRouterTransformsText": "プロンプトとメッセージチェーンをコンテキストサイズに圧縮 (<a>OpenRouter Transforms</a>)",
"anthropicApiKey": "Anthropic APIキー",
"getAnthropicApiKey": "Anthropic APIキーを取得",
"anthropicUseAuthToken": "Anthropic APIキーをX-Api-Keyの代わりにAuthorizationヘッダーとして渡す",
"deepSeekApiKey": "DeepSeek APIキー",
"getDeepSeekApiKey": "DeepSeek APIキーを取得",
"geminiApiKey": "Gemini APIキー",

View file

@ -116,6 +116,7 @@
"openRouterTransformsText": "프롬프트와 메시지 체인을 컨텍스트 크기로 압축 (<a>OpenRouter Transforms</a>)",
"anthropicApiKey": "Anthropic API 키",
"getAnthropicApiKey": "Anthropic API 키 받기",
"anthropicUseAuthToken": "X-Api-Key 대신 Authorization 헤더로 Anthropic API 키 전달",
"deepSeekApiKey": "DeepSeek API 키",
"getDeepSeekApiKey": "DeepSeek API 키 받기",
"geminiApiKey": "Gemini API 키",

View file

@ -116,6 +116,7 @@
"openRouterTransformsText": "Kompresuj podpowiedzi i łańcuchy wiadomości do rozmiaru kontekstu (<a>Transformacje OpenRouter</a>)",
"anthropicApiKey": "Klucz API Anthropic",
"getAnthropicApiKey": "Uzyskaj klucz API Anthropic",
"anthropicUseAuthToken": "Przekaż klucz API Anthropic jako nagłówek Authorization zamiast X-Api-Key",
"deepSeekApiKey": "Klucz API DeepSeek",
"getDeepSeekApiKey": "Uzyskaj klucz API DeepSeek",
"geminiApiKey": "Klucz API Gemini",

View file

@ -116,6 +116,7 @@
"openRouterTransformsText": "Comprimir prompts e cadeias de mensagens para o tamanho do contexto (<a>Transformações OpenRouter</a>)",
"anthropicApiKey": "Chave de API Anthropic",
"getAnthropicApiKey": "Obter chave de API Anthropic",
"anthropicUseAuthToken": "Passar a chave de API Anthropic como cabeçalho Authorization em vez de X-Api-Key",
"deepSeekApiKey": "Chave de API DeepSeek",
"getDeepSeekApiKey": "Obter chave de API DeepSeek",
"geminiApiKey": "Chave de API Gemini",

View file

@ -116,6 +116,7 @@
"openRouterTransformsText": "İstem ve mesaj zincirlerini bağlam boyutuna sıkıştır (<a>OpenRouter Dönüşümleri</a>)",
"anthropicApiKey": "Anthropic API Anahtarı",
"getAnthropicApiKey": "Anthropic API Anahtarı Al",
"anthropicUseAuthToken": "Anthropic API Anahtarını X-Api-Key yerine Authorization başlığı olarak geçir",
"deepSeekApiKey": "DeepSeek API Anahtarı",
"getDeepSeekApiKey": "DeepSeek API Anahtarı Al",
"geminiApiKey": "Gemini API Anahtarı",

View file

@ -115,6 +115,7 @@
"getRequestyApiKey": "Lấy khóa API Requesty",
"anthropicApiKey": "Khóa API Anthropic",
"getAnthropicApiKey": "Lấy khóa API Anthropic",
"anthropicUseAuthToken": "Truyền khóa API Anthropic dưới dạng tiêu đề Authorization thay vì X-Api-Key",
"deepSeekApiKey": "Khóa API DeepSeek",
"getDeepSeekApiKey": "Lấy khóa API DeepSeek",
"geminiApiKey": "Khóa API Gemini",

View file

@ -116,6 +116,7 @@
"openRouterTransformsText": "自动压缩提示词和消息链到上下文长度限制内 (<a>OpenRouter转换</a>)",
"anthropicApiKey": "Anthropic API 密钥",
"getAnthropicApiKey": "获取 Anthropic API 密钥",
"anthropicUseAuthToken": "将 Anthropic API 密钥作为 Authorization 标头传递,而不是 X-Api-Key",
"deepSeekApiKey": "DeepSeek API 密钥",
"getDeepSeekApiKey": "获取 DeepSeek API 密钥",
"geminiApiKey": "Gemini API 密钥",

View file

@ -116,6 +116,7 @@
"openRouterTransformsText": "將提示和訊息鏈壓縮到上下文大小 (<a>OpenRouter 轉換</a>)",
"anthropicApiKey": "Anthropic API 金鑰",
"getAnthropicApiKey": "取得 Anthropic API 金鑰",
"anthropicUseAuthToken": "將 Anthropic API 金鑰作為 Authorization 標頭傳遞,而非使用 X-Api-Key",
"deepSeekApiKey": "DeepSeek API 金鑰",
"getDeepSeekApiKey": "取得 DeepSeek API 金鑰",
"geminiApiKey": "Gemini API 金鑰",