From 5cc9f6f01afae7e4fb4cdee53380e8905f1d745f Mon Sep 17 00:00:00 2001 From: Arnav Naval Date: Wed, 6 May 2026 17:11:57 -0500 Subject: [PATCH] =?UTF-8?q?[arnav]=20remove=206=20dead=20HTTP=20endpoints?= =?UTF-8?q?=20Verified=20each=20had=20no=20production=20caller=20(frontend?= =?UTF-8?q?,=20electron,=20or=20internal=20backend)=20=E2=80=94=20only=20t?= =?UTF-8?q?est=20references=20kept=20them=20looking=20alive=20to=20coverag?= =?UTF-8?q?e=20tools.=20Drops=20~140=20LOC=20of=20route=20handlers=20+=20h?= =?UTF-8?q?elpers.=20-=20POST=20/api/outputs/vibe-code:=20never=20wired=20?= =?UTF-8?q?up;=20frontend=20has=20no=20=20=20vibe-code=20UI.=20Also=20drop?= =?UTF-8?q?s=20VibeCodeRequest=20model=20and=20=20=20VIBE=5FCODE=5FSYSTEM?= =?UTF-8?q?=5FPROMPT.=20-=20GET=20/api/service/cost-breakdown:=20frontend?= =?UTF-8?q?=20Usage=20page=20reads=20=20=20/usage-summary,=20which=20alrea?= =?UTF-8?q?dy=20returns=20by=5Fmodel/by=5Fprovider.=20-=20GET=20/api/servi?= =?UTF-8?q?ce/status:=20zero=20callers;=20was=20placeholder.=20-=20GET=20/?= =?UTF-8?q?api/service/spool/count:=20debug-only,=20no=20UI=20surface.=20-?= =?UTF-8?q?=20GET=20/api/settings/default-system-prompt:=20frontend=20defi?= =?UTF-8?q?nes=20its=20=20=20own=20DEFAULT=5FSYSTEM=5FPROMPT=20in=20settin?= =?UTF-8?q?gsSlice.ts=20and=20never=20fetches=20=20=20the=20backend=20cons?= =?UTF-8?q?tant.=20-=20POST=20/api/browser/command:=20sole=20caller=20(bro?= =?UTF-8?q?wser=5Fmcp=5Fserver.py=20=20=20subprocess)=20was=20deleted=20in?= =?UTF-8?q?=208286cc1;=20browser=5Fagent.py=20now=20calls=20=20=20ws=5Fman?= =?UTF-8?q?ager.send=5Fbrowser=5Fcommand=20directly=20in-process.=20Tests?= =?UTF-8?q?=20covering=20the=20removed=20endpoints=20are=20dropped=20along?= =?UTF-8?q?=20with=20the=20now-unused=20mock-anthropic=20helpers=20in=20te?= =?UTF-8?q?st=5Fapi=5Foutputs.py.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/apps/agents/agents.py | 20 ---- backend/apps/outputs/models.py | 7 -- backend/apps/outputs/outputs.py | 107 +----------------- backend/apps/service/service.py | 34 +----- backend/apps/settings/settings.py | 5 - backend/main.py | 19 ---- backend/tests/test_api_agents.py | 15 --- .../tests/test_api_agents_subscriptions.py | 20 ---- backend/tests/test_api_dashboards.py | 10 +- backend/tests/test_api_outputs.py | 79 +------------ backend/tests/test_api_settings.py | 10 +- backend/tests/test_phase1_stress.py | 10 +- backend/tests/test_service.py | 9 -- 13 files changed, 19 insertions(+), 326 deletions(-) diff --git a/backend/apps/agents/agents.py b/backend/apps/agents/agents.py index 42ce5aa9..de5ec34d 100644 --- a/backend/apps/agents/agents.py +++ b/backend/apps/agents/agents.py @@ -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. diff --git a/backend/apps/outputs/models.py b/backend/apps/outputs/models.py index 515ef187..25c7a385 100644 --- a/backend/apps/outputs/models.py +++ b/backend/apps/outputs/models.py @@ -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 = "" diff --git a/backend/apps/outputs/outputs.py b/backend/apps/outputs/outputs.py index ab01e5be..24876f77 100644 --- a/backend/apps/outputs/outputs.py +++ b/backend/apps/outputs/outputs.py @@ -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: - - 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. diff --git a/backend/apps/service/service.py b/backend/apps/service/service.py index 1ef21098..01e297ed 100644 --- a/backend/apps/service/service.py +++ b/backend/apps/service/service.py @@ -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())} diff --git a/backend/apps/settings/settings.py b/backend/apps/settings/settings.py index ce98b218..86ac01e5 100644 --- a/backend/apps/settings/settings.py +++ b/backend/apps/settings/settings.py @@ -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() diff --git a/backend/main.py b/backend/main.py index 7d5b5186..7532374e 100644 --- a/backend/main.py +++ b/backend/main.py @@ -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.""" diff --git a/backend/tests/test_api_agents.py b/backend/tests/test_api_agents.py index 87e04751..c606a26f 100644 --- a/backend/tests/test_api_agents.py +++ b/backend/tests/test_api_agents.py @@ -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 diff --git a/backend/tests/test_api_agents_subscriptions.py b/backend/tests/test_api_agents_subscriptions.py index cf42e037..929d74d2 100644 --- a/backend/tests/test_api_agents_subscriptions.py +++ b/backend/tests/test_api_agents_subscriptions.py @@ -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 # --------------------------------------------------------------------------- diff --git a/backend/tests/test_api_dashboards.py b/backend/tests/test_api_dashboards.py index 07888783..74c0d1cd 100644 --- a/backend/tests/test_api_dashboards.py +++ b/backend/tests/test_api_dashboards.py @@ -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"] diff --git a/backend/tests/test_api_outputs.py b/backend/tests/test_api_outputs.py index 0f866dad..3eefce82 100644 --- a/backend/tests/test_api_outputs.py +++ b/backend/tests/test_api_outputs.py @@ -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": ""}, - ) - body = resp.json() - assert "no aux model" in body["message"] - assert body["frontend_code"] == "" - - -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": ""}, - ) - body = resp.json() - assert "anthropic SDK not installed" in body["message"] - assert body["frontend_code"] == "" - - # --------------------------------------------------------------------------- # /auto-run (Anthropic mocked) # --------------------------------------------------------------------------- diff --git a/backend/tests/test_api_settings.py b/backend/tests/test_api_settings.py index c9f39f6b..890fbdee 100644 --- a/backend/tests/test_api_settings.py +++ b/backend/tests/test_api_settings.py @@ -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 diff --git a/backend/tests/test_phase1_stress.py b/backend/tests/test_phase1_stress.py index 26c468eb..efbb341c 100644 --- a/backend/tests/test_phase1_stress.py +++ b/backend/tests/test_phase1_stress.py @@ -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 = { diff --git a/backend/tests/test_service.py b/backend/tests/test_service.py index 9b98bce8..acc0f2c7 100644 --- a/backend/tests/test_service.py +++ b/backend/tests/test_service.py @@ -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}