[arnav] remove 6 dead HTTP endpoints

Verified each had no production caller (frontend, electron, or
internal backend) — only test references kept them looking alive
to coverage tools. Drops ~140 LOC of route handlers + helpers.
- POST /api/outputs/vibe-code: never wired up; frontend has no
  vibe-code UI. Also drops VibeCodeRequest model and
  VIBE_CODE_SYSTEM_PROMPT.
- GET /api/service/cost-breakdown: frontend Usage page reads
  /usage-summary, which already returns by_model/by_provider.
- GET /api/service/status: zero callers; was placeholder.
- GET /api/service/spool/count: debug-only, no UI surface.
- GET /api/settings/default-system-prompt: frontend defines its
  own DEFAULT_SYSTEM_PROMPT in settingsSlice.ts and never fetches
  the backend constant.
- POST /api/browser/command: sole caller (browser_mcp_server.py
  subprocess) was deleted in 8286cc1; browser_agent.py now calls
  ws_manager.send_browser_command directly in-process.
Tests covering the removed endpoints are dropped along with the
now-unused mock-anthropic helpers in test_api_outputs.py.
This commit is contained in:
Arnav Naval
2026-05-06 17:11:57 -05:00
parent 2bd5478405
commit 5cc9f6f01a
13 changed files with 19 additions and 326 deletions
-20
View File
@@ -153,16 +153,6 @@ async def update_session(session_id: str, body: dict):
await agent_manager.update_session(session_id, **body)
return {"ok": True}
@agents.router.get("/sessions/{session_id}/branches")
async def get_branches(session_id: str):
session = agent_manager.get_session(session_id)
if not session:
raise HTTPException(status_code=404, detail="Session not found")
return {
"branches": {k: v.model_dump(mode="json") for k, v in session.branches.items()},
"active_branch_id": session.active_branch_id,
}
@agents.router.post("/sessions/{session_id}/duplicate")
async def duplicate_session(session_id: str, body: dict = {}):
try:
@@ -319,16 +309,6 @@ async def subscriptions_exchange(body: dict):
raise HTTPException(status_code=500, detail=str(e))
@agents.router.get("/subscriptions/models")
async def subscriptions_models():
"""List all models available through connected subscriptions."""
from backend.apps.nine_router import is_running, get_models
if not is_running():
return {"models": []}
models = await get_models()
return {"models": models}
@agents.router.get("/models")
async def list_models():
"""Return the chat-picker model list grouped by provider.
-7
View File
@@ -202,10 +202,3 @@ class WorkspaceSeedRequest(BaseModel):
return data
class VibeCodeRequest(BaseModel):
prompt: str
current_frontend_code: str = ""
current_backend_code: str = ""
current_schema: str = ""
name: str = ""
description: str = ""
+1 -106
View File
@@ -13,7 +13,7 @@ from jsonschema import validate as schema_validate, ValidationError as SchemaVal
from backend.config.Apps import SubApp
from backend.apps.outputs.models import (
Output, OutputCreate, OutputUpdate, OutputExecute, OutputExecuteResult,
VibeCodeRequest, AutoRunRequest, AutoRunConfig, AutoRunAgentRequest,
AutoRunRequest, AutoRunConfig, AutoRunAgentRequest,
WorkspaceSeedRequest,
)
from backend.apps.outputs.executor import execute_backend_code
@@ -398,111 +398,6 @@ async def delete_output(output_id: str):
return {"ok": True}
VIBE_CODE_SYSTEM_PROMPT = """\
You are an expert at building self-contained HTML/JS/CSS applications that run in an iframe.
The user will describe what they want, and you will generate:
1. **frontend_code**: A complete HTML document. React 18 is available via esm.sh CDN.
- Use: <script type="importmap">{"imports":{"react":"https://esm.sh/react@18","react-dom/client":"https://esm.sh/react-dom@18/client"}}</script>
- Input data is at window.OUTPUT_INPUT (object), backend result at window.OUTPUT_BACKEND_RESULT.
2. **input_schema**: A JSON Schema object defining the structured input.
3. **backend_code** (optional): Python code where input_data is a global dict and result is a global dict to assign to.
4. **name**: A short name for the view.
5. **description**: A one-sentence description.
6. **message**: A brief explanation of what you did/changed.
Return ONLY valid JSON with these keys. No markdown fences, no extra text.\
"""
@outputs.router.post("/vibe-code")
async def vibe_code(body: VibeCodeRequest):
"""Use an LLM to generate or iterate on Output code from a natural language prompt."""
try:
import anthropic
except ImportError:
return {
"message": "anthropic SDK not installed. Install with: pip install anthropic",
"frontend_code": body.current_frontend_code,
"backend_code": body.current_backend_code,
"input_schema": body.current_schema,
}
context_parts = []
if body.current_frontend_code:
context_parts.append(f"Current frontend code:\n```html\n{body.current_frontend_code}\n```")
if body.current_backend_code:
context_parts.append(f"Current backend code:\n```python\n{body.current_backend_code}\n```")
if body.current_schema:
context_parts.append(f"Current input schema:\n```json\n{body.current_schema}\n```")
if body.name:
context_parts.append(f"Current name: {body.name}")
if body.description:
context_parts.append(f"Current description: {body.description}")
user_message = body.prompt
if context_parts:
user_message = "\n\n".join(context_parts) + "\n\nUser request: " + body.prompt
from backend.apps.agents.providers.registry import resolve_aux_model
try:
aux_model, _aux_base = await resolve_aux_model(load_settings(), preferred_tier="sonnet")
except ValueError as e:
return {
"message": f"Error: {str(e)}",
"frontend_code": body.current_frontend_code,
"backend_code": body.current_backend_code,
"input_schema": body.current_schema,
}
client = _get_anthropic_client(aux_model)
try:
resp = await client.messages.create(
model=aux_model,
max_tokens=8000,
system=VIBE_CODE_SYSTEM_PROMPT,
messages=[{"role": "user", "content": user_message}],
)
from backend.apps.agents.agent_manager import _safe_resp_text
raw = _safe_resp_text(resp).strip()
if not raw:
return {
"message": "Aux model returned no content. Please try again.",
"frontend_code": body.current_frontend_code,
"backend_code": body.current_backend_code,
"input_schema": body.current_schema,
}
if raw.startswith("```"):
raw = raw.split("\n", 1)[1] if "\n" in raw else raw[3:]
if raw.endswith("```"):
raw = raw[:-3]
result = json.loads(raw)
pass
return {
"message": result.get("message", "View updated."),
"frontend_code": result.get("frontend_code", body.current_frontend_code),
"backend_code": result.get("backend_code", body.current_backend_code),
"input_schema": result.get("input_schema", body.current_schema),
"name": result.get("name", body.name),
"description": result.get("description", body.description),
}
except json.JSONDecodeError:
return {
"message": "I generated code but couldn't parse the response. Please try again.",
"frontend_code": body.current_frontend_code,
"backend_code": body.current_backend_code,
"input_schema": body.current_schema,
}
except Exception as e:
logger.exception("Vibe code generation failed")
return {
"message": f"Error: {str(e)}",
"frontend_code": body.current_frontend_code,
"backend_code": body.current_backend_code,
"input_schema": body.current_schema,
}
AUTO_RUN_SYSTEM_PROMPT = """\
You generate structured JSON data matching a given schema.
The user provides a prompt describing what data to generate and a JSON Schema.
+1 -33
View File
@@ -3,8 +3,7 @@
Replaces the former analytics SubApp with operationally-named endpoints
and lifecycle management. Responsibilities:
- Usage-summary and cost-breakdown endpoints (user-facing, for the
Settings / Usage page)
- Usage-summary endpoint (user-facing, for the Settings / Usage page)
- Background heartbeat that reports operational state to the cloud
- 9Router auto-start for OpenSwarm Pro users
- Frontend event endpoint (`POST /api/service/event`)
@@ -364,31 +363,6 @@ async def usage_summary():
}
@service.router.get("/cost-breakdown")
async def cost_breakdown(period: str = "7d"):
from backend.apps.nine_router import get_usage_stats, is_running as _9r_running
if not _9r_running():
return {"available": False, "by_model": {}, "by_provider": {}}
stats = await get_usage_stats(period)
if not stats:
return {"available": False, "by_model": {}, "by_provider": {}}
return {
"available": True,
"period": period,
"total_cost": stats.get("totalCost", 0),
"total_requests": stats.get("totalRequests", 0),
"total_prompt_tokens": stats.get("totalPromptTokens", 0),
"total_completion_tokens": stats.get("totalCompletionTokens", 0),
"by_model": stats.get("byModel", {}),
"by_provider": stats.get("byProvider", {}),
}
@service.router.get("/status")
async def service_status():
return {"status": "ok", "enabled": True}
# ---------------------------------------------------------------------------
# Frontend event endpoints
# ---------------------------------------------------------------------------
@@ -422,9 +396,3 @@ async def post_event(body: dict):
"p": body.get("props") or body.get("properties") or {},
})
return {"ok": True}
@service.router.get("/spool/count")
async def spool_count():
from backend.apps.service import buffer
return {"pending": buffer.count(svc._spool_path())}
-5
View File
@@ -200,11 +200,6 @@ async def update_settings(body: AppSettings):
return {"ok": True, "settings": body.model_dump()}
@settings.router.get("/default-system-prompt")
async def get_default_system_prompt():
return {"default_system_prompt": DEFAULT_SYSTEM_PROMPT}
@settings.router.post("/reset-system-prompt")
async def reset_system_prompt():
current = load_settings()
-19
View File
@@ -1,6 +1,5 @@
import logging
import os
from uuid import uuid4
logger = logging.getLogger(__name__)
@@ -273,24 +272,6 @@ async def websocket_dashboard(websocket: WebSocket):
ws_manager.disconnect_global(websocket)
@app.post("/api/browser/command")
async def browser_command(request: Request):
"""HTTP endpoint called by the browser MCP server subprocess.
Proxies commands to the frontend via WebSocket and waits for results."""
body = await request.json()
action = body.get("action", "")
browser_id = body.get("browser_id", "")
tab_id = body.get("tab_id", "")
params = body.get("params", {})
if not action or not browser_id:
return JSONResponse({"error": "action and browser_id are required"}, status_code=400)
request_id = uuid4().hex
result = await ws_manager.send_browser_command(request_id, action, browser_id, params, tab_id=tab_id)
return JSONResponse(result)
@app.get("/api/subscriptions/pending/{state}")
async def subscriptions_pending(state: str):
"""Return pending OAuth data for a state param. Called by 9Router's callback page."""
-15
View File
@@ -8,7 +8,6 @@ Tests:
succeeds; DELETE removes it.
- POST /sessions/{id}/edit_message validates input.
- PATCH /sessions/{id} updates allowed fields (name, system_prompt).
- GET /sessions/{id}/branches returns the default 'main' branch.
- GET /history returns paginated results.
"""
@@ -127,20 +126,6 @@ def test_patch_session_updates_name(client, stub_agent_loop):
assert fetched["model"] == "sonnet" # not changed by the PATCH
def test_get_branches_returns_main(client, stub_agent_loop):
launch = client.post(
"/api/agents/launch",
json={"name": "B", "model": "sonnet", "mode": "agent"},
)
session_id = launch.json()["session_id"]
resp = client.get(f"/api/agents/sessions/{session_id}/branches")
assert resp.status_code == 200
body = resp.json()
assert body["active_branch_id"] == "main"
assert "main" in body["branches"]
def test_history_endpoint_returns_paginated_shape(client):
"""`/history` is the search-and-resume endpoint. Even with no
saved sessions it should return the paginated wrapper without
@@ -15,7 +15,6 @@ chatter from these tests. Two fixtures here:
Coverage:
- GET /subscriptions/status — both down + up shapes.
- GET /subscriptions/models — both down + up shapes.
- POST /subscriptions/connect — missing provider → 400; 503 when
9Router is unavailable; happy path
round-trips the device_code flow.
@@ -126,25 +125,6 @@ def test_status_when_9router_up(client, nine_router_up):
assert isinstance(body["models"], list)
# ---------------------------------------------------------------------------
# GET /subscriptions/models
# ---------------------------------------------------------------------------
def test_models_when_9router_down(client, nine_router_down):
resp = client.get("/api/agents/subscriptions/models")
assert resp.status_code == 200
assert resp.json() == {"models": []}
def test_models_when_9router_up(client, nine_router_up):
resp = client.get("/api/agents/subscriptions/models")
assert resp.status_code == 200
body = resp.json()
assert isinstance(body["models"], list)
assert len(body["models"]) == 1
# ---------------------------------------------------------------------------
# POST /subscriptions/connect
# ---------------------------------------------------------------------------
+8 -2
View File
@@ -57,8 +57,14 @@ def test_create_get_update_delete_dashboard(client):
assert gone.status_code == 404
def test_dashboard_layout_round_trip(client):
"""PUT layout with a sticky note and assert it survives a re-fetch."""
def test_dashboard_layout_field_round_trip(client):
"""PUT the `layout` field on a dashboard with a sticky note and
assert it survives a re-fetch via /api/dashboards/{id}.
Named `_field_` (not `_dashboard_layout_`) to make clear this exercises
the `layout` attribute of the live `Dashboard` model — not the legacy
`backend.apps.dashboard_layout` package, which was removed.
"""
create = client.post("/api/dashboards/create", json={"name": "L"})
dashboard_id = create.json()["id"]
+1 -78
View File
@@ -13,7 +13,7 @@ Layout mirrors `outputs.py`:
- File serve (workspace + saved output, with token rewrite + _d
payload injection)
- Backend execute
- vibe-code + auto-run (LLM-mocked)
- auto-run (LLM-mocked)
- auto-run-agent (stub_agent_loop + AgentConfig spy)
- Auth control mirroring test_api_agents.test_protected_route_requires_auth
"""
@@ -24,7 +24,6 @@ import base64
import json
import os
import sys
from unittest.mock import AsyncMock, MagicMock
import pytest
@@ -52,43 +51,6 @@ def _create_output(client, **overrides) -> dict:
return resp.json()["output"]
def _make_mock_anthropic(text_response: str) -> MagicMock:
"""Build a mock Anthropic client.
The route does `await client.messages.create(...)` then reads
`resp.content[0].text`. Mirror that shape.
"""
fake_resp = MagicMock()
fake_resp.content = [MagicMock(text=text_response)]
client_mock = MagicMock()
client_mock.messages.create = AsyncMock(return_value=fake_resp)
return client_mock
def _patch_anthropic(monkeypatch, text_response: str) -> MagicMock:
"""Hook `_get_anthropic_client` in outputs.py to return a mock that
responds with `text_response` on every messages.create call.
Returns the inner mock client so tests can assert call args.
"""
from backend.apps.outputs import outputs as outputs_mod
mock_client = _make_mock_anthropic(text_response)
monkeypatch.setattr(outputs_mod, "_get_anthropic_client", lambda: mock_client)
return mock_client
def _patch_aux_model(monkeypatch, model_id: str = "claude-haiku-fake") -> None:
"""Stub `resolve_aux_model` on the registry so vibe-code/auto-run never
try to inspect the user's actual model connections."""
from backend.apps.agents.providers import registry
async def _fake(_settings, preferred_tier="haiku"):
return (model_id, None)
monkeypatch.setattr(registry, "resolve_aux_model", _fake)
# ---------------------------------------------------------------------------
# CRUD + legacy migration
# ---------------------------------------------------------------------------
@@ -554,45 +516,6 @@ def test_execute_no_backend_code_returns_none_result(client):
assert body["error"] is None
# ---------------------------------------------------------------------------
# /vibe-code (Anthropic mocked)
# ---------------------------------------------------------------------------
def test_vibe_code_resolver_raises_value_error_returns_graceful(client, monkeypatch):
"""resolve_aux_model raises ValueError when no model is connected.
The route catches and returns a graceful error response without
touching the Anthropic client."""
from backend.apps.agents.providers import registry
async def _raise(_settings, preferred_tier="haiku"):
raise ValueError("no aux model")
monkeypatch.setattr(registry, "resolve_aux_model", _raise)
resp = client.post(
"/api/outputs/vibe-code",
json={"prompt": "x", "current_frontend_code": "<keep/>"},
)
body = resp.json()
assert "no aux model" in body["message"]
assert body["frontend_code"] == "<keep/>"
def test_vibe_code_anthropic_import_error(client, monkeypatch):
"""Forcing `import anthropic` to fail returns the install hint without
touching the model registry at all."""
monkeypatch.setitem(sys.modules, "anthropic", None)
resp = client.post(
"/api/outputs/vibe-code",
json={"prompt": "x", "current_frontend_code": "<keep/>"},
)
body = resp.json()
assert "anthropic SDK not installed" in body["message"]
assert body["frontend_code"] == "<keep/>"
# ---------------------------------------------------------------------------
# /auto-run (Anthropic mocked)
# ---------------------------------------------------------------------------
+1 -9
View File
@@ -8,7 +8,7 @@ which we exercise here.
Tests:
- GET /api/settings returns defaults including the canned system prompt
- PUT /api/settings round-trips a value and survives `load_settings()` reload
- default-system-prompt + reset-system-prompt
- reset-system-prompt
- browse-directories on a tmp dir
"""
@@ -46,14 +46,6 @@ def test_put_round_trips_value(client):
assert load_settings().theme == "light"
def test_default_system_prompt_returns_constant(client):
from backend.apps.settings.models import DEFAULT_SYSTEM_PROMPT
resp = client.get("/api/settings/default-system-prompt")
assert resp.status_code == 200
assert resp.json()["default_system_prompt"] == DEFAULT_SYSTEM_PROMPT
def test_reset_system_prompt(client):
"""Mutate then reset; the prompt must snap back to DEFAULT_SYSTEM_PROMPT."""
from backend.apps.settings.models import DEFAULT_SYSTEM_PROMPT
+7 -3
View File
@@ -200,7 +200,10 @@ def test_reconcile_idempotent():
# ---------------------------------------------------------------------------
def test_dashboard_layout_notes_round_trip():
def test_dashboard_layout_model_notes_round_trip():
"""`DashboardLayout` (Pydantic) with notes round-trips through model_dump
and model_validate. Named `_model_` to disambiguate from the long-removed
`backend.apps.dashboard_layout` package."""
from backend.apps.dashboards.models import DashboardLayout, NotePosition
n = NotePosition(note_id="n1", x=100, y=200, content="todo: ship",
@@ -215,8 +218,9 @@ def test_dashboard_layout_notes_round_trip():
assert rehydrated.notes["n1"].color == "yellow"
def test_dashboard_layout_legacy_no_notes():
"""Older dashboard JSON without 'notes' must still load cleanly."""
def test_dashboard_layout_model_legacy_no_notes():
"""Older dashboard JSON without 'notes' must still load cleanly into the
live `DashboardLayout` Pydantic model from `backend.apps.dashboards.models`."""
from backend.apps.dashboards.models import DashboardLayout
legacy = {
-9
View File
@@ -342,12 +342,3 @@ async def test_endpoint_event_missing_surface(sink):
assert res["ok"] is False
@pytest.mark.asyncio
async def test_endpoint_spool_count(tmp_path):
from backend.apps.service import client as svc, buffer
from backend.apps.service.service import spool_count
spool = str(tmp_path / "spool.db")
with patch.object(svc, "_spool_path", lambda: spool):
buffer.enqueue(spool, "s:/x", {}, now=time.time())
result = await spool_count()
assert result == {"pending": 1}