Repository navigation
Releases: SmetDenis/openwebui-token-usage-display
Releases · SmetDenis/openwebui-token-usage-display
Release list
v2.8.0
- Checked against Open WebUI 0.12.0: no change needed. Usage merging, the outlet body (apart from the new
finish_reasonbelow), the model listing and connection settings the context probe reads, status/citation persistence and the frontend render gates are as in 0.11.x. The plugin loads and runs in 0.12.0's rewritten function loader. - Truncation marker (
✂️ truncated). Open WebUI 0.12.0 tells filters why the model stopped. When it stopped because it hit its output-token limit, the line now says so next to the output count:⬇︎ 4,096 · ✂️ truncated · Σ ..., so a cut-off answer is not mistaken for a complete one. Details:- New admin valve
show_finish_reason(on by default) and metric keyfinish, placed right afteroutputin the default order so it survives the one-line clamp of the status line. If you saved a customdisplay_order, the new key is appended at the end, as with any unlisted metric - addfinishwhere you want it. Icons:✂️(emoji) /✂︎(simple); withicon_style: offit readstruncated. - Shown only for the
lengthstop reason. A normal stop, a turn that ended on a tool call (paused for approval, or stopped at the tool-call limit) and a provider content filter show nothing. - Needs Open WebUI 0.12.0+ and a Chat Completions (OpenAI-compatible) or Ollama model, or a pipe function that passes such a response through. Not shown for the Responses API: Open WebUI passes no stop reason for its streams and a made-up
stopfor its non-streaming replies (as it does for pipes returning plain text). Older Open WebUI versions pass none at all. On a tool turn it is the stop reason of the last request. debug_mode: new top-levelfinish_reasonfield (nullwhen Open WebUI sent none).
- New admin valve
What's Changed
- feat: Add truncation marker for OWUI 0.12.0 and release every merge by @SmetDenis in #29
Full Changelog: 2.7.0...v2.8.0
2.7.0
What's Changed
- deps: Bump pydantic from 2.13.4 to 2.13.5 by @dependabot[bot] in #20
- fix(context): use last round, not cross-round sum, for window used by @jtoland11 in #27
- fix(context): Scope the last-round window fix and cut v2.7.0 by @SmetDenis in #28
- ci: Bump astral-sh/setup-uv from 10.1.0 to 10.2.0 by @dependabot[bot] in #22
- deps: bump ruff from 0.16.7 to 0.16.10 by @dependabot[bot] in #25
- deps: bump mypy from 2.3.1 to 2.4.0 by @dependabot[bot] in #24
New Contributors
- @jtoland11 made their first contribution in #27
Full Changelog: v2.6.0...2.7.0
v2.6.0
- Re-released on 2026-09-15. The first
2.6.0and2.6.1builds (published 2026-09-14) were replaced by this single release, which also contains the context-window work below. If you installed either of them, paste this file again - the version number did not change. - Running chat token total (
🧮). A new metric shows how many tokens the chat has consumed so far: the sum of every response'sΣon the current branch - the token twin of the💰Σcost total, and the only chat-wide number for models that report no cost (Ollama, llama.cpp, other local or free models). Because the model re-reads the whole history on every turn, it counts processed tokens and grows much faster than the context window - it is not the size of the chat (that is📐). Details:- New admin valve
show_cumulative_tokens(on by default) and metric keytokens_total, placed right aftertotalin the default order. If you saved a customdisplay_order, the new key is appended at the end, as with any unlisted metric - addtokens_totalwhere you want it. Icons:🧮(emoji) /ΣΣ(simple);compact_numbersapplies to it. - Hidden when it equals the message
Σ(the first turn), like💰Σ. Marked≈when the current turn'sΣis a tiktoken estimate. - Past responses are summed from their stored
usagewith the same cache-aware total asΣ; responses without usage are skipped, not re-estimated. - Temporary chats (and API requests without a chat id): Open WebUI hands the filter their history without per-message usage, so both
🧮and💰Σcollapse to the current message there (and are hidden). Verified against Open WebUI v0.11.0 and v0.11.3. debug_mode: newcumulative_tokens_debugblock (total,estimated,messages_counted,messages_skipped_no_usage); thetokensblock carriescumulative/cumulative_est.
- New admin valve
- Fixed: a non-finite token count (
NaN/Infinity) inusageno longer breaks the stats line. Python's JSON parser accepts these values, and the plugin converted them withint()on a path that is not exception-guarded, so such a value in the current response aborted the line - and, with the new chat total walking the history, one bad stored response would have aborted it for every later response in the chat. Non-finite numbers are now treated as "not a number" at the single numeric intake (_num), so the affected field is simply omitted. The same class of value in thecontext_size_mapvalve (Infinity) is now skipped per entry instead of disabling the whole map. - Fixed: a local model showed its trained maximum instead of the server's real context window (#6: Qwen3.6 on
llama-server --ctx-size 16384showed7.2k/131.1k (6%)instead of7.2k/16.4k (44%); #14: a llama.cpp model started by llama-swap with--fitshowed no real context size). The size came from the built-in table (qwen3= 131072), and the opt-in llama.cpp / llama-swap probe was ranked below that table, so it never ran for any model family the table knows - settingllamacpp_urlcould not fix it either. Behind llama-swap three more things stood in the way: llama.cpp's--fit(on by default) picks the window at load time, so the--ctx-sizethe probe looked for is not in the launch command; llama-swap refuses a plain/propswithout?model=; and the probe URL needed the API key typed into a valve. The context size is now resolved in this order:context_size_override->num_ctx-> yourcontext_size_map-> live connection window -> backend listing -> matched probe -> models.dev -> built-in table -> unmatched probe. Changes:- New automatic source: the running window asked through the model's own Open WebUI connection. For a model listed by llama-swap (
owned_by: llama-swap) or a llama.cpp router (owned_by: llamacppwith astatusblock), the plugin reuses that connection's base URL, API key andprefix_idfrom Open WebUI - no valve, no key copied. Cloud connections are never contacted: they are not detected. Debugcontext_debug.source: connection_probe,matched_keynames the backend and model id. A hit is cached forcontext_probe_ttl; a miss is not. - New automatic source, no configuration and no network: the size the serving backend lists for the model in its
/v1/modelsrow, which Open WebUI already passes to the plugin. llama.cpp lists the running window asmeta.n_ctx(builds since May 2026), llama-swap does the same whencapabilities.contextis set in its config, and vLLM listsmax_model_len. A live connection answer beats it, because llama-swap'scapabilities.contextis a config number that may not be the window--fitpicked. Debugcontext_debug.source: backend,matched_keynames the field. Workspace/"agent" models read it from their base model's listing entry. It is not cached, so a relisted model applies at once. - A probe never loads a model. A model-dispatched request makes llama-swap start the model if it is not running, evicting whatever is. llama-swap is therefore asked
/runningfirst and/props?model=only for a model in stateready; a llama.cpp router is asked withautoload=false, which refuses instead of loading. The API key is sent only as a bearer token, and only when the connection itself uses bearer auth; session / OAuth / Entra ID connections are probed without it. Not detected: a connection restricted to hand-typed model ids (Open WebUI keeps no listing row for those) - use the valves orcontext_size_mapthere. - The valve probe (
llamacpp_url/llama_swap_url) now outranks models.dev and the built-in table when its row matches the called model (debugmatched_keynames the row). llama-swap can run several models at once, and the probe used to take the first/runningrow regardless of which model Open WebUI called; it now picks the matching row, asks/props?model=for it when it isready, and falls back to the--ctx-sizeof its command (e.g. a vLLM upstream without/props). With several running models and no match it reports nothing and falls through tollamacpp_url, if set. llama.cpp is asked/v1/modelsfirst (id or alias ->meta.n_ctx), then/propsfor older builds (matched by the model alias or GGUF file name). A backend's only running model under a different id (an alias) is still used, but only after the tables, so a cloud model is never given a local server's window. - Strict probe id matching: a row matches when its id equals the called id or ends it after a connection prefix (
prefix.id) or a path (org/id) - no substring match, so a localqwennever claims a cloudqwen3-max. debug_mode: newmodel.backend_contextblock with the listing'sn_ctx(running),n_ctx_train(trained) andmax_model_len, andmodel.live_backend([kind, listed id]ornull) showing whether the model was recognized as a live-probe candidate.outletnow also accepts__request__(needed for the workspace-model and connection lookups); its dunder parameters became keyword-only, which is how Open WebUI passes them.- Verified against Open WebUI v0.11.3 (connection lookup
routers/openai.py:get_openai_connection, 0.10.0+;app.state.configon 0.9.x), llama.cppmaster(2026-09-15), llama-swapmain(2026-09-13) and vLLMmain.
- New automatic source: the running window asked through the model's own Open WebUI connection. For a model listed by llama-swap (
- Internal (no behavior change): stricter linting (ruff with every rule enabled), the stats-line renderers take a single
_Statsobject instead of six positional arguments, and the most branchy resolvers were split into helpers. Development tooling moved fromrequirements-dev.txtto uv with a committeduv.lock, so CI runs exactly the locally locked ruff/mypy/pytest versions (see CONTRIBUTING.md). - Idea credit for the chat token total: the fork ArtyCooL/openwebui-token-usage-display.
Full Changelog: v2.5.2...v2.6.0
v2.5.2
What's Changed
- deps: update ruff requirement from >=0.5 to >=0.15.22 by @dependabot[bot] in #4
- deps: update pytest requirement from >=8.0 to >=9.1.1 by @dependabot[bot] in #2
- deps: update pytest-cov requirement from >=5.0 to >=7.1.0 by @dependabot[bot] in #3
- deps: update mypy requirement from >=1.10 to >=2.3.0 by @dependabot[bot] in #1
- chore(release): Refresh plugin metadata and cut v2.5.2 by @SmetDenis in #5
Full Changelog: v2.5.1...v2.5.2
v2.5.1
context_size_mapnow takes precedence over the live models.dev fetch. It was previously merged into the built-in table, which is consulted after the models.dev lookup — so withfetch_context_from_modelsdevenabled, an automatic remote match silently overrode your explicit manual entry. It's now resolved in its own tier, right afteroverride/num_ctxand before models.dev and the static table (sourceuser_mapin the debug payload), matching howprice_mapalready beats models.dev for cost. Consequences: a map entry now wins outright on any substring match (even against a longer static/models.dev key — the same semantics asprice_map); a falsy size (0) is skipped and falls through; parsing is per-entry tolerant (one bad value drops only that entry). It's returned uncached, so edits take effect immediately.
v2.5.0
- Workspace/custom models now infer context size and cost from their base model. Context-window and cost matching previously keyed on the top-level model id, which for a custom model / "agent" is an arbitrary label (e.g.
research) that matches no context/price table — so context % and estimated/native cost silently went blank unless you mapped the agent id by hand. Both now resolve the model'sbase_model_id(the real underlying LLM — the same id the model-name metric shows and the one Open WebUI itself calls) and fall back to the top-level id only when there is no base. A singlecontext_size_map/price_mapentry keyed on the base model now covers every agent built on it; direct base models are unaffected (nobase_model_id→ their own id is used). Thedebug_modepayload'smodel.resolved_idand provider guess now reflect the id actually used for matching.
v2.4.0
debug_mode: providercost_details. When the provider reports a cost breakdown next to the total (OpenRouter'susage.cost_details—upstream_inference_cost,cache_discount), it's now surfaced under thecost_debugblock. The displayed cost still uses the authoritative top-levelcost; this is diagnostics only (the provider-vs-upstream split and any cache discount).debug_mode: web-search usage hint. A newweb_searchblock flags whether the response carried server-side web-search citations (url_citation→ OWUIsources), with a citation count and the domains. It's a heads-up that a provider-side search/tool surcharge may not be fully reflected inusage.cost(and BYOK engines bill outside the gateway entirely) — cross-check the Activity page. Detection only; the visible stats line is byte-identical.- Docs: added a "Does the cost include tool / function / MCP calls?" section (Open-WebUI-orchestrated tool turns are summed across rounds into the shown cost; only a provider's own server-side tool surcharge outside
usage.costis invisible), and strengthened theestimate-mode caveat — a token×price estimate can drift several-fold under a gateway's routing / tier / cache pricing, soauto(native, authoritative) is preferred.
v2.3.0
- Configurable metric order. New admin
display_ordervalve sets the order of the stats line via a comma-separated list of metric keys (input, output, total, reasoning, cached, audio, context, time, tps, cost, cost_total, model, source). The field is pre-filled with the full default order for discoverability — reorder or trim it (empty also means default, so it stays fully backward-compatible). Ordering only — the existingshow_*toggles still decide visibility (removing a key does not hide it, it moves to the end), and enabled-but-unlisted metrics are appended in the default order. Unknown / misspelled keys are ignored and surfaced under a newdisplay_orderblock in thedebug_modepayload. - Configurable line appearance. New admin valves:
separator(string between items, default·);icon_style—emoji(default),simple(monochrome unicode, e.g.↑ ↓ Σ, context severity○ ◐ ●), oroff(bare values, no icons); andcompact_numbers(abbreviate the six token counters ask/M, matching the context-window style). All default to the previous behavior, so the line is byte-identical out of the box. - Cost display threshold. New
cost_min_displayvalve hides message/cumulative cost below a USD threshold (default0.0shows everything, including$0.00). - Metric order is now admin-only — the earlier per-user override was dropped as unnecessary;
UserValves.enabled(hide the whole line for yourself) is unchanged.
v2.2.0
- Fixed Anthropic cache double-counting behind a proxy (LiteLLM, etc.). Some proxies fold Anthropic's cache tokens into
prompt_tokenswhile still reporting the top-levelcache_read_input_tokens/cache_creation_input_tokens; the plugin previously treated those as additional to input, inflating both the total token count and the estimated cost (roughly doubling the cached portion). Cache accounting is now unified around an uncachedfresh_input: cache is detected as a subset of input (OpenAI / DeepSeek / Gemini / Anthropic-via-proxy) or on top of it (native Anthropic), so totals and estimated cost match regardless of how the same call is routed. Provider-reported (auto) cost was never affected. - Richer
debug_modepayload for troubleshooting model/cost issues (still copyable via the "Token Usage & Cost Display - Debug info" source + server log; the visible stats line is unchanged). Four new blocks:model— sanitized, safe-to-share identity of the selected model:id/name/base_model_id,owned_by,connection_type,provider,preset/is_pipe/has_url_idx, a best-effortprovider_guess, thefunction_callingmode and other OWUI params. Uses a strict whitelist, so no prompt text, user/session ids, access grants, base URLs or API keys ever appear. Generation params (temperature/top_p/…) are marked unavailable on purpose — Open WebUI strips them before the filter runs.cost_debug— why the cost came out the way it did: thecost_mode, whether a native provider cost was found (and from which key), the resolved price source (price_map/models.dev/static/none) with the exact matched key (nullis the tell-tale for an id that matches no price entry), the applied per-1M rates, and a full per-component breakdown (billable input, cached, cache-write, output → USD each).context_debug— how the context-window size was resolved (override/num_ctx/user_map/models.dev/static_table/probe) and which key matched, plus size/used/percent.valves— a snapshot of the current admin configuration to reproduce issues from a single payload; the two optional local-backend URLs (llamacpp_url,llama_swap_url) are masked.
v2.1.0
- Message & chat cost. New
cost_modevalve:auto(default) shows the provider's own reported cost only (OpenRouterusage.cost, LiteLLM, ...) and never fetches or guesses;estimateadditionally approximates cost from models.dev prices (always marked≈), with correct cached/reasoning and Anthropic cache-read/write pricing;offdisables cost entirely. Shows the per-message cost (💰) and a running chat total (💰Σ) in USD. - Optional live models.dev price fetch (
api.json, exact per-provider prices, cached ~24h, on by default in estimate mode) with a built-in offline price table as fallback and a manualprice_mapoverride that always wins. debug_modeis now copyable in-chat: the stats line just gets a small(debug)marker, and the full diagnostic payload is attached as a "Token Usage & Cost Display - Debug info" source whose modal renders it as a JSON block with a Copy button and persists with the message (still mirrored to the server log). The previous status line was clamped to one line and couldn't be selected.- Minimum lowered to Open WebUI 0.9.0 (was 0.10.0+): the data contracts the plugin relies on - per-message
usagein the outlet (since 0.9.0) and normalized usage (since 0.8.0) - predate 0.10, so it runs on 0.9.x with graceful degradation; only provider-reported cost inautomode needs 0.10.0+ (useestimateon older versions). debug_modenow writes the full payload to the server/container logs (copyable, never truncated) and shows only a short summary in the chat status line - the previous status-only output was clamped to one line and couldn't be selected.