mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-21 12:12:22 +02:00
224 lines
13 KiB
Python
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.")
|