Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ package and installed command are both `opentab`.
| `conversation.py` | Shared conversation input validation, bounded text windows, anchors and snapshot-bound cursors |
| `conversation_search.py` | Explicit private SQLite/FTS5 text index, source-bound root replacement and grouped lexical candidates; service owns visibility and live verification |
| `models.py` | Workflow, qualified session identity and summary records |
| `tools.py` | Numeric per-call projection of recorded usage rows; ordered repeated calls and proportional attribution |
| `stores/` | Harness readers, combined views, portable summaries and warm caches |
| `remote_content.py` | Opt-in keyed SSH traces, snapshot/live identity validation, bounded transport and cancelable jobs |
| `tui/app.py` | Application state, accounting projections, keyboard/mouse navigation |
Expand Down
11 changes: 11 additions & 0 deletions docs/keys.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,17 @@ Expansion is temporary and is released when you leave the turn. Recorded tool
errors are labeled explicitly. Sources that do not support content have no turn
detail; real content is also unavailable in demo mode.

On **Tools**, `j`/`k` select a tool or server/namespace ranking and `Enter` or
double-click opens its contribution, per-call averages, token composition and exact
model attribution. The chronological call list uses the same keys; `Enter` opens
the owning prompt's turn list with the relevant turn selected. Another `Enter`
opens the existing trace reader where supported. `Esc` returns through each level,
restoring the originating tool call and ranking. `g`/`G` select first/last; page keys
and the mouse wheel scroll the details. `$` updates attribution without changing
the selected tool. The rankings remain useful for aggregate-only sources, which
explicitly report that individual calls are unavailable. Tools CSV export remains
the session's aggregate tool/model attribution, not the selected call ledger.

The overview chart shows **total cost per prompt**, matching the prompt table.
Inside a prompt, the charts show **cost per turn and context growth** for that
prompt only, with the same session-wide turn numbers as its table. Each peak is
Expand Down
16 changes: 16 additions & 0 deletions docs/tui.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,22 @@ missing or uniform per-call rates fall back to area. Small entries fold into
pairs while the browser measures its responsive container. Neither chart needs
another store query.

The Tools explorer adds selectable tool and namespace rankings beneath this chart.
Opening one shows contribution, averages, a token-composition band, token categories,
per-model attribution and individual calls recovered from the existing timeline.
`tools.tool_calls_from_turns` preserves original turn indices and repeated calls;
it does not read raw content. Aggregate and recovered-ledger coverage are compared
separately for calls, tokens and cost. A turn timestamp is not tool start time,
and attributed model output is not the returned result's size.

The App retains one numeric Tools projection and the renderer one layout, keyed by
source-row snapshots, pricing revision/mode and layout inputs. Cached layouts restore
their paint metadata, while cursors and viewport scrolling stay independent. A call
opens the owning prompt's Turns list with that turn selected; trace content still
requires a further explicit action. Back unwinds trace, owning Turns, Tools detail,
then rankings. Manual tab/scope changes discard the cross-tab return context; reload
clears the projection and drill rather than reusing call ordinals from an old snapshot.

## Coordinates and Encoding

The outer app frame is a viewport boundary. `draw()` paints it in screen
Expand Down
26 changes: 26 additions & 0 deletions docs/web.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,32 @@ an older browser tab before requesting more prompts.

## Served live

### Tools explorer

The live **Tools** tab pairs the spend treemap with tool and server/namespace
rankings. Select a row or treemap tile to inspect contribution, per-call averages,
a token-composition band, exact token categories, model attribution and a
chronological individual-call ledger. Tables sort on their headers; repeated
calls remain separate, even within one turn. The ledger shows its aggregate
coverage instead of assuming both accounting surfaces contain identical records.

Select a call to open its owning prompt in **Turns**, with the exact turn
highlighted and focused. `Esc`, browser Back or the visible Back button returns
to that call in Tools; another Back returns to the ranking. Focused ranking/call
rows support `j`/`k`, arrows and Enter, and `Tab` follows native browser focus.
`$` changes the cost snapshot without losing the selected tool or call. Manual
tab/scope changes and reload invalidate this transient navigation. Detail text
wraps on small screens; tables scroll horizontally within their panels.

Cost and token figures are **model usage attributed across a turn's tool calls**,
not execution fees or tool-result size. Times are owning-turn timestamps, not tool
start/end times. Status and duration are not inferred. Aggregate-only sources retain
the rankings and breakdowns without inventing a call ledger. No arguments, tool
results or raw content keys enter the live extras or static report; opening a call
does not add a raw web trace reader. Static HTML still omits Tools entirely.

### Running the server

`opentab web` serves the browser on `http://localhost:8321` (`--port` changes the
port) and opens it in your default browser. `opentab web --headless` serves without
launching a browser. Stop either with `Ctrl-C`.
Expand Down
45 changes: 45 additions & 0 deletions src/opentab/tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Shared projections for per-tool-call accounting."""

from collections.abc import Iterable

from opentab.util import tool_names, tool_namespace

_ARITHMETIC_FIELDS = (
"cost",
"tokens_total",
"input",
"output",
"reasoning",
"cache_read",
"cache_write",
"cache_write_1h",
)


def tool_calls_from_turns(turns: Iterable[dict]) -> list[dict]:
"""Flatten turns into chronological calls with evenly attributed usage."""
calls = []
for turn_index, turn in enumerate(turns):
tools = tool_names(turn.get("tools"))
if not tools:
continue
share_count = len(tools)
for call_index, tool in enumerate(tools):
call = {
"index": len(calls),
"turn_index": turn_index,
"call_index": call_index,
"tool": tool,
"namespace": tool_namespace(tool),
"time": turn.get("time"),
"agent": turn.get("agent"),
"depth": turn.get("depth"),
"model_name": turn.get("model_name"),
"effort": turn.get("effort"),
"prompt_id": turn.get("prompt_id"),
"prompt_title": turn.get("prompt_title"),
}
for field in _ARITHMETIC_FIELDS:
call[field] = (turn.get(field) or 0) / share_count
calls.append(call)
return calls
Loading
Loading