mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-13 21:27:41 +02:00
[arnav] remove dead code identified by audit
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>
This commit is contained in:
@@ -450,86 +450,6 @@ class AgentManager:
|
||||
logger.info(f"[MCP-DEBUG] Final mcp_servers: {list(mcp_servers.keys())}")
|
||||
return mcp_servers
|
||||
|
||||
def _build_connected_tools_context(self, allowed_tools: list[str]) -> str | None:
|
||||
"""Build a context block describing connected MCP tools and their accounts.
|
||||
|
||||
Tools set to 'deny' and fully-denied servers are excluded.
|
||||
"""
|
||||
all_tools = load_all_tools()
|
||||
mcp_tools = [t for t in all_tools if t.mcp_config and t.enabled and t.auth_status in ("configured", "connected")]
|
||||
|
||||
sections = []
|
||||
for tool in mcp_tools:
|
||||
tool_ref = f"mcp:{tool.name}"
|
||||
if tool_ref not in allowed_tools and allowed_tools != get_all_tool_names():
|
||||
continue
|
||||
|
||||
if _is_fully_denied(tool):
|
||||
continue
|
||||
|
||||
server_name = _sanitize_server_name(tool.name)
|
||||
denied = _get_denied_tool_names(tool)
|
||||
tool_descs = {
|
||||
k: v for k, v in tool.tool_permissions.get("_tool_descriptions", {}).items()
|
||||
if k not in denied
|
||||
}
|
||||
if not tool_descs:
|
||||
continue
|
||||
|
||||
lines = [f"MCP Server: {server_name}"]
|
||||
lines.append(f" Status: {tool.auth_status}")
|
||||
|
||||
if tool.connected_account_email:
|
||||
lines.append(f" Connected account: {tool.connected_account_email}")
|
||||
lines.append(
|
||||
f" IMPORTANT: When calling tools from this server that require an email "
|
||||
f"parameter (e.g. user_google_email, user_email), always use "
|
||||
f"\"{tool.connected_account_email}\" automatically — do NOT ask the user."
|
||||
)
|
||||
|
||||
# Discord guild scoping — hard restriction. The bot may technically
|
||||
# be in other servers (across other OpenSwarm users), but this
|
||||
# specific user only authorized these guild IDs.
|
||||
if tool.name.lower() == "discord":
|
||||
guilds = tool.oauth_tokens.get("guilds") or []
|
||||
if guilds:
|
||||
guild_descriptions = ", ".join(
|
||||
f"{g.get('name', 'Unknown')} ({g.get('id', '')})" for g in guilds
|
||||
)
|
||||
allowed_ids = [g.get("id", "") for g in guilds if g.get("id")]
|
||||
lines.append(
|
||||
f" AUTHORIZED DISCORD SERVERS (guild_ids): {guild_descriptions}"
|
||||
)
|
||||
lines.append(
|
||||
f" HARD RESTRICTION: You MUST only call Discord tools that operate on "
|
||||
f"these guild_ids: {allowed_ids}. NEVER call Discord tools on any other "
|
||||
f"guild_id even if the bot has access to it. NEVER list, search, or "
|
||||
f"enumerate servers outside this list. If a user asks about a server "
|
||||
f"not in this list, refuse and tell them to authorize it via the Connect "
|
||||
f"Discord button. This is a security boundary, not a preference."
|
||||
)
|
||||
else:
|
||||
lines.append(
|
||||
f" No Discord servers authorized yet. Tell the user to click "
|
||||
f"'Connect Discord' to add a server before attempting any Discord actions."
|
||||
)
|
||||
|
||||
tool_names = list(tool_descs.keys())
|
||||
if tool_names:
|
||||
lines.append(f" Available tools ({len(tool_names)}): {', '.join(tool_names)}")
|
||||
|
||||
sections.append("\n".join(lines))
|
||||
|
||||
if not sections:
|
||||
return None
|
||||
return (
|
||||
"<connected_mcp_tools>\n"
|
||||
"The following MCP tool servers are connected and available. "
|
||||
"Use them directly when relevant to the user's request.\n\n"
|
||||
+ "\n\n".join(sections)
|
||||
+ "\n</connected_mcp_tools>"
|
||||
)
|
||||
|
||||
def _build_outputs_context(self, active_outputs: list[str] | None = None) -> str | None:
|
||||
"""Outputs context for the system prompt.
|
||||
|
||||
@@ -962,86 +882,6 @@ class AgentManager:
|
||||
return ""
|
||||
return "<prior_conversation>\n" + "\n".join(lines) + "\n</prior_conversation>"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Compaction & token guard (Phase 2)
|
||||
#
|
||||
# Triggered by *live* context-usage ratio, not turn count. The signal
|
||||
# is the same `ctx_used_pct` we already broadcast to the UI on every
|
||||
# turn: input_tokens / context_window. Three escalating thresholds:
|
||||
# - compact_threshold_pct (default 0.65): summarize stale tool_results
|
||||
# and old user/assistant pairs before the next query() call
|
||||
# - context_soft_cap_pct (default 0.90): pre-send hard guard. After
|
||||
# compaction, if still over, LRU-trim active_outputs/active_mcps
|
||||
# - >= 1.0 hits the proxy/Anthropic 200K ceiling — friendly card
|
||||
# surfaces from the catch-all
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def _approx_tokens(text: str) -> int:
|
||||
"""Conservative chars/4 estimate. Used for the pre-send guard
|
||||
and the compaction trigger when a precise count_tokens isn't
|
||||
cheap (or the route isn't Anthropic). Errs slightly high so we
|
||||
compact a touch earlier than strictly necessary."""
|
||||
return max(1, len(text or "") // 4)
|
||||
|
||||
@staticmethod
|
||||
def _summarize_message_block(messages: list) -> str:
|
||||
"""Programmatic, no-LLM summary of a message slice. Mirrors the
|
||||
shape of browser_agent._summarize_messages: extracts the original
|
||||
user task, counts tool calls, captures the last assistant text.
|
||||
Cheap, deterministic, and never makes a network call — so
|
||||
compaction itself adds zero latency to the user's turn.
|
||||
"""
|
||||
if not messages:
|
||||
return ""
|
||||
|
||||
initial_task = ""
|
||||
for m in messages:
|
||||
if getattr(m, "role", "") == "user":
|
||||
content = getattr(m, "content", "")
|
||||
txt = content if isinstance(content, str) else str(content)
|
||||
if txt.strip():
|
||||
initial_task = txt.strip()[:400]
|
||||
break
|
||||
|
||||
tool_calls_by_name: dict[str, int] = {}
|
||||
last_tool_results = 0
|
||||
last_assistant_text = ""
|
||||
for m in messages:
|
||||
role = getattr(m, "role", "")
|
||||
if role == "tool_call":
|
||||
content = getattr(m, "content", {}) or {}
|
||||
name = (content.get("tool") if isinstance(content, dict) else None) or "unknown"
|
||||
tool_calls_by_name[name] = tool_calls_by_name.get(name, 0) + 1
|
||||
elif role == "tool_result":
|
||||
last_tool_results += 1
|
||||
elif role == "assistant":
|
||||
content = getattr(m, "content", "")
|
||||
if isinstance(content, str) and content.strip():
|
||||
last_assistant_text = content.strip()
|
||||
elif isinstance(content, list):
|
||||
for block in content:
|
||||
if isinstance(block, dict) and block.get("type") == "text":
|
||||
txt = (block.get("text") or "").strip()
|
||||
if txt:
|
||||
last_assistant_text = txt
|
||||
|
||||
parts = ["<compacted_history>"]
|
||||
parts.append("[The following is a programmatic summary of earlier turns in this session. Originals are preserved on disk and viewable via the chat UI's compaction drawer.]")
|
||||
if initial_task:
|
||||
parts.append(f'Initial user request: "{initial_task}"')
|
||||
if tool_calls_by_name:
|
||||
total = sum(tool_calls_by_name.values())
|
||||
top = sorted(tool_calls_by_name.items(), key=lambda kv: -kv[1])[:8]
|
||||
parts.append(f"Tool calls so far ({total} total): " + ", ".join(f"{n}×{c}" for n, c in top))
|
||||
if last_tool_results:
|
||||
parts.append(f"Tool results received: {last_tool_results}")
|
||||
if last_assistant_text:
|
||||
parts.append("Last assistant message:")
|
||||
parts.append(last_assistant_text[:1200])
|
||||
parts.append("</compacted_history>")
|
||||
return "\n".join(parts)
|
||||
|
||||
def _maybe_compact(self, session: AgentSession, force: bool = False) -> bool:
|
||||
"""Run summarizer when ctx_used_pct >= compact_threshold_pct (or force).
|
||||
|
||||
|
||||
@@ -20,12 +20,6 @@ from backend.apps.tools_lib.tools_lib import load_builtin_permissions
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MODEL_MAP = {
|
||||
"sonnet": "claude-sonnet-4-6",
|
||||
"opus": "claude-opus-4-6",
|
||||
"haiku": "claude-haiku-4-5-20251001",
|
||||
}
|
||||
|
||||
# Cache of conversation history per browser_id so successive BrowserAgent
|
||||
# calls on the same browser can resume rather than restart from scratch.
|
||||
# Without this every "swipe right" / "swipe left" call has to take a new
|
||||
@@ -35,11 +29,6 @@ _browser_history: dict[str, list[dict]] = {}
|
||||
_MAX_HISTORY_MESSAGES = 30
|
||||
|
||||
|
||||
def clear_browser_history(browser_id: str) -> None:
|
||||
"""Drop cached conversation history for a browser (e.g. when it's closed)."""
|
||||
_browser_history.pop(browser_id, None)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Loop detection
|
||||
#
|
||||
|
||||
@@ -32,35 +32,6 @@ from backend.apps.tools_lib.tools_lib import _load_all as load_all_tools
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool-agnostic discovery scaffolding — appended to the agent's system prompt
|
||||
# only when preflight flags the prompt as vague/information-gathering.
|
||||
# ---------------------------------------------------------------------------
|
||||
DISCOVERY_SCAFFOLDING = (
|
||||
"# Discovery before action\n"
|
||||
"When a request is vague or could be grounded in user context, do not "
|
||||
"guess generic defaults. First silently enumerate what would change the "
|
||||
"output — voice, tone, audience, prior context, recent precedent, facts "
|
||||
"that only live in the user's data. Then look at your available tools and "
|
||||
"pick the ones that could answer those unknowns. Read a few examples "
|
||||
"(usually 3–10 is enough), summarize what you found into a few bullets, "
|
||||
"then act confidently.\n\n"
|
||||
"Tool-selection hierarchy for information gathering:\n"
|
||||
" 1. Direct local access (filesystem reads, code search, shell) — "
|
||||
"cheapest and fastest.\n"
|
||||
" 2. Connected services / MCP tools — for user data that lives in a "
|
||||
"linked account (email, calendar, notes, tickets, etc.).\n"
|
||||
" 3. Web search / fetch — for public information that isn't in your "
|
||||
"training cutoff.\n"
|
||||
" 4. Browser automation — only when a real interactive session or "
|
||||
"login is required.\n"
|
||||
" 5. Sub-agents — only for parallelizable subtasks or to isolate heavy "
|
||||
"context. Not for serial steps.\n\n"
|
||||
"Asking the user is a fallback, not a first move. Never fabricate. If "
|
||||
"no tool can ground a critical unknown, ask one concise question."
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Curated MCP shortlist. These `id` values MUST match the exact `name` field
|
||||
# on ToolDefinition entries that OpenSwarm ships as defaults (see
|
||||
|
||||
@@ -184,92 +184,6 @@ BUILTIN_MODELS: dict[str, list[dict[str, Any]]] = {
|
||||
],
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Thinking level translation
|
||||
# ---------------------------------------------------------------------------
|
||||
# Each provider has a different API shape for "how hard should the model
|
||||
# think." We expose a single provider-agnostic level (off/low/medium/high/
|
||||
# auto) on the session and translate here.
|
||||
#
|
||||
# Returns the provider-specific payload to merge into request params, or
|
||||
# None if no special thinking params should be sent (use defaults).
|
||||
|
||||
def thinking_params_for(api: str, level: str, model_id: str = "") -> dict | None:
|
||||
"""Translate a provider-agnostic thinking level to per-provider API params.
|
||||
|
||||
Args:
|
||||
api: "anthropic" | "codex" | "gemini-cli"
|
||||
level: "off" | "low" | "medium" | "high" | "auto"
|
||||
model_id: optional, used to pick adaptive vs legacy for Claude
|
||||
|
||||
Returns a dict to merge into request params, or None for "use defaults".
|
||||
"""
|
||||
if level == "auto":
|
||||
# Let provider use its own default. For Claude 4.6 we still want
|
||||
# adaptive thinking on by default so users see reasoning.
|
||||
if api == "anthropic":
|
||||
return {"thinking": {"type": "adaptive"}}
|
||||
return None
|
||||
|
||||
if level == "off":
|
||||
if api == "anthropic":
|
||||
return {"thinking": {"type": "disabled"}}
|
||||
if api == "codex":
|
||||
return {"reasoning": {"effort": "none"}}
|
||||
# Gemini: thinkingBudget=0 truly disables reasoning (no
|
||||
# thoughtSignature emitted). Critical for multi-step tool turns
|
||||
# — without this Gemini 2.5/3.x still emits signatures even at
|
||||
# the lowest "level," which then break the next request with
|
||||
# "Thought signature is not valid" 400 because the SDK has no
|
||||
# way to round-trip them. The translator at 9Router 0.3.60
|
||||
# explicitly checks `thinkingBudget == 0` to skip emitting
|
||||
# thinking config, which is what we want.
|
||||
if api == "gemini-cli":
|
||||
return {"thinkingConfig": {"thinkingBudget": 0}}
|
||||
return None
|
||||
|
||||
# Claude 4.6 models use adaptive thinking (no manual budget). For older
|
||||
# Claude models we'd use budget_tokens; we don't ship those today.
|
||||
if api == "anthropic":
|
||||
return {"thinking": {"type": "adaptive"}}
|
||||
|
||||
if api == "codex":
|
||||
effort_map = {"low": "low", "medium": "medium", "high": "high"}
|
||||
return {"reasoning": {"effort": effort_map[level]}}
|
||||
|
||||
if api == "gemini-cli":
|
||||
level_map = {"low": "LOW", "medium": "MEDIUM", "high": "HIGH"}
|
||||
return {"thinkingConfig": {"thinkingLevel": level_map[level]}}
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OpenRouter: built-in integration for 300+ models
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
|
||||
|
||||
_9router_cache: dict = {"available": None, "checked_at": 0}
|
||||
|
||||
|
||||
def _is_9router_available() -> bool:
|
||||
"""Check if 9Router is running on localhost:20128. Caches for 30 seconds."""
|
||||
import time as _time
|
||||
now = _time.time()
|
||||
if _9router_cache["available"] is not None and now - _9router_cache["checked_at"] < 30:
|
||||
return _9router_cache["available"]
|
||||
try:
|
||||
import httpx
|
||||
r = httpx.get("http://localhost:20128/v1/models", timeout=2.0)
|
||||
available = r.status_code == 200
|
||||
except Exception:
|
||||
available = False
|
||||
_9router_cache["available"] = available
|
||||
_9router_cache["checked_at"] = now
|
||||
return available
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Model resolution (used by the live claude_agent_sdk path)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -485,24 +399,6 @@ async def resolve_aux_model(
|
||||
)
|
||||
|
||||
|
||||
def get_context_window(provider: str, model: str, settings: AppSettings | None = None) -> int:
|
||||
"""Look up context window for any model."""
|
||||
# Check built-in models first
|
||||
for models in BUILTIN_MODELS.values():
|
||||
for m in models:
|
||||
if m["value"] == model:
|
||||
return m.get("context_window", 128_000)
|
||||
|
||||
# Check custom providers
|
||||
if settings:
|
||||
for cp in getattr(settings, "custom_providers", []):
|
||||
for m in cp.models:
|
||||
if m.get("value") == model or m.get("id") == model:
|
||||
return m.get("context_window", 128_000)
|
||||
|
||||
return 128_000 # safe default
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cost tracking
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -14,10 +14,6 @@ class BaseTool(ABC):
|
||||
name: str
|
||||
description: str
|
||||
|
||||
@abstractmethod
|
||||
def get_schema(self) -> dict:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def execute(self, input_data: dict, context: ToolContext) -> list[dict]:
|
||||
...
|
||||
|
||||
@@ -49,24 +49,6 @@ class WebSearchTool(BaseTool):
|
||||
"snippets for the top results."
|
||||
)
|
||||
|
||||
def get_schema(self) -> dict:
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "The search query.",
|
||||
},
|
||||
"num_results": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of results to return (default 5).",
|
||||
"default": 5,
|
||||
},
|
||||
},
|
||||
"required": ["query"],
|
||||
"additionalProperties": False,
|
||||
}
|
||||
|
||||
async def execute(self, input_data: dict, context: ToolContext) -> list[dict]:
|
||||
query: str = input_data["query"]
|
||||
num_results: int = input_data.get("num_results", 5)
|
||||
@@ -163,23 +145,6 @@ class WebFetchTool(BaseTool):
|
||||
"HTML is stripped to plain text. Output capped at ~250 KB."
|
||||
)
|
||||
|
||||
def get_schema(self) -> dict:
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "The URL to fetch.",
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
"description": "Optional prompt/context describing what information to look for.",
|
||||
},
|
||||
},
|
||||
"required": ["url"],
|
||||
"additionalProperties": False,
|
||||
}
|
||||
|
||||
async def execute(self, input_data: dict, context: ToolContext) -> list[dict]:
|
||||
url: str = input_data["url"]
|
||||
prompt: str | None = input_data.get("prompt")
|
||||
|
||||
@@ -22,17 +22,6 @@ from backend.apps.settings.settings import load_settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MODEL_MAP = {
|
||||
"sonnet": "claude-sonnet-4-20250514",
|
||||
"opus": "claude-opus-4-20250514",
|
||||
"haiku": "claude-haiku-4-5-20251001",
|
||||
}
|
||||
|
||||
|
||||
def _resolve_model(short_name: str) -> str:
|
||||
return MODEL_MAP.get(short_name, short_name)
|
||||
|
||||
|
||||
def _get_anthropic_client(api_model: str | None = None):
|
||||
"""Create an AsyncAnthropic client using the API key from app settings.
|
||||
|
||||
@@ -184,15 +173,6 @@ def _load(output_id: str) -> Output:
|
||||
return Output(**json.load(f))
|
||||
|
||||
|
||||
def load_output(output_id: str) -> Output | None:
|
||||
"""Public helper for other modules to resolve an output by ID."""
|
||||
path = os.path.join(DATA_DIR, f"{output_id}.json")
|
||||
if not os.path.exists(path):
|
||||
return None
|
||||
with open(path) as f:
|
||||
return Output(**json.load(f))
|
||||
|
||||
|
||||
def _walk_directory(folder: str) -> dict[str, str]:
|
||||
"""Walk a directory tree and return {relative_path: content} for all text files."""
|
||||
files: dict[str, str] = {}
|
||||
|
||||
@@ -46,7 +46,6 @@ _MAX_INFLIGHT = 16
|
||||
|
||||
_test_sink: Optional[Any] = None
|
||||
_install_id: Optional[str] = None
|
||||
_user_id: Optional[str] = None
|
||||
_inflight = 0
|
||||
_inflight_lock = asyncio.Lock()
|
||||
_drain_lock = asyncio.Lock()
|
||||
@@ -85,9 +84,6 @@ def _get_install_id() -> str:
|
||||
|
||||
|
||||
def _get_user_id() -> Optional[str]:
|
||||
global _user_id
|
||||
if _user_id:
|
||||
return _user_id
|
||||
try:
|
||||
from backend.apps.settings.settings import load_settings
|
||||
s = load_settings()
|
||||
@@ -96,11 +92,6 @@ def _get_user_id() -> Optional[str]:
|
||||
return None
|
||||
|
||||
|
||||
def set_user_id(uid: Optional[str]) -> None:
|
||||
global _user_id
|
||||
_user_id = uid or None
|
||||
|
||||
|
||||
def _is_enabled(kind: str) -> bool:
|
||||
"""Honour user opt-out. Diagnostic always flows (errors block usability);
|
||||
state + session honour the toggle."""
|
||||
|
||||
@@ -75,36 +75,6 @@ def validate_credentials(settings: AppSettings, provider: str = "anthropic") ->
|
||||
return
|
||||
|
||||
|
||||
def get_provider_credentials(settings: AppSettings, provider: str) -> dict[str, str]:
|
||||
"""Return credential dict for a specific provider."""
|
||||
p = provider.lower().strip()
|
||||
validate_credentials(settings, provider)
|
||||
|
||||
if p in ("anthropic", "claude"):
|
||||
if getattr(settings, "connection_mode", "own_key") == "openswarm-pro":
|
||||
return {
|
||||
"auth_token": getattr(settings, "openswarm_bearer_token", "") or "",
|
||||
"base_url": getattr(settings, "openswarm_proxy_url", None) or OPENSWARM_DEFAULT_PROXY_URL,
|
||||
}
|
||||
return {"api_key": settings.anthropic_api_key or ""}
|
||||
|
||||
if p in ("openai", "codex"):
|
||||
return {"api_key": settings.openai_api_key or ""}
|
||||
|
||||
if p in ("gemini", "google", "gemini-cli"):
|
||||
return {"api_key": getattr(settings, "google_api_key", "") or ""}
|
||||
|
||||
if p == "openrouter":
|
||||
return {"api_key": getattr(settings, "openrouter_api_key", "") or ""}
|
||||
|
||||
# Custom provider
|
||||
for cp in getattr(settings, "custom_providers", []):
|
||||
if cp.name.lower() == p:
|
||||
return {"api_key": cp.api_key, "base_url": cp.base_url}
|
||||
|
||||
raise ValueError(f"No credentials for provider: {provider}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Legacy helpers (kept for backward compat during migration)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user