## 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 `