From 367d3758d522a1958d27c5ef420c4b17ea6d0705 Mon Sep 17 00:00:00 2001 From: antydizajn Date: Sun, 31 May 2026 21:12:49 +0200 Subject: [PATCH] fix(auxiliary): route custom: through named-provider arm + Palantir Bearer auth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the user's main provider is a named custom_providers entry exposing an Anthropic Messages surface (e.g. Palantir Foundry's /api/v2/llm/proxy/anthropic, custom LiteLLM/Bedrock proxies), auxiliary tasks (title generation, compression, web extract, session search, etc.) returned HTTP 404 NOT_FOUND for every call. Root cause: `_resolve_auto` collapsed any `custom:` main provider to plain `"custom"` and passed runtime_base_url as explicit_base_url. This landed in `resolve_provider_client`'s anonymous-custom arm (`if provider == "custom":`), which unconditionally calls `_to_openai_base_url` — that helper strips a trailing `/anthropic` and substitutes `/v1` (designed for MiniMax/ZAI which expose both surfaces). The result for Palantir is `/api/v2/llm/proxy/v1`, which does not exist on the proxy — every auxiliary call 404s. The runtime `api_mode= anthropic_messages` flag was discarded by this arm. Fix: split the conditional so only the literal `"custom"` provider takes the anonymous-custom path; `custom:` keeps its full `custom:` string when handed to `resolve_provider_client`, where the named-custom-provider arm (added in earlier work) honours the entry's `api_mode` and routes through `AnthropicAuxiliaryClient` against the original `/anthropic` URL. Also: extend `_requires_bearer_auth` in `anthropic_adapter.py` to recognise palantirfoundry hosts so the SDK sends `Authorization: Bearer` instead of the default `x-api-key` (Palantir's proxy rejects x-api-key with 401). Verified end-to-end against a live Palantir Foundry deployment with both claude-4-6-opus and claude-4-7-opus models — `generate_title` returns real titles instead of 404ing. Regression-tested: - anonymous `custom` (with base_url) still routes to OpenAI wire - built-in NVIDIA provider unchanged - custom-without-base_url still falls through to Step-2 chain --- agent/anthropic_adapter.py | 1 + agent/auxiliary_client.py | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/agent/anthropic_adapter.py b/agent/anthropic_adapter.py index 689d01010ad6..0c346cad56f7 100644 --- a/agent/anthropic_adapter.py +++ b/agent/anthropic_adapter.py @@ -544,6 +544,7 @@ def _requires_bearer_auth(base_url: str | None) -> bool: return ( normalized.startswith(("https://api.minimax.io/anthropic", "https://api.minimaxi.com/anthropic")) or "azure.com" in normalized + or "palantirfoundry" in normalized ) diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index bacd3e7749ff..3b1af7ea6f2f 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -4384,10 +4384,26 @@ def _resolve_auto( resolved_provider = main_provider explicit_base_url = runtime_base_url or None explicit_api_key = None - if runtime_base_url and (main_provider == "custom" or main_provider.startswith("custom:")): + if runtime_base_url and main_provider == "custom": + # Anonymous custom endpoint (OPENAI_BASE_URL / config.model.base_url) + # — pass through with explicit base_url + api_key. resolved_provider = "custom" explicit_base_url = runtime_base_url explicit_api_key = runtime_api_key or None + elif main_provider.startswith("custom:"): + # Named custom provider (custom_providers / providers dict entry). + # KEEP the full ``custom:`` so resolve_provider_client lands in + # the named-custom-provider arm — that arm honours the entry's + # api_mode (e.g. anthropic_messages → AnthropicAuxiliaryClient, + # avoiding the /anthropic→/v1 rewrite that 404s against proxies + # like Palantir Foundry's Anthropic surface). Do NOT collapse to + # plain "custom"; that path strips /anthropic and routes through + # OpenAI chat.completions. + resolved_provider = main_provider + # base_url / api_key come from the named entry itself — leave the + # explicit_* overrides unset so the named arm reads them from config. + if runtime_api_key: + explicit_api_key = runtime_api_key elif runtime_api_key: # Pin auxiliary to the same api_key as the active main chat session # so that a working key is reused instead of re-selecting from the pool