[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:
Arnav Naval
2026-05-06 18:49:30 -05:00
co-authored by Cursor
parent 9100e91652
commit 64dfd50d12
15 changed files with 34 additions and 565 deletions
-160
View File
@@ -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).
-11
View File
@@ -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
#
-29
View File
@@ -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 310 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
-104
View File
@@ -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
# ---------------------------------------------------------------------------
-4
View File
@@ -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]:
...
-35
View File
@@ -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")
-20
View File
@@ -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] = {}
-9
View File
@@ -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."""
-30
View File
@@ -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)
# ---------------------------------------------------------------------------
-7
View File
@@ -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)
+2 -56
View File
@@ -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 "<compacted_history>" 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
# ---------------------------------------------------------------------------
+1 -25
View File
@@ -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
# ---------------------------------------------------------------------------
+3 -34
View File
@@ -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")
+26 -20
View File
@@ -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"]
+2 -21
View File
@@ -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)
# ===========================================================================