diff --git a/plugins/_context_window/AGENTS.md b/plugins/_context_window/AGENTS.md index 0ef7c027e..ef4f246ad 100644 --- a/plugins/_context_window/AGENTS.md +++ b/plugins/_context_window/AGENTS.md @@ -8,12 +8,14 @@ ## Ownership - `helpers/usage.py` owns per-prompt bucket measurement and reconciliation. +- `helpers/currency.py` owns optional USD exchange-rate lookup and validation. - `extensions/python/` records prompt parts at their source extension points, preserves terminal streamed usage, and captures optional provider usage. - `api/context_window.py` exposes the active chat's token usage and effective - model limit without returning prompt content. + model limit plus display flags and price-currency metadata without returning + prompt content or modifying stored provider usage. - `webui/` and `extensions/webui/` own the Alpine store, indicator, popover, - model-override refresh, and Interface visibility row. + model-override refresh, plugin display settings, and Interface visibility row. ## Local Contracts @@ -33,9 +35,19 @@ - The prompt estimate never guesses provider-specific image token costs or counts embedded image bytes as text. - Provider price, cache hit, and input/output tokens form a flat summary without - diagnostic detail rows. + diagnostic detail rows and share the same regular-weight typography. - Provider rows are exposed only when the provider or transport reports their values; unavailable price and cache data render no row. +- Global `show_breakdown`, `show_price`, `show_cache_hit`, and `show_tokens` + settings default to true and apply on each usage refresh or popover opening. + Hiding the breakdown also hides its empty state; hiding all provider rows + removes their container and divider. Accounting and the summary remain intact. +- `price_currency` defaults to USD; USD never invokes the rate helper or makes + an external request. Fetch Frankfurter's daily reference rate only for a + visible, nonzero provider cost in a selected non-USD currency. Cache valid + rates for 24 hours and failed lookups for five minutes. Only currency codes + leave the server. Validate pair, positive finite rate, and date; on failure, + show the original USD amount with an explicit USD label and explanation. - Streaming main turns request LiteLLM's terminal usage event for every provider through the transport's `stream_options.include_usage` injection, so provider input/output and cache tokens reach the summary. The response @@ -56,7 +68,8 @@ - Keep prompt accounting out of rendered-text heuristics. - Keep provider-reported usage separate from the six estimated context buckets. -- Keep the API response limited to counts needed by the UI. +- Keep the API response limited to usage counts, whitelisted display flags, + and effective price-currency metadata. - Preserve the upward, right-aligned popover beside the model/profile selectors. Its minimum strip footprint is bounded by the available width; shared `x-overflow` positions the same popover when the indicator enters the overflow menu. `data-overflow-label` names the entry; `data-overflow-icon` retains its live percentage ring. ## Verification diff --git a/plugins/_context_window/README.md b/plugins/_context_window/README.md index a043867c1..a1100becc 100644 --- a/plugins/_context_window/README.md +++ b/plugins/_context_window/README.md @@ -8,6 +8,17 @@ Extras, and Free space. Older chats gain the detailed breakdown after their next model turn. Mobile and desktop visibility can be changed under **Settings > Interface**. +Under **Plugins > Context Window > Settings**, show or hide the whole breakdown +block, Price, Cache hit, and Tokens In/Out independently. All four are visible +by default. These global settings apply when the popover next refreshes or opens. + +**Price currency** defaults to USD and uses the provider's reported amount +directly, with no exchange-rate request. Other currencies convert the visible +price using [Frankfurter's daily reference rates](https://frankfurter.dev/). +Rates are cached for 24 hours; no chat content or prices are sent to the rate +service. Hover over a converted amount to see the rate date. If a rate is +unavailable, the price is explicitly shown in USD and retries wait five minutes. + When a model provider reports usage, the popover shows price, cache-hit rate, and input/output tokens. Unreported price and cache data are omitted. The context breakdown does not guess model-specific image token costs. diff --git a/plugins/_context_window/api/context_window.py b/plugins/_context_window/api/context_window.py index ed701be5e..a89e01a45 100644 --- a/plugins/_context_window/api/context_window.py +++ b/plugins/_context_window/api/context_window.py @@ -1,4 +1,8 @@ +import re + from helpers.api import ApiHandler, Input, Output, Request +from helpers import plugins +from plugins._context_window.helpers.currency import get_exchange_rate from plugins._context_window.helpers.usage import ( latest_provider_usage, usage_snapshot, @@ -13,10 +17,28 @@ class ContextWindow(ApiHandler): window = agent.get_data(agent.DATA_NAME_CTX_WINDOW) window = window if isinstance(window, dict) else {} config = get_chat_model_config(agent) + display = plugins.get_plugin_config("_context_window") or {} + provider_usage = latest_provider_usage(agent) + currency = str(display.get("price_currency") or "USD").upper() + if not re.fullmatch(r"[A-Z]{3}", currency): + currency = "USD" + price_currency = {"code": "USD", "rate": 1, "date": ""} + if currency != "USD" and display.get("show_price") is not False and "cost" in provider_usage: + if provider_usage["cost"] == 0: + price_currency = {"code": currency, "rate": None, "date": ""} + else: + price_currency = await get_exchange_rate(currency) or { + "code": "USD", "rate": 1, "date": "", "requested": currency, + } return { "tokens": max(int(window.get("tokens") or 0), 0), "context_window": max(int(config.get("ctx_length") or 0), 0), "usage": usage_snapshot(window.get("usage")), - "provider_usage": latest_provider_usage(agent), + "provider_usage": provider_usage, + "price_currency": price_currency, + "display": { + key: display.get(key, True) is not False + for key in ("show_breakdown", "show_price", "show_cache_hit", "show_tokens") + }, } diff --git a/plugins/_context_window/default_config.yaml b/plugins/_context_window/default_config.yaml new file mode 100644 index 000000000..9c45378e5 --- /dev/null +++ b/plugins/_context_window/default_config.yaml @@ -0,0 +1,5 @@ +show_breakdown: true +show_price: true +show_cache_hit: true +show_tokens: true +price_currency: USD diff --git a/plugins/_context_window/extensions/webui/model-context-strip-end/context-window.html b/plugins/_context_window/extensions/webui/model-context-strip-end/context-window.html index 6f2406d99..f84367892 100644 --- a/plugins/_context_window/extensions/webui/model-context-strip-end/context-window.html +++ b/plugins/_context_window/extensions/webui/model-context-strip-end/context-window.html @@ -57,9 +57,10 @@