Files
openswarm/backend/apps/agents/manager/configure_provider_env.py
T

224 lines
13 KiB
Python

"""Configure the SDK environment for the run's provider route: set ANTHROPIC/OPENAI/GOOGLE
auth env vars (direct key, OpenSwarm Pro proxy, OpenRouter, or 9Router) and pin subagent models,
ensuring 9Router is up where the route needs it. sub_conns is the active-connection list for
subagent-model fallback (empty today)."""
import os
from typing import Dict, List, Optional
from typeguard import typechecked
from backend.apps.agents.core.models import AgentSession
from backend.apps.settings.models import AppSettings
from backend.auth import get_auth_token
logger = __import__("logging").getLogger(__name__)
@typechecked
async def configure_provider_env(
options_kwargs: Dict,
session: AgentSession,
resolved_model: object,
api_type: Optional[str],
global_settings: AppSettings,
sub_conns: List,
) -> None:
from backend.apps.nine_router import is_running as nine_router_running
from backend.apps.agents.providers.registry import NINEROUTER_MODEL_PREFIXES as NINEROUTER_MODEL_PREFIXES
resolved_is_9router = isinstance(resolved_model, str) and resolved_model.startswith(NINEROUTER_MODEL_PREFIXES)
from backend.apps.agents.providers.registry import find_builtin_model as find_builtin_model
model_entry = find_builtin_model(session.model)
is_pinned_api_route = (
model_entry is not None
and model_entry.get("route") == "api"
)
api_route_provider = (model_entry or {}).get("api") if is_pinned_api_route else None
if is_pinned_api_route and api_route_provider == "anthropic" and getattr(global_settings, "anthropic_api_key", None):
options_kwargs["env"] = {
"ANTHROPIC_API_KEY": global_settings.anthropic_api_key,
"ANTHROPIC_BASE_URL": "https://api.anthropic.com",
# Pin subagents so they don't drift back to the proxy.
"CLAUDE_CODE_SUBAGENT_MODEL": "claude-sonnet-4-6",
"ANTHROPIC_SMALL_FAST_MODEL": "claude-haiku-4-5",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4-5",
}
logger.info(f"[MCP-DEBUG] Using direct Anthropic API key (route=api) for {session.model}")
elif is_pinned_api_route and api_route_provider == "openai" and getattr(global_settings, "openai_api_key", None):
# openai-passthrough renames max_tokens->max_completion_tokens before relaying (GPT-5 400s on max_tokens; 9Router 0.3.60, pinned for WebSearch, emits the legacy field).
passthrough_url = f"http://127.0.0.1:{os.environ.get('OPENSWARM_PORT', '8324')}/api/openai-passthrough/v1"
options_kwargs["env"] = {
"OPENAI_API_KEY": global_settings.openai_api_key,
"OPENAI_BASE_URL": passthrough_url,
"ANTHROPIC_API_KEY": get_auth_token() or "9router",
"ANTHROPIC_BASE_URL": "http://localhost:20128",
}
logger.info(f"[MCP-DEBUG] Using direct OpenAI API key (route=api) for {session.model} via openai-passthrough")
elif is_pinned_api_route and api_route_provider == "custom":
# User OpenAI-compatible endpoint (Ollama/Together/LM Studio) via 9Router's synced provider node.
from backend.apps.nine_router import ensure_running as p_9r_ensure_c
if not nine_router_running():
logger.info(f"[MCP-DEBUG] custom provider selected but 9Router not running; waiting for startup")
await p_9r_ensure_c()
if not nine_router_running():
raise ValueError(
"9Router could not start. Custom OpenAI-compatible "
"providers need 9Router to translate the Anthropic "
"protocol, install Node.js and restart the app."
)
from backend.apps.agents.providers.registry import find_custom_provider_for_value as find_custom_provider_for_value
cp = find_custom_provider_for_value(global_settings, session.model)
env = {
"ANTHROPIC_API_KEY": "9router",
"ANTHROPIC_BASE_URL": "http://localhost:20128",
"ENABLE_TOOL_SEARCH": "auto",
}
if cp:
# Local servers often run auth-disabled; placeholder key since the OpenAI SDK requires non-empty.
env["OPENAI_API_KEY"] = (cp.api_key or "").strip() or "no-auth-required"
from backend.apps.nine_router import normalize_openai_compat_base_url as norm_cp_url
env["OPENAI_BASE_URL"] = norm_cp_url(cp.base_url or "")
# Pin subagents or CLI's default Haiku 4.5 404s on the custom provider.
if global_settings.anthropic_api_key:
env["CLAUDE_CODE_SUBAGENT_MODEL"] = "claude-sonnet-4-6"
env["ANTHROPIC_SMALL_FAST_MODEL"] = "claude-haiku-4-5-20251001"
env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = "claude-haiku-4-5-20251001"
else:
# No Anthropic key: pin subagents to the custom model so they stay on the user's endpoint.
env["CLAUDE_CODE_SUBAGENT_MODEL"] = resolved_model
env["ANTHROPIC_SMALL_FAST_MODEL"] = resolved_model
env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = resolved_model
options_kwargs["env"] = env
logger.info(f"[MCP-DEBUG] Using custom provider for {session.model}{resolved_model}")
elif is_pinned_api_route and api_route_provider == "gemini" and getattr(global_settings, "google_api_key", None):
# Local anthropic-proxy scrubs JSON-Schema fields Gemini rejects ($schema, additionalProperties, propertyNames, exclusiveMinimum, nested const) that 9Router 0.3.60 misses.
proxy_url = f"http://127.0.0.1:{os.environ.get('OPENSWARM_PORT', '8324')}/api/anthropic-proxy"
options_kwargs["env"] = {
"GEMINI_API_KEY": global_settings.google_api_key,
"GOOGLE_API_KEY": global_settings.google_api_key,
"ANTHROPIC_API_KEY": get_auth_token() or "9router",
"ANTHROPIC_BASE_URL": proxy_url,
}
logger.info(f"[MCP-DEBUG] Using direct Google API key (route=api) for {session.model} via local proxy")
elif api_type == "openrouter" and getattr(global_settings, "openrouter_api_key", None):
# OpenRouter via 9Router; with no Anthropic key/sub, fall back to OR's resold Claude for subagents (incl. WebSearch delegation) so they stay on the same OR billing.
if not nine_router_running():
from backend.apps.nine_router import ensure_running as nine_router_ensure
logger.info(f"[MCP-DEBUG] OpenRouter selected but 9Router not running; waiting for startup")
await nine_router_ensure()
if not nine_router_running():
raise ValueError(
"9Router could not start. OpenRouter routing requires "
"Node.js, install it and restart the app, or pick a "
"model that uses a direct API key (Anthropic, OpenAI, "
"or Google AI Studio)."
)
env = {
"ANTHROPIC_API_KEY": "9router",
"ANTHROPIC_BASE_URL": "http://localhost:20128",
}
if global_settings.anthropic_api_key:
env["CLAUDE_CODE_SUBAGENT_MODEL"] = "claude-sonnet-4-6"
env["ANTHROPIC_SMALL_FAST_MODEL"] = "claude-haiku-4-5-20251001"
env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = "claude-haiku-4-5-20251001"
else:
env["CLAUDE_CODE_SUBAGENT_MODEL"] = "openrouter/anthropic/claude-sonnet-4.5"
env["ANTHROPIC_SMALL_FAST_MODEL"] = "openrouter/anthropic/claude-haiku-4.5"
env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = "openrouter/anthropic/claude-haiku-4.5"
env["ENABLE_TOOL_SEARCH"] = "auto"
options_kwargs["env"] = env
logger.info(f"[MCP-DEBUG] Using OpenRouter for {session.model}")
elif api_type == "anthropic" and not resolved_is_9router and getattr(global_settings, "connection_mode", "own_key") in ("openswarm-pro", "free-trial"):
from backend.apps.settings.credentials import proxy_auth
bearer, proxy_url = proxy_auth(global_settings)
bearer = bearer or ""
options_kwargs["env"] = {
"ANTHROPIC_AUTH_TOKEN": bearer,
"ANTHROPIC_BASE_URL": proxy_url,
# Pin subagents; Pro rejects CLI's default haiku as "No credentials for provider: anthropic".
"CLAUDE_CODE_SUBAGENT_MODEL": "claude-sonnet-4-6",
"ANTHROPIC_SMALL_FAST_MODEL": "claude-haiku-4-5-20251001",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4-5-20251001",
# auto: tengu_defer_all_bn4 defers every tool, which collides with cache_control and 400s the first tool-laden request.
"ENABLE_TOOL_SEARCH": "auto",
}
# Tag every call (subagents inherit env) + aux calls with the task id so a query plus its title-gen meter as ONE free run; base goes straight to cloud (no 9Router) so the header rides.
if getattr(global_settings, "connection_mode", "own_key") == "free-trial":
options_kwargs["env"]["ANTHROPIC_CUSTOM_HEADERS"] = f"X-Openswarm-Task-Id: {session.id}"
# Cloud serves every free run as Haiku; keep the subagent on Haiku too (sonnet adds `effort`, Haiku 400s).
options_kwargs["env"]["CLAUDE_CODE_SUBAGENT_MODEL"] = "claude-haiku-4-5-20251001"
logger.info(f"[MCP-DEBUG] Using OpenSwarm cloud proxy at {proxy_url}")
elif api_type == "anthropic" and not resolved_is_9router and global_settings.anthropic_api_key:
options_kwargs["env"] = {"ANTHROPIC_API_KEY": global_settings.anthropic_api_key}
logger.info("[MCP-DEBUG] Using direct Anthropic API key")
elif nine_router_running():
# Gemini-bound ids go through the local proxy for schema scrubbing; everything else hits 9Router directly.
is_gemini_bound = (
isinstance(resolved_model, str)
and resolved_model.startswith(("gemini/", "gc/", "ag/"))
)
if is_gemini_bound:
base_url = f"http://127.0.0.1:{os.environ.get('OPENSWARM_PORT', '8324')}/api/anthropic-proxy"
env = {
"ANTHROPIC_API_KEY": get_auth_token() or "9router",
"ANTHROPIC_BASE_URL": base_url,
}
else:
env = {
"ANTHROPIC_API_KEY": "9router",
"ANTHROPIC_BASE_URL": "http://localhost:20128",
}
# Pin subagents to whichever lane the user has, else CLI's default Haiku 4.5 hits 9Router with no Claude route and 401s. NOTE: callers pass sub_conns=[] today so this is inert (latent regression from the run/ split; pyright caught the dangling _conns ref).
active = {c.get("provider") for c in sub_conns
if isinstance(c, dict) and c.get("isActive")}
sub_model = None
small_model = None
if global_settings.anthropic_api_key:
sub_model = "claude-sonnet-4-6"
small_model = "claude-haiku-4-5-20251001"
elif "claude" in active or "anthropic" in active:
sub_model = "cc/claude-sonnet-4-6"
small_model = "cc/claude-haiku-4-5-20251001"
elif "antigravity" in active:
sub_model = "ag/gemini-3-flash"
small_model = "ag/gemini-3-flash"
elif "gemini-cli" in active:
sub_model = "gc/gemini-2.5-flash"
small_model = "gc/gemini-2.5-flash"
elif "codex" in active:
sub_model = "cx/gpt-5.4-mini"
small_model = "cx/gpt-5.4-mini"
if sub_model:
env["CLAUDE_CODE_SUBAGENT_MODEL"] = sub_model
if small_model:
env["ANTHROPIC_SMALL_FAST_MODEL"] = small_model
env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = small_model
logger.info(
f"[MCP-DEBUG] 9Router direct, subagent_model={sub_model}, small_fast={small_model}"
)
# auto eagerly loads tools when the schema budget fits; without it tengu_defer_all_bn4 defers 16 tools unloadable off Anthropic networks. Don't use --bare (strips system prompt).
env["ENABLE_TOOL_SEARCH"] = "auto"
options_kwargs["env"] = env
logger.info(f"[MCP-DEBUG] Using 9Router (api_type={api_type})")
else:
if api_type != "anthropic":
from backend.apps.nine_router import ensure_running as nine_router_ensure
logger.info(f"[MCP-DEBUG] 9Router not running for non-Anthropic model {session.model}; waiting for startup")
await nine_router_ensure()
if nine_router_running():
options_kwargs["env"] = {
"ANTHROPIC_API_KEY": "9router",
"ANTHROPIC_BASE_URL": "http://localhost:20128",
}
logger.info(f"[MCP-DEBUG] 9Router started; routing {session.model} via 9Router")
else:
raise ValueError(
f"9Router is not running; cannot use {session.model}. "
"Install Node.js and restart the app, or switch to a model "
"with a direct API key."
)
else:
raise ValueError("No AI provider configured. Set an API key or connect a subscription.")