diff --git a/docs-site/src/content/docs/ja/reference/adapters.md b/docs-site/src/content/docs/ja/reference/adapters.md index c71df875e..ea97ddbcf 100644 --- a/docs-site/src/content/docs/ja/reference/adapters.md +++ b/docs-site/src/content/docs/ja/reference/adapters.md @@ -45,7 +45,7 @@ interface ProviderAdapter { ## `anthropic` **対象:** Anthropic **Messages**(`/v1/messages`)。 -**認証:** `key`(`x-api-key`)または `oauth`(Bearer + `anthropic-beta`、Claude Pro/Max 用)。 +**認証:** `key`(デフォルトは `x-api-key`、または `apiKeyTransport: "bearer"` による `Authorization: Bearer`)または `oauth`(Bearer + `anthropic-beta`、Claude Pro/Max 用)。 - メッセージを Anthropic content block(text、base64 image、`tool_use`、`thinking`)に変換します。 - **Extended thinking の計算:** Anthropic は `max_tokens > thinking.budget_tokens` を要求します。 diff --git a/docs-site/src/content/docs/ja/reference/configuration.md b/docs-site/src/content/docs/ja/reference/configuration.md index e9a42e87d..5443729e6 100644 --- a/docs-site/src/content/docs/ja/reference/configuration.md +++ b/docs-site/src/content/docs/ja/reference/configuration.md @@ -136,6 +136,7 @@ token の代わりに使えます。すべての候補は timing side channel | `responsesPath?` | `string` | `key` 認証の `openai-responses` リクエストに使う任意の相対 resource path。`/` で始め、URL scheme、query、fragment を含めてはいけません。省略時は従来の `/v1/responses` URL 構築を維持します。 | | `disabled?` | `boolean` | 設定はディスクに残すがルーティングとモデル/カタログ一覧から除外します。 | | `apiKey?` | `string` | API キーまたはリクエスト時に解釈する `${ENV_VAR}` / `$ENV_VAR` 参照。 | +| `apiKeyTransport?` | `"x-api-key" \| "bearer"` | Anthropic API キーのヘッダー方式。デフォルトはネイティブの `x-api-key` です。`Authorization: Bearer ` が必要な互換 gateway では `"bearer"` を設定します。key 認証の `anthropic` プロバイダーでのみ有効です。 | | `apiKeyPool?` | `ApiKeyPoolEntry[]` | 複数キーを納める pool。`apiKey` はアクティブ項目を反映します。各項目には `id`、`key`、選択 `label`、選択数値 `addedAt` があります。 | | `defaultModel?` | `string` | 明示的なモデルなしでこのプロバイダーを選んだときに使うモデル。 | | `models?` | `string[]` | seed/fallback モデル一覧。`liveModels` が `false` ならここにあるモデルだけが発見されます。 | diff --git a/docs-site/src/content/docs/ko/reference/adapters.md b/docs-site/src/content/docs/ko/reference/adapters.md index 9610162ae..ddfe86864 100644 --- a/docs-site/src/content/docs/ko/reference/adapters.md +++ b/docs-site/src/content/docs/ko/reference/adapters.md @@ -54,7 +54,7 @@ interface ProviderAdapter { ## `anthropic` **대상:** Anthropic **Messages**(`/v1/messages`). -**인증:** `key`(`x-api-key`) 또는 `oauth`(Bearer + `anthropic-beta`, Claude Pro/Max용). +**인증:** `key`(기본 `x-api-key`, 또는 `apiKeyTransport: "bearer"` 설정 시 `Authorization: Bearer`) 또는 `oauth`(Bearer + `anthropic-beta`, Claude Pro/Max용). - 메시지를 Anthropic content block(text, base64 image, `tool_use`, `thinking`)으로 변환합니다. - **Extended thinking 계산:** Anthropic은 `max_tokens > thinking.budget_tokens`를 요구합니다. diff --git a/docs-site/src/content/docs/ko/reference/configuration.md b/docs-site/src/content/docs/ko/reference/configuration.md index f56e6288d..80f9649f3 100644 --- a/docs-site/src/content/docs/ko/reference/configuration.md +++ b/docs-site/src/content/docs/ko/reference/configuration.md @@ -139,6 +139,7 @@ token 대신 쓸 수 있습니다. 모든 후보는 timing side channel을 막 | `responsesPath?` | `string` | `key` 인증 `openai-responses` 요청에 사용할 선택적 상대 resource path. `/`로 시작해야 하며 URL scheme, query, fragment를 포함할 수 없습니다. 생략하면 기존 `/v1/responses` URL 구성을 유지합니다. | | `disabled?` | `boolean` | 설정은 디스크에 남기되 라우팅과 모델/카탈로그 목록에서 제외합니다. | | `apiKey?` | `string` | API 키 또는 요청 시점에 해석할 `${ENV_VAR}` / `$ENV_VAR` 참조. | +| `apiKeyTransport?` | `"x-api-key" \| "bearer"` | Anthropic API 키 헤더 방식입니다. 기본값은 네이티브 `x-api-key`이며, `Authorization: Bearer `를 요구하는 호환 gateway에는 `"bearer"`를 설정합니다. key 인증 `anthropic` 프로바이더에서만 유효합니다. | | `apiKeyPool?` | `ApiKeyPoolEntry[]` | 여러 키를 담는 pool. `apiKey`는 활성 항목을 반영합니다. 각 항목에는 `id`, `key`, 선택 `label`, 선택 숫자 `addedAt`이 있습니다. | | `defaultModel?` | `string` | 명시적인 모델 없이 이 프로바이더를 선택했을 때 쓸 모델. | | `models?` | `string[]` | seed/fallback 모델 목록. `liveModels`가 `false`이면 여기 있는 모델만 발견됩니다. | diff --git a/docs-site/src/content/docs/reference/adapters.md b/docs-site/src/content/docs/reference/adapters.md index e2ab3ebee..25cd3b962 100644 --- a/docs-site/src/content/docs/reference/adapters.md +++ b/docs-site/src/content/docs/reference/adapters.md @@ -54,7 +54,7 @@ streams the response back **untranslated**. ## `anthropic` **Targets:** Anthropic **Messages** (`/v1/messages`). -**Auth:** `key` (`x-api-key`) or `oauth` (Bearer + `anthropic-beta`, for Claude Pro/Max). +**Auth:** `key` (`x-api-key` by default, or `Authorization: Bearer` with `apiKeyTransport: "bearer"`) or `oauth` (Bearer + `anthropic-beta`, for Claude Pro/Max). - Converts messages to Anthropic content blocks (text, base64 image, `tool_use`, `thinking`). - **Extended thinking math:** Anthropic requires `max_tokens > thinking.budget_tokens`. The adapter diff --git a/docs-site/src/content/docs/reference/configuration.md b/docs-site/src/content/docs/reference/configuration.md index ee28eb270..a704f12a1 100644 --- a/docs-site/src/content/docs/reference/configuration.md +++ b/docs-site/src/content/docs/reference/configuration.md @@ -241,6 +241,7 @@ or bind the forward explicitly to loopback (`ssh -L 127.0.0.1:20100:localhost:10 | `responsesPath?` | `string` | Optional relative resource path for key-auth `openai-responses` requests. It must start with `/` and contain no URL scheme, query, or fragment. When omitted, the adapter keeps its legacy `/v1/responses` URL construction. | | `disabled?` | `boolean` | Keep the provider on disk but exclude it from routing and model/catalog listings. | | `apiKey?` | `string` | API key, or an `${ENV_VAR}` / `$ENV_VAR` reference resolved at request time. | +| `apiKeyTransport?` | `"x-api-key" \| "bearer"` | Anthropic API-key header style. Defaults to native `x-api-key`; set `"bearer"` for compatible gateways that require `Authorization: Bearer `. Valid only for key-auth `anthropic` providers. | | `apiKeyPool?` | `ApiKeyPoolEntry[]` | Multi-key pool. `apiKey` mirrors the active entry; each item has `id`, `key`, optional `label`, and optional numeric `addedAt`. | | `defaultModel?` | `string` | Model used when this provider is selected without an explicit model. | | `models?` | `string[]` | Seed/fallback model list. When `liveModels` is `false`, these are the only discovered models. | diff --git a/docs-site/src/content/docs/ru/reference/adapters.md b/docs-site/src/content/docs/ru/reference/adapters.md index fac4fc6cf..80c7ae647 100644 --- a/docs-site/src/content/docs/ru/reference/adapters.md +++ b/docs-site/src/content/docs/ru/reference/adapters.md @@ -57,7 +57,7 @@ interface ProviderAdapter { ## `anthropic` **Назначение:** Anthropic **Messages** (`/v1/messages`). -**Аутентификация:** `key` (`x-api-key`) или `oauth` (Bearer + `anthropic-beta`, для Claude Pro/Max). +**Аутентификация:** `key` (по умолчанию `x-api-key`, либо `Authorization: Bearer` при `apiKeyTransport: "bearer"`) или `oauth` (Bearer + `anthropic-beta`, для Claude Pro/Max). - Преобразует сообщения в блоки контента Anthropic (text, base64 image, `tool_use`, `thinking`). - **Арифметика extended thinking:** Anthropic требует `max_tokens > thinking.budget_tokens`. diff --git a/docs-site/src/content/docs/ru/reference/configuration.md b/docs-site/src/content/docs/ru/reference/configuration.md index 4d426322f..a3101a9d7 100644 --- a/docs-site/src/content/docs/ru/reference/configuration.md +++ b/docs-site/src/content/docs/ru/reference/configuration.md @@ -158,6 +158,7 @@ x-opencodex-api-key: your-secret-token | `responsesPath?` | `string` | Необязательный относительный путь ресурса для запросов `openai-responses` с аутентификацией `key`. Должен начинаться с `/` и не содержать схему URL, query или fragment. Если поле опущено, сохраняется прежнее построение URL `/v1/responses`. | | `disabled?` | `boolean` | Провайдер остаётся на диске, но исключается из маршрутизации и списков моделей/каталога. | | `apiKey?` | `string` | API-ключ или ссылка `${ENV_VAR}` / `$ENV_VAR`, разрешаемая в момент запроса. | +| `apiKeyTransport?` | `"x-api-key" \| "bearer"` | Способ передачи API-ключа Anthropic. По умолчанию используется нативный `x-api-key`; для совместимых gateway, требующих `Authorization: Bearer `, задайте `"bearer"`. Допустимо только для `anthropic` provider с key-аутентификацией. | | `apiKeyPool?` | `ApiKeyPoolEntry[]` | Пул из нескольких ключей. `apiKey` отражает активную запись; каждый элемент содержит `id`, `key`, необязательный `label` и необязательное числовое `addedAt`. | | `defaultModel?` | `string` | Модель, используемая при выборе этого провайдера без явной модели. | | `models?` | `string[]` | Список seed/fallback-моделей. Когда `liveModels` равен `false`, обнаруживаются только эти модели. | diff --git a/docs-site/src/content/docs/zh-cn/reference/adapters.md b/docs-site/src/content/docs/zh-cn/reference/adapters.md index 6b52365cf..6a3d89a98 100644 --- a/docs-site/src/content/docs/zh-cn/reference/adapters.md +++ b/docs-site/src/content/docs/zh-cn/reference/adapters.md @@ -52,7 +52,7 @@ interface ProviderAdapter { ## `anthropic` **目标:** Anthropic **Messages**(`/v1/messages`)。 -**认证:** `key`(`x-api-key`)或 `oauth`(Bearer + `anthropic-beta`,用于 Claude Pro/Max)。 +**认证:** `key`(默认 `x-api-key`,或设置 `apiKeyTransport: "bearer"` 后使用 `Authorization: Bearer`)或 `oauth`(Bearer + `anthropic-beta`,用于 Claude Pro/Max)。 - 把消息转换成 Anthropic content block(text、base64 image、`tool_use`、`thinking`)。 - **Extended thinking 计算:** Anthropic 要求 `max_tokens > thinking.budget_tokens`。adapter 把 diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration.md b/docs-site/src/content/docs/zh-cn/reference/configuration.md index f41c59aea..fe4b47352 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration.md @@ -131,6 +131,7 @@ x-opencodex-api-key: your-secret-token | `responsesPath?` | `string` | `key` 认证的 `openai-responses` 请求可选相对 resource path。必须以 `/` 开头,且不得包含 URL scheme、query 或 fragment。省略时保留原有的 `/v1/responses` URL 构造。 | | `disabled?` | `boolean` | 配置保留在磁盘上,但从路由和模型/目录列表排除。 | | `apiKey?` | `string` | API key,或在请求时解析的 `${ENV_VAR}` / `$ENV_VAR` 引用。 | +| `apiKeyTransport?` | `"x-api-key" \| "bearer"` | Anthropic API key 的请求头方式。默认使用原生 `x-api-key`;兼容 gateway 要求 `Authorization: Bearer ` 时设为 `"bearer"`。仅适用于使用 key 认证的 `anthropic` provider。 | | `apiKeyPool?` | `ApiKeyPoolEntry[]` | 多 key pool。`apiKey` 映射当前活动条目;每项包含 `id`、`key`、可选 `label` 和可选数字 `addedAt`。 | | `defaultModel?` | `string` | 选中该 provider 但未指定明确模型时使用的模型。 | | `models?` | `string[]` | seed/fallback 模型列表。`liveModels` 为 `false` 时,只会发现这些模型。 | diff --git a/gui/src/components/AddProviderModal.tsx b/gui/src/components/AddProviderModal.tsx index a17753fa4..333971bbb 100644 --- a/gui/src/components/AddProviderModal.tsx +++ b/gui/src/components/AddProviderModal.tsx @@ -144,6 +144,7 @@ export default function AddProviderModal({ : p.baseUrl, authMode: p.auth, apiKey: "", + apiKeyTransport: undefined, defaultModel: p.defaultModel ?? "", allowPrivateNetwork: false, }, diff --git a/gui/src/components/add-provider-form-pane.tsx b/gui/src/components/add-provider-form-pane.tsx index 89a3fbd8a..78cba31d3 100644 --- a/gui/src/components/add-provider-form-pane.tsx +++ b/gui/src/components/add-provider-form-pane.tsx @@ -143,6 +143,18 @@ export function AddProviderFormPane({ onFormChange({ ...form, apiKey: e.target.value })} placeholder={t("modal.apiKeyPlaceholder")} /> + {form.adapter === "anthropic" && form.authMode === "key" && ( + + + + )} )} {!isReservedForward && diff --git a/gui/src/components/add-provider-modal-reducer.ts b/gui/src/components/add-provider-modal-reducer.ts index db9775d62..97ee7cb17 100644 --- a/gui/src/components/add-provider-modal-reducer.ts +++ b/gui/src/components/add-provider-modal-reducer.ts @@ -56,7 +56,7 @@ export function createInitialAddProviderState( return { preset: initialCustom ? customPreset : null, form: initialCustom - ? { name: "", adapter: "openai-chat", baseUrl: "", authMode: "key", apiKey: "", defaultModel: "", allowPrivateNetwork: false } + ? { name: "", adapter: "openai-chat", baseUrl: "", authMode: "key", apiKey: "", apiKeyTransport: undefined, defaultModel: "", allowPrivateNetwork: false } : null, saving: false, error: "", diff --git a/gui/src/components/provider-workspace/ProviderSettings.tsx b/gui/src/components/provider-workspace/ProviderSettings.tsx index e88047366..c43776467 100644 --- a/gui/src/components/provider-workspace/ProviderSettings.tsx +++ b/gui/src/components/provider-workspace/ProviderSettings.tsx @@ -40,6 +40,7 @@ export default function ProviderSettings({ const [baseUrl, setBaseUrl] = useState(item.baseUrl); const [defaultModel, setDefaultModel] = useState(item.defaultModel ?? ""); const [authMode, setAuthMode] = useState(initialAuth); + const [apiKeyTransport, setApiKeyTransport] = useState(item.apiKeyTransport ?? "x-api-key"); const [note, setNote] = useState(item.note ?? ""); const [allowPrivateNetwork, setAllowPrivateNetwork] = useState(item.allowPrivateNetwork ?? false); const [liveModels, setLiveModels] = useState(item.liveModels !== false); @@ -55,12 +56,13 @@ export default function ProviderSettings({ setBaseUrl(item.baseUrl); setDefaultModel(item.defaultModel ?? ""); setAuthMode(String(item.authMode ?? (item.keyOptional ? "local" : "key"))); + setApiKeyTransport(item.apiKeyTransport ?? "x-api-key"); setNote(item.note ?? ""); setAllowPrivateNetwork(item.allowPrivateNetwork ?? false); setLiveModels(item.liveModels !== false); setMsg(null); queueMicrotask(() => setEndpointChoice(matchChoiceId(baseUrlChoices, item.baseUrl))); - }, [item.adapter, item.baseUrl, item.defaultModel, item.authMode, item.keyOptional, item.note, item.allowPrivateNetwork, item.liveModels, baseUrlChoices]); + }, [item.adapter, item.baseUrl, item.defaultModel, item.authMode, item.apiKeyTransport, item.keyOptional, item.note, item.allowPrivateNetwork, item.liveModels, baseUrlChoices]); /* eslint-enable react-hooks/set-state-in-effect */ useEffect(() => { @@ -97,6 +99,7 @@ export default function ProviderSettings({ || baseUrl.trim() !== item.baseUrl || defaultModel.trim() !== (item.defaultModel ?? "") || authMode !== String(item.authMode ?? (item.keyOptional ? "local" : "key")) + || (adapter.trim() === "anthropic" && authMode === "key" && apiKeyTransport !== (item.apiKeyTransport ?? "x-api-key")) || note.trim() !== (item.note ?? "") || allowPrivateNetwork !== (item.allowPrivateNetwork ?? false) || liveModels !== (item.liveModels !== false); @@ -118,6 +121,7 @@ export default function ProviderSettings({ const isPreset = isCatalogProviderId(item.name); const hasEndpointPicker = choicesStatus === "ready" && !!(baseUrlChoices && baseUrlChoices.length > 0); + const supportsApiKeyTransport = adapter.trim() === "anthropic" && authMode === "key"; // Lock plain baseUrl for presets while loading or when there is no picker. // On fetch error, keep it editable so allowBaseUrlOverride providers are not trapped. const plainBaseUrlLocked = isPreset && choicesStatus !== "error"; @@ -132,6 +136,8 @@ export default function ProviderSettings({ setMsg(null); try { const patch: ProviderUpdatePatch = { adapter: adapter.trim(), baseUrl: nextBaseUrl, defaultModel: defaultModel.trim(), authMode, note: note.trim(), allowPrivateNetwork, liveModels }; + if (supportsApiKeyTransport) patch.apiKeyTransport = apiKeyTransport; + else if (item.apiKeyTransport !== undefined) patch.apiKeyTransport = ""; const res = await onUpdateProvider(item.name, patch); setMsg(res.ok ? { ok: true, text: t("pws.settingsSaved") } : { ok: false, text: res.error || t("prov.saveFailed") }); return res.ok; @@ -153,6 +159,7 @@ export default function ProviderSettings({ const discard = () => { setAdapter(item.adapter); setBaseUrl(item.baseUrl); setDefaultModel(item.defaultModel ?? ""); setAuthMode(initialAuth); + setApiKeyTransport(item.apiKeyTransport ?? "x-api-key"); setNote(item.note ?? ""); setAllowPrivateNetwork(item.allowPrivateNetwork ?? false); setLiveModels(item.liveModels !== false); setMsg(null); setEndpointChoice(matchChoiceId(baseUrlChoices, item.baseUrl)); }; @@ -233,6 +240,15 @@ export default function ProviderSettings({ )} + {supportsApiKeyTransport && ( + + )}