diff --git a/backend/apps/agents/agent_manager.py b/backend/apps/agents/agent_manager.py
index 4e716638..7170e530 100644
--- a/backend/apps/agents/agent_manager.py
+++ b/backend/apps/agents/agent_manager.py
@@ -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 (
- "\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"
- )
-
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 "\n" + "\n".join(lines) + "\n"
- # ------------------------------------------------------------------
- # 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 = [""]
- 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("")
- 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).
diff --git a/backend/apps/agents/browser_agent.py b/backend/apps/agents/browser_agent.py
index 97591d39..7bd8fce5 100644
--- a/backend/apps/agents/browser_agent.py
+++ b/backend/apps/agents/browser_agent.py
@@ -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
#
diff --git a/backend/apps/agents/mcp_preflight.py b/backend/apps/agents/mcp_preflight.py
index 3492fee3..9a4cd0c6 100644
--- a/backend/apps/agents/mcp_preflight.py
+++ b/backend/apps/agents/mcp_preflight.py
@@ -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
diff --git a/backend/apps/agents/providers/registry.py b/backend/apps/agents/providers/registry.py
index 5b007845..89bb3fb1 100644
--- a/backend/apps/agents/providers/registry.py
+++ b/backend/apps/agents/providers/registry.py
@@ -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
# ---------------------------------------------------------------------------
diff --git a/backend/apps/agents/tools/base.py b/backend/apps/agents/tools/base.py
index cfca6418..1cf00fb0 100644
--- a/backend/apps/agents/tools/base.py
+++ b/backend/apps/agents/tools/base.py
@@ -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]:
...
diff --git a/backend/apps/agents/tools/web.py b/backend/apps/agents/tools/web.py
index 0609e3b2..e575775c 100644
--- a/backend/apps/agents/tools/web.py
+++ b/backend/apps/agents/tools/web.py
@@ -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")
diff --git a/backend/apps/outputs/outputs.py b/backend/apps/outputs/outputs.py
index 1c8ef4f9..0a63fd7b 100644
--- a/backend/apps/outputs/outputs.py
+++ b/backend/apps/outputs/outputs.py
@@ -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] = {}
diff --git a/backend/apps/service/client.py b/backend/apps/service/client.py
index d9ee2720..92bfc9ab 100644
--- a/backend/apps/service/client.py
+++ b/backend/apps/service/client.py
@@ -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."""
diff --git a/backend/apps/settings/credentials.py b/backend/apps/settings/credentials.py
index 9883d134..5a321b95 100644
--- a/backend/apps/settings/credentials.py
+++ b/backend/apps/settings/credentials.py
@@ -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)
# ---------------------------------------------------------------------------
diff --git a/backend/main.py b/backend/main.py
index 38b1097e..7a62b590 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -207,13 +207,6 @@ async def websocket_session(websocket: WebSocket, session_id: str):
"message": payload.get("message"),
"updated_input": payload.get("updated_input"),
})
- elif event == "agent:edit_message":
- from backend.apps.agents.agent_manager import agent_manager
- await agent_manager.edit_message(
- session_id,
- payload.get("message_id", ""),
- payload.get("content", ""),
- )
elif event == "agent:stop":
from backend.apps.agents.agent_manager import agent_manager
await agent_manager.stop_agent(session_id)
diff --git a/backend/tests/test_agent_manager_unit.py b/backend/tests/test_agent_manager_unit.py
index c3e63b87..241a23e8 100644
--- a/backend/tests/test_agent_manager_unit.py
+++ b/backend/tests/test_agent_manager_unit.py
@@ -12,8 +12,7 @@ Test groups:
- pure instance methods (`_resolve_mode`, `_compose_system_prompt`,
`_resolve_context_paths`, `_build_dir_tree`, `_resolve_forced_tools`,
`_resolve_attached_skills`, `_get_branch_messages`,
- `_build_history_prefix`, `_approx_tokens`,
- `_summarize_message_block`, `_truncate_large_tool_result`,
+ `_build_history_prefix`, `_truncate_large_tool_result`,
`_build_search_text`, `_maybe_compact`)
- lifecycle (launch/update/edit/switch_branch/duplicate/close/
delete/resume/stop, history, browser-agent children, approval,
@@ -564,7 +563,7 @@ def test_get_branch_messages_walks_fork_lineage():
# ---------------------------------------------------------------------------
-# _build_history_prefix / _approx_tokens / _summarize_message_block
+# _build_history_prefix
# ---------------------------------------------------------------------------
@@ -587,59 +586,6 @@ def test_build_history_prefix_empty_returns_empty():
assert AgentManager._build_history_prefix([]) == ""
-@pytest.mark.parametrize("text,expected", [
- ("", 1),
- ("a" * 4, 1),
- ("a" * 16, 4),
- ("a" * 100, 25),
-])
-def test_approx_tokens_chars_over_four(text, expected):
- assert AgentManager._approx_tokens(text) == expected
-
-
-def test_approx_tokens_handles_none():
- assert AgentManager._approx_tokens(None) == 1
-
-
-def test_summarize_message_block_empty_returns_empty():
- assert AgentManager._summarize_message_block([]) == ""
-
-
-def test_summarize_message_block_extracts_initial_task_and_counts():
- msgs = [
- Message(role="user", content="please do the thing"),
- Message(role="tool_call", content={"tool": "Read", "input": {}}),
- Message(role="tool_call", content={"tool": "Bash", "input": {}}),
- Message(role="tool_call", content={"tool": "Read", "input": {}}),
- Message(role="tool_result", content="ok"),
- Message(role="assistant", content="done"),
- ]
- out = AgentManager._summarize_message_block(msgs)
- assert "" in out
- assert "please do the thing" in out
- # Tool counts
- assert "Read×2" in out
- assert "Bash×1" in out
- assert "Tool calls so far (3 total)" in out
- assert "Tool results received: 1" in out
- assert "Last assistant message:" in out
- assert "done" in out
-
-
-def test_summarize_message_block_assistant_list_content():
- """Assistant messages with list content (Anthropic block shape)
- should still surface their text."""
- msgs = [
- Message(role="user", content="task"),
- Message(role="assistant", content=[
- {"type": "text", "text": "the answer"},
- {"type": "tool_use", "id": "t1"},
- ]),
- ]
- out = AgentManager._summarize_message_block(msgs)
- assert "the answer" in out
-
-
# ---------------------------------------------------------------------------
# _truncate_large_tool_result
# ---------------------------------------------------------------------------
diff --git a/backend/tests/test_browser_agent_unit.py b/backend/tests/test_browser_agent_unit.py
index 2f0e7ef2..82fd7946 100644
--- a/backend/tests/test_browser_agent_unit.py
+++ b/backend/tests/test_browser_agent_unit.py
@@ -11,8 +11,7 @@ in small helpers that can be exercised in isolation:
conversations.
- `_format_tool_result` — translate `ws_manager` result dicts into
Anthropic content blocks.
- - `clear_browser_history` / `execute_browser_tool` — small async + state
- helpers.
+ - `execute_browser_tool` — small async + state helper.
The integration-level tests for `run_browser_agent` /
`run_browser_agents` / `_create_browser_card` live in the sister file
@@ -40,7 +39,6 @@ from backend.apps.agents.browser_agent import (
_summarize_messages,
_trim_history_by_turns,
_validate_message_pairing,
- clear_browser_history,
execute_browser_tool,
)
@@ -59,28 +57,6 @@ def _clear_browser_history_module_state():
ba._browser_history.clear()
-# ---------------------------------------------------------------------------
-# clear_browser_history
-# ---------------------------------------------------------------------------
-
-
-def test_clear_browser_history_drops_only_target_entry():
- ba._browser_history["b-1"] = [{"role": "user", "content": "hi"}]
- ba._browser_history["b-2"] = [{"role": "user", "content": "yo"}]
-
- clear_browser_history("b-1")
-
- assert "b-1" not in ba._browser_history
- assert "b-2" in ba._browser_history
-
-
-def test_clear_browser_history_missing_id_is_noop():
- """Calling clear on an unknown id must not raise (it's used in
- cleanup paths where the cache may already be empty)."""
- clear_browser_history("never-existed")
- assert ba._browser_history == {}
-
-
# ---------------------------------------------------------------------------
# _hash_tool_call
# ---------------------------------------------------------------------------
diff --git a/backend/tests/test_outputs_unit.py b/backend/tests/test_outputs_unit.py
index 753c7e48..827d722c 100644
--- a/backend/tests/test_outputs_unit.py
+++ b/backend/tests/test_outputs_unit.py
@@ -4,12 +4,12 @@ These exercise pure logic and pydantic models without booting FastAPI.
The integration surface (routes) lives in `test_api_outputs.py`.
Covers:
- - outputs.py helpers: _resolve_model, _validate_against_schema,
+ - outputs.py helpers: _validate_against_schema,
_build_data_injection, _inject_data_into_html,
_inject_token_into_relative_urls (every branch in
_ABSOLUTE_URL_PREFIXES + token-already-present + fragment),
_decode_data_param, _walk_directory.
- - On-disk store helpers: _save / _load / load_output / _load_all.
+ - On-disk store helpers: _save / _load / _load_all.
- Models: legacy `frontend_code` / `backend_code` / `schema_json`
migration into `files`, plus the property accessors.
- executor.execute_backend_code: happy path, stdout capture,
@@ -35,12 +35,9 @@ from backend.apps.outputs.outputs import (
_inject_token_into_relative_urls,
_load,
_load_all,
- _resolve_model,
_save,
_validate_against_schema,
_walk_directory,
- load_output,
- MODEL_MAP,
)
from backend.apps.outputs.models import (
AutoRunConfig,
@@ -55,22 +52,6 @@ from backend.apps.outputs.executor import (
)
-# ---------------------------------------------------------------------------
-# _resolve_model
-# ---------------------------------------------------------------------------
-
-
-def test_resolve_model_known_short_name():
- assert _resolve_model("sonnet") == MODEL_MAP["sonnet"]
- assert _resolve_model("opus") == MODEL_MAP["opus"]
- assert _resolve_model("haiku") == MODEL_MAP["haiku"]
-
-
-def test_resolve_model_unknown_passthrough():
- assert _resolve_model("claude-3-5-haiku") == "claude-3-5-haiku"
- assert _resolve_model("") == ""
-
-
# ---------------------------------------------------------------------------
# _validate_against_schema
# ---------------------------------------------------------------------------
@@ -272,7 +253,7 @@ def test_walk_directory_skips_unreadable(tmp_path):
# ---------------------------------------------------------------------------
-# _load_all / _save / _load / load_output
+# _load_all / _save / _load
# ---------------------------------------------------------------------------
@@ -291,18 +272,6 @@ def test_load_missing_raises_404(tmp_data_dirs):
assert exc.value.status_code == 404
-def test_load_output_returns_none_for_missing(tmp_data_dirs):
- assert load_output("does-not-exist") is None
-
-
-def test_load_output_returns_resolved(tmp_data_dirs):
- out = Output(name="x")
- _save(out)
- fetched = load_output(out.id)
- assert fetched is not None
- assert fetched.name == "x"
-
-
def test_load_all_picks_up_saved(tmp_data_dirs):
a = Output(name="a")
b = Output(name="b")
diff --git a/backend/tests/test_service.py b/backend/tests/test_service.py
index acc0f2c7..2d5f57ba 100644
--- a/backend/tests/test_service.py
+++ b/backend/tests/test_service.py
@@ -45,7 +45,6 @@ def patch_settings(tmp_path):
def fresh_client(tmp_path):
import backend.apps.service.client as client
client._install_id = None
- client._user_id = None
client._test_sink = None
spool = tmp_path / "spool.db"
with patch.object(client, "_spool_path", lambda: str(spool)):
@@ -78,35 +77,42 @@ def test_sync_carries_install_id(sink):
assert body["client_state"]["install_id"] == "test-install-abc"
-def test_sync_carries_user_id_when_set(sink):
- from backend.apps.service.client import sync, set_user_id
- set_user_id("alice@example.com")
- sync({})
+def test_sync_carries_user_id_when_set_in_settings(tmp_path, sink):
+ """User id is read from settings.user_email at envelope-build time."""
+ sf = tmp_path / "settings_with_email.json"
+ sf.write_text(json.dumps({
+ "installation_id": "test-install-abc",
+ "analytics_opt_in": True,
+ "user_email": "alice@example.com",
+ }))
+ import backend.apps.settings.settings as settings_mod
+ with patch.object(settings_mod, "SETTINGS_FILE", str(sf)):
+ from backend.apps.service.client import sync
+ sync({})
_, body = sink[0]
assert body["client_state"]["user_id"] == "alice@example.com"
-def test_sync_no_user_id_when_not_set(sink):
+def test_sync_no_user_id_when_email_not_set(sink):
+ """No user_email in settings → user_id absent from envelope."""
from backend.apps.service.client import sync
sync({})
_, body = sink[0]
assert "user_id" not in body["client_state"]
-def test_sync_user_id_cleared_with_none(sink):
- from backend.apps.service.client import sync, set_user_id
- set_user_id("alice")
- set_user_id(None)
- sync({})
- _, body = sink[0]
- assert "user_id" not in body["client_state"]
-
-
-def test_sync_user_id_cleared_with_empty(sink):
- from backend.apps.service.client import sync, set_user_id
- set_user_id("alice")
- set_user_id("")
- sync({})
+def test_sync_no_user_id_when_email_empty(tmp_path, sink):
+ """Empty-string user_email is treated like missing."""
+ sf = tmp_path / "settings_empty_email.json"
+ sf.write_text(json.dumps({
+ "installation_id": "test-install-abc",
+ "analytics_opt_in": True,
+ "user_email": "",
+ }))
+ import backend.apps.settings.settings as settings_mod
+ with patch.object(settings_mod, "SETTINGS_FILE", str(sf)):
+ from backend.apps.service.client import sync
+ sync({})
_, body = sink[0]
assert "user_id" not in body["client_state"]
diff --git a/backend/tests/test_v2_invariants.py b/backend/tests/test_v2_invariants.py
index 304ff1d0..5ea2af25 100644
--- a/backend/tests/test_v2_invariants.py
+++ b/backend/tests/test_v2_invariants.py
@@ -926,23 +926,6 @@ def test_find_builtin_model_returns_dict_for_known():
assert sonnet.get("api") == "anthropic"
-# ===========================================================================
-# Group R — context window
-# ===========================================================================
-
-
-def test_get_context_window_known_model():
- from backend.apps.agents.providers.registry import get_context_window
- cw = get_context_window("Anthropic", "sonnet")
- assert cw >= 200_000
-
-
-def test_get_context_window_unknown_returns_default():
- from backend.apps.agents.providers.registry import get_context_window
- cw = get_context_window("Unknown", "fake-model")
- assert cw == 128_000
-
-
# ===========================================================================
# Group S — calculate_cost regression tests
# ===========================================================================
@@ -1051,18 +1034,16 @@ def test_web_tools_classes_inherit_basetool():
assert issubclass(WebFetchTool, BaseTool)
-def test_web_search_tool_has_name_and_schema():
+def test_web_search_tool_has_name():
from backend.apps.agents.tools.web import WebSearchTool
tool = WebSearchTool()
assert tool.name
- assert isinstance(tool.get_schema(), dict)
-def test_web_fetch_tool_has_name_and_schema():
+def test_web_fetch_tool_has_name():
from backend.apps.agents.tools.web import WebFetchTool
tool = WebFetchTool()
assert tool.name
- assert isinstance(tool.get_schema(), dict)
# ===========================================================================