diff --git a/src/mnemosyne/gateway.py b/src/mnemosyne/gateway.py index 1afeb1d..e915a03 100644 --- a/src/mnemosyne/gateway.py +++ b/src/mnemosyne/gateway.py @@ -2587,24 +2587,43 @@ def create_app( } # OAuth: replace dummy x-api-key with Bearer token when available. - # Anthropic requires the "oauth-2025-04-20" beta header and a - # Claude-CLI-style user-agent for OAuth Bearer auth to be accepted. - # See: treplay/server.mjs proxyAnthropicContinuation() + # Anthropic requires specific beta headers and a Claude-CLI-style + # user-agent for OAuth Bearer auth to be accepted. + # Ref: rmk40/opencode-anthropic-auth request-headers.mjs oauth_token = os.environ.get("ANTHROPIC_AUTH_TOKEN") if oauth_token and provider == "anthropic": headers.pop("x-api-key", None) headers.pop("X-Api-Key", None) headers["authorization"] = f"Bearer {oauth_token}" - # Beta flag is REQUIRED — without it, Anthropic returns - # "OAuth authentication is currently not supported" + + # Required beta flags — must match Claude CLI expectations. + _OAUTH_BETAS = [ + "claude-code-20250219", + "oauth-2025-04-20", + "context-1m-2025-08-07", + "interleaved-thinking-2025-05-14", + "redact-thinking-2026-02-12", + "prompt-caching-scope-2026-01-05", + "advanced-tool-use-2025-11-20", + "effort-2025-11-24", + ] + # Add opus-specific betas if model is opus + model_name = req.model.lower() if req.model else "" + if "opus" in model_name: + _OAUTH_BETAS.append("context-management-2025-06-27") + existing_beta = headers.get("anthropic-beta", "") - oauth_beta = "oauth-2025-04-20" - if oauth_beta not in existing_beta: - if existing_beta: - headers["anthropic-beta"] = f"{existing_beta},{oauth_beta}" - else: - headers["anthropic-beta"] = oauth_beta - headers["user-agent"] = "claude-cli/2.1.2 (external, cli)" + existing_set = set(b.strip() for b in existing_beta.split(",") if b.strip()) + merged = list(dict.fromkeys(list(existing_set) + _OAUTH_BETAS)) # preserve order, dedup + headers["anthropic-beta"] = ",".join(merged) + headers["user-agent"] = "claude-cli/2.1.75 (external, cli)" + headers.setdefault("anthropic-dangerous-direct-browser-access", "true") + headers.setdefault("x-app", "cli") + + # Add ?beta=true to /v1/messages URL + if endpoint == "messages" and "beta=true" not in upstream_path: + sep = "&" if "?" in upstream_path else "?" + upstream_path = f"{upstream_path}{sep}beta=true" if req.stream: @@ -2831,13 +2850,21 @@ def create_app( oauth_token = os.environ.get("ANTHROPIC_AUTH_TOKEN") if oauth_token: headers["authorization"] = f"Bearer {oauth_token}" - existing_beta = headers.get("anthropic-beta", "") - oauth_beta = "oauth-2025-04-20" - if oauth_beta not in existing_beta: - headers["anthropic-beta"] = ( - f"{existing_beta},{oauth_beta}" if existing_beta else oauth_beta - ) - headers["user-agent"] = "claude-cli/2.1.2 (external, cli)" + headers["anthropic-beta"] = ",".join( + [ + "claude-code-20250219", + "oauth-2025-04-20", + "context-1m-2025-08-07", + "interleaved-thinking-2025-05-14", + "redact-thinking-2026-02-12", + "prompt-caching-scope-2026-01-05", + "advanced-tool-use-2025-11-20", + "effort-2025-11-24", + ] + ) + headers["user-agent"] = "claude-cli/2.1.75 (external, cli)" + headers["anthropic-dangerous-direct-browser-access"] = "true" + headers["x-app"] = "cli" else: headers["x-api-key"] = api_key url = f"https://api.anthropic.com/v1/{path}"