mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-14 05:37:40 +02:00
Each removal verified by checking actual production callers (frontend,
electron, internal HTTP, MCP-server subprocesses) — not just test
references. Symbols whose only callers were tests are removed along
with those tests.
Production removals (~390 LOC):
- backend/main.py
- websocket_session: drop `agent:edit_message` WS branch. Frontend
only ever uses HTTP `POST /api/agents/sessions/{id}/edit_message`
(frontend/src/shared/state/agentsSlice.ts); nothing on the wire
sends a WS message of this type.
- backend/apps/agents/agent_manager.py
- AgentManager._build_connected_tools_context (~80 LOC): zero call
sites in production; the connected-tools system-prompt context is
built inline in _compose_system_prompt now.
- AgentManager._approx_tokens / _summarize_message_block: pure
helpers whose only callers were tests. The compaction path uses
LLM-driven _maybe_compact instead.
- backend/apps/agents/browser_agent.py
- clear_browser_history: only used by tests. _browser_history is
pruned via the size cap inline.
- MODEL_MAP constant: never read.
- backend/apps/agents/mcp_preflight.py
- DISCOVERY_SCAFFOLDING (~25-line system-prompt block): defined but
never appended anywhere. The header comment described an intended
use that the codebase no longer has.
- backend/apps/agents/providers/registry.py
- thinking_params_for, _is_9router_available, OPENROUTER_BASE_URL,
get_context_window: zero callers in production. Thinking-params
routing is done by the provider classes directly; 9Router presence
is detected at request time; context-window numbers are stamped
onto sessions from BUILTIN_MODELS at launch.
- backend/apps/agents/tools/{base,web}.py
- BaseTool.get_schema (abstract) + WebSearchTool/WebFetchTool
overrides: production code in backend/apps/web/web.py instantiates
these tools and only calls .execute(); the JSON-schema lives in
the HTTP wrapper, not on the tool class.
- backend/apps/outputs/outputs.py
- _resolve_model + MODEL_MAP: tests-only.
- load_output: docstring claimed it was a public helper for "other
modules" but no module imported it.
- backend/apps/service/client.py
- set_user_id, the _user_id module global, and the dead cache short-
circuit in _get_user_id: setter was tests-only. _get_user_id now
reads user_email directly from settings on every call.
- backend/apps/settings/credentials.py
- get_provider_credentials: zero callers. The sibling get_agent_sdk_env
is kept (it has the explicit "Legacy helpers" keep-comment).
Test updates:
- test_agent_manager_unit.py: drop _approx_tokens / _summarize_message_block
cases (5 tests), update module docstring index.
- test_browser_agent_unit.py: drop clear_browser_history cases (2 tests)
and the unused _Boom helper class in the repr-fallback test.
- test_outputs_unit.py: drop _resolve_model / load_output cases
(4 tests), update docstring + import list.
- test_v2_invariants.py: drop get_context_window tests + get_schema
assertions on web tools (kept name + BaseTool inheritance checks).
- test_service.py: rewrite the 4 set_user_id-driven tests to drive
user_id through settings.user_email instead, so _get_user_id's live
envelope-stamping path stays covered.
Verification:
- ruff --select F401,F811,F841 backend/ → clean.
- pytest backend/tests/ → 1167 passed, 1 deselected (pre-existing
sandbox git test, unrelated). No tests dropped silently — every
deletion is paired with the corresponding test removal/rewrite.
- Dead-code scan re-run: dead WS events 1→0, Tier-2 high-confidence
14→11 (residue is SDK-callback `context` params + Pydantic `cls`
validators — both false positives vulture can't see through),
vulture total 165→145.
Total diff: -565 / +34 LOC across 15 files.
Co-authored-by: Cursor <cursoragent@cursor.com>
145 lines
5.6 KiB
Python
145 lines
5.6 KiB
Python
"""Centralized credential resolution for LLM API calls.
|
|
|
|
Supports multiple providers: Anthropic (native), OpenAI, Gemini,
|
|
OpenRouter, and user-configured custom providers.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
import anthropic
|
|
from backend.apps.settings.models import AppSettings
|
|
|
|
OPENSWARM_DEFAULT_PROXY_URL = "https://api.openswarm.com"
|
|
|
|
|
|
def _check_9router() -> bool:
|
|
"""Check if 9Router is running locally."""
|
|
try:
|
|
import httpx
|
|
r = httpx.get("http://localhost:20128/v1/models", timeout=2.0)
|
|
return r.status_code == 200
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def validate_credentials(settings: AppSettings, provider: str = "anthropic") -> None:
|
|
"""Raise ValueError if credentials are missing for the given provider.
|
|
|
|
Allows through if 9Router is running as a fallback.
|
|
Handles both display names ('Anthropic') and lowercase ('anthropic').
|
|
"""
|
|
p = provider.lower().strip()
|
|
|
|
# 9Router-backed providers don't need traditional credentials
|
|
if p == "9router":
|
|
return
|
|
|
|
# If 9Router is running, all providers are accessible
|
|
if _check_9router():
|
|
return
|
|
|
|
if p == "anthropic":
|
|
if getattr(settings, "connection_mode", "own_key") == "openswarm-pro":
|
|
if not getattr(settings, "openswarm_bearer_token", None):
|
|
raise ValueError("Open Swarm account not connected. Sign in via Settings -> API.")
|
|
return
|
|
if settings.anthropic_api_key:
|
|
return
|
|
raise ValueError("Anthropic API key not configured. Set it in Settings, or connect a subscription.")
|
|
elif p == "openai":
|
|
if settings.openai_api_key:
|
|
return
|
|
raise ValueError("OpenAI API key not configured. Set it in Settings, or connect a subscription.")
|
|
elif p in ("gemini", "google"):
|
|
if getattr(settings, "google_api_key", None):
|
|
return
|
|
raise ValueError("Google API key not configured. Set it in Settings, or connect a subscription.")
|
|
elif p == "openrouter":
|
|
if getattr(settings, "openrouter_api_key", None):
|
|
return
|
|
raise ValueError("OpenRouter API key not configured. Set it in Settings.")
|
|
elif p in ("xai", "meta", "deepseek", "mistral", "qwen", "cohere"):
|
|
# These route through OpenRouter — need either OpenRouter key or 9Router
|
|
if getattr(settings, "openrouter_api_key", None):
|
|
return
|
|
raise ValueError(f"{provider} requires an OpenRouter API key, or connect a subscription via 9Router.")
|
|
else:
|
|
# Custom provider — check if it exists in custom_providers
|
|
for cp in getattr(settings, "custom_providers", []):
|
|
if cp.name.lower() == p:
|
|
return
|
|
# Unknown provider — allow through (create_provider will handle the error)
|
|
return
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Legacy helpers (kept for backward compat during migration)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def get_agent_sdk_env(settings: AppSettings) -> dict[str, str]:
|
|
"""Return the env dict for ClaudeAgentOptions based on connection mode.
|
|
|
|
DEPRECATED: Use create_provider() from providers.registry instead.
|
|
"""
|
|
validate_credentials(settings, "anthropic")
|
|
|
|
if getattr(settings, "connection_mode", "own_key") == "openswarm-pro":
|
|
proxy_url = getattr(settings, "openswarm_proxy_url", None) or OPENSWARM_DEFAULT_PROXY_URL
|
|
return {
|
|
"ANTHROPIC_AUTH_TOKEN": getattr(settings, "openswarm_bearer_token", ""),
|
|
"ANTHROPIC_BASE_URL": proxy_url,
|
|
}
|
|
|
|
return {"ANTHROPIC_API_KEY": settings.anthropic_api_key}
|
|
|
|
|
|
def get_anthropic_client(settings: AppSettings) -> anthropic.AsyncAnthropic:
|
|
"""Return a configured AsyncAnthropic client based on connection mode.
|
|
|
|
Priority: managed mode → 9Router subscription → API key
|
|
"""
|
|
import anthropic
|
|
|
|
if getattr(settings, "connection_mode", "own_key") == "openswarm-pro":
|
|
proxy_url = getattr(settings, "openswarm_proxy_url", None) or OPENSWARM_DEFAULT_PROXY_URL
|
|
return anthropic.AsyncAnthropic(
|
|
auth_token=getattr(settings, "openswarm_bearer_token", None),
|
|
base_url=proxy_url,
|
|
)
|
|
|
|
# Prefer API key when set
|
|
if settings.anthropic_api_key:
|
|
return anthropic.AsyncAnthropic(api_key=settings.anthropic_api_key)
|
|
|
|
# Fall back to 9Router subscription (free for users with Claude/ChatGPT/Gemini subscriptions)
|
|
if _check_9router():
|
|
return anthropic.AsyncAnthropic(
|
|
api_key="9router",
|
|
base_url="http://localhost:20128",
|
|
)
|
|
|
|
raise ValueError("No AI provider configured. Set an API key or connect a subscription.")
|
|
|
|
|
|
def get_anthropic_client_for_model(settings: AppSettings, api_model: str) -> anthropic.AsyncAnthropic:
|
|
"""Return a client configured for the given resolved model id.
|
|
|
|
When api_model carries a 9Router prefix (cc/, cx/, gc/), the client
|
|
targets 9Router directly — even if connection_mode is openswarm-pro. This
|
|
is what lets pinned-route models like "sonnet-cc" actually reach the
|
|
user's own subscription instead of getting sent through the managed proxy
|
|
with an unrecognizable model id.
|
|
Otherwise delegates to get_anthropic_client() for the default mode-driven
|
|
routing.
|
|
"""
|
|
import anthropic
|
|
if isinstance(api_model, str) and api_model.startswith(("cc/", "cx/", "gc/")):
|
|
return anthropic.AsyncAnthropic(
|
|
api_key="9router",
|
|
base_url="http://localhost:20128",
|
|
)
|
|
return get_anthropic_client(settings)
|