## Implementation Plan: Display Deep Research Costs for Admin Users ### Overview The investigation confirms a complete gap chain across six source files and one test file. The gateway already returns cost headers (`X-Gateway-Cost-USD` via `parse_usage_headers()`). The fix requires plumbing cost data through every layer: LLM client → planner → orchestration → worker report → router → template. The reference patterns exist in `correction.py:34-54,82` and `bot/llm.py:97-110`. ### Step-by-Step Changes #### 1. LLM Client – `devplacepy/services/deepsearch/llm.py` (lines 38-44) **Current:** Returns `(data, data.get("usage"), elapsed_ms)` – headers discarded. **Change:** - Import `parse_usage_headers` from `devplacepy.services.openai_gateway.usage`. - After `response = await client.post(...)`, extract cost from headers: ```python cost_info = parse_usage_headers(response.headers) or {} ``` - Append `cost_usd` to the returned usage dict or return it as a separate third element. Because the callers at `orchestrate.py` consume the second element as a dict, the cleanest approach is to add `"cost_usd": cost_info.get("cost_usd", 0.0)` into the usage dict returned. **Result:** The function now returns `(data, {**body_usage, "cost_usd": ...}, elapsed_ms)`. #### 2. Planner – `devplacepy/services/jobs/deepsearch/enhance.py` (lines 86-92) **Current:** Only `data = response.json()` – headers discarded. **Change:** - Import `parse_usage_headers`. - Extract cost info from `response.headers` similarly. - Pass the cost info back to the caller. The caller (`orchestrate.py`) already captures the planner’s usage via the `usage` dict in the planner’s return. Modify the return tuple to include cost, or add cost into the usage dict (consistent with step 1). **Recommendation:** Use a small helper `_extract_cost(response)` that calls `parse_usage_headers` and returns a dict with `cost_usd`. Then merge that dict into the usage returned. #### 3. Orchestration – `devplacepy/services/jobs/deepsearch/orchestrate.py` **Files affected:** - `Orchestration` dataclass (lines 35-42) – add `total_cost_usd: float = 0.0`. - `_complete()` (lines 153-169) – after gathering usage from LLM client and planner, add their cost_usd values and store in the `Orchestration` instance. - `_agent_done()` (lines 289-300) – include `cost_usd` in the progress frames emitted (optional but good for real‑time tracking). - The call to `_complete()` in the main loop should also propagate cost. #### 4. Worker Report – `devplacepy/services/jobs/deepsearch/worker.py` (lines 215-231) **Current:** Report dict has 14 keys, none for cost. **Change:** - After the orchestration finishes and produces an `outcome`, extract `outcome.orchestration.total_cost_usd` and add `"cost_usd": total_cost_usd` to the report dict. #### 5. Router – `devplacepy/routers/tools/deepsearch.py` **Changes:** - In `_session_context()` (lines 225-257), after loading the report from the database, get `cost_usd = report.get("cost_usd", 0.0)`. - Add `cost_usd` to the context dict that gets passed to the template. Only include it for admin users: ```python if viewer_is_admin: context["cost_usd"] = cost_usd ``` - If desired, also add it to the job payload in `_job_payload()` (line 55-78) for real‑time updates, but the session context is the primary target. #### 6. Template – `devplacepy/templates/tools/deepsearch_session.html` (lines 13-19) **Current:** Five metrics rendered; no cost metric. **Change:** - Add a new span inside the `
` block, guarded by an `{% if viewer_is_admin %}` check: ```html {% if viewer_is_admin %} ${{ "%.6f"|format(cost_usd) }} cost {% endif %} ``` - Ensure the decimal formatting matches the pattern used elsewhere (check other admin cost displays in templates for consistency). #### 7. Tests – `tests/api/tools/deepsearch/session.py` **Current:** Seed report fixture has no cost fields. **Change:** - In `_seed_done_session()` (lines 30-42), add `"cost_usd": 0.123456` to the report dict. - In the admin user test (lines 85-99), assert that the rendered HTML contains `0.123456` and the label `cost`. - Ensure the non‑admin test does not contain the cost metric. ### Definition of Done - All six source files and the test file have been modified per the steps above. - The changes compile without syntax errors: `python -c "import devplacepy.services.deepsearch.llm; import devplacepy.services.jobs.deepsearch.enhance; import devplacepy.services.jobs.deepsearch.orchestrate; import devplacepy.services.jobs.deepsearch.worker; import devplacepy.routers.tools.deepsearch; print('OK')"` returns 0. - The lint command passes: `pip install -q ruff && ruff check .` exits with 0. - The unit test command passes: `pip install -e '.[dev]' -q && make test-unit` exits with 0. (Note: if the environment restricts pip install, use `pip install --break-system-packages -e '.[dev]' -q` or activate a virtual environment first.) - A grep for `cost_usd` across `devplacepy/services/deepsearch/`, `devplacepy/services/jobs/deepsearch/`, `devplacepy/routers/tools/deepsearch.py`, and `devplacepy/templates/tools/deepsearch_session.html` shows the new occurrences expected after changes. - The session template renders cost only for admin users: a manual test (or existing e2e test) confirms the admin view includes the cost metric and the non‑admin view does not.