diff --git a/.agent-maintainer/dashboard-source-baseline.json b/.agent-maintainer/dashboard-source-baseline.json index 5629614b..01fcb7c3 100644 --- a/.agent-maintainer/dashboard-source-baseline.json +++ b/.agent-maintainer/dashboard-source-baseline.json @@ -13,12 +13,12 @@ "frontend/dashboard/src/api/client.ts": { "group": "source", "nonblank": 765, - "physical": 826 + "physical": 825 }, "frontend/dashboard/src/api/types.ts": { "group": "source", - "nonblank": 424, - "physical": 450 + "nonblank": 422, + "physical": 448 }, "frontend/dashboard/src/app/zh-Hans/advancedPages.ts": { "group": "source", diff --git a/CHANGELOG.md b/CHANGELOG.md index c34db2a3..d481cbf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ ## Unreleased +- Ingest aggregate `response.completed` telemetry from local + `codex-completions*.jsonl` exporter files and conservatively reconcile exact + Fast/Standard service-tier evidence to canonical calls without retaining raw + response bodies or arbitrary OTLP attributes. +- Show exact service tier separately from the existing Fast proxy in Calls, + details, and CSV exports; older or unmatched history remains Unknown. +- Apply documented model-family Fast multipliers to confirmed Codex credit + estimates with source URL/date/confidence and local overrides while leaving + standard-credit allowance calibration unchanged. +- Cache Standard, Batch, Flex, and Priority API pricing together, select the + observed tier per call, and expose Standard/Priority cost scenarios plus an + explicit local billing basis without applying ChatGPT Fast multipliers to API + USD estimates. +- Preserve exact response tiers in dashboard labels and CSV contracts, clear + OTel staging on confirmed database reset, and make incremental source cursors + descriptor-safe across concurrent file rotation. Verify a bounded content + anchor before resuming so same-inode rewrites cannot silently skip telemetry. + ## 0.20.0 - 2026-07-16 - Reinvent the Threads tab around inline row expansion, progressively loading and virtualizing every call in the selected thread while preserving explicit investigator actions, deep links, responsive layouts, retry recovery, and aggregate-first privacy boundaries. diff --git a/docs/architecture.md b/docs/architecture.md index ca44e1fd..9537fc92 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -17,6 +17,15 @@ Shareable outputs remain aggregate-first and must omit indexed/raw content unles ## Boundaries - `parser.py` converts local JSONL events into aggregate `UsageEvent` records. It also attaches metadata-only call-origin categories, diagnostic facts from `diagnostic_facts.py`, archived-session flags, conservative thread keys, source cursors, and parser diagnostics. +- `parser/otel.py`, `store/otel_ingest.py`, and `store/otel_reconciliation.py` + provide aggregate-only completion enrichment from local + `~/.codex-usage-tracker/otel/codex-completions*.jsonl` files. The importer + retains only approved completion identifiers, token counters, model/effort, + application version, and service-tier metadata; response bodies and arbitrary + OTLP attributes are never persisted. Reconciliation requires one canonical + call group with the same conversation id and all four token counters. Model + and effort narrow a match when both sides provide them, but timestamps are not + identity evidence. - `store/content_index.py` owns normalized local content-index population and cleanup. It may persist bounded local snippets, tool-call metadata, command roots/labels, file path hashes/basenames, parser adapter metadata, source provenance, parse warnings, and FTS5 search rows. Investigation/report builders may persist bounded run summaries in `investigation_runs`. These surfaces must not feed raw/indexed content into default CSV, dashboard HTML, support bundle, or aggregate report payloads. - `call_origin.py` owns the pure call-origin classifier and migrated-row fallback. It must not open source JSONL files; source-log reads belong in refresh/indexing or explicit context loading. - `schema.py` owns persisted SQLite columns and migrations. Add columns or tables there before changing refresh, export, or MCP behavior. @@ -62,6 +71,10 @@ Shareable outputs remain aggregate-first and must omit indexed/raw content unles totals through `canonical_usage_events`. New physical-row investigation surfaces must be bounded, explicitly labeled, and tested against an original/copy/new-call fixture. +15. Treat service tier as exact only when a completion explicitly reports it, or + when Codex `0.143.0` or newer omits it under the documented Standard + protocol. Older or unmatched calls remain Unknown. Latency and reasoning + effort are useful throughput signals, not proof of Fast usage. Normal aggregate responses use a 64-entry, 256-KiB-per-entry process cache. Allowance history and diagnostics use a separate four-entry, 8-MiB-per-entry diff --git a/docs/cli-json-schemas.md b/docs/cli-json-schemas.md index 75ec4f8a..3768f34d 100644 --- a/docs/cli-json-schemas.md +++ b/docs/cli-json-schemas.md @@ -1466,4 +1466,38 @@ Most setup and file-writing commands accept `--json` and return a schema-specifi - `init-thresholds --json`, `init-projects --json` - `support-bundle --json` +Refresh and rebuild payloads add bounded OTel counters under +`parser_diagnostics`: `otel_files_scanned`, `otel_imported`, +`otel_duplicates`, `otel_matched`, `otel_pending`, `otel_ambiguous`, and +`otel_conflicts` (zero-valued counters may be omitted from the immediate JSON +payload). Stored refresh metadata keeps the same keys with zero defaults so +localhost status is stable. These surfaces never expose OTel source paths, +semantic fingerprints, response bodies, arbitrary attributes, or staging ids. + +Aggregate call rows may add nullable `service_tier`, `fast`, +`service_tier_source`, and `service_tier_confidence` fields. `service_tier` +preserves the exact normalized response value such as `priority`, `default`, +`standard`, `flex`, or `batch`; `fast` is the derived speed classification. + +Cost-annotated rows may add `standard_cost_usd`, `priority_cost_usd`, +`pricing_service_tier`, `billing_basis`, and +`cost_semantics="api_token_estimate"`. `estimated_cost_usd` remains the +API-token-equivalent estimate selected from the observed tier when pricing-v2 +contains that table. `billing_basis` is `unknown`, `chatgpt_credits`, or +`api_tokens` and describes applicability rather than changing the observed tier. + +Credit-annotated rows may also add `standard_usage_credits`, +`fast_usage_credits`, `usage_credit_multiplier`, +`usage_credit_multiplier_source`, `usage_credit_multiplier_source_url`, +`usage_credit_multiplier_fetched_at`, and +`usage_credit_multiplier_confidence`. These are ChatGPT-equivalent credit +scenarios and are never used as generic multipliers for API USD. `fast: null` +means Unknown; clients must not replace it with the separate throughput proxy. +CSV export includes the exact tier, both scenario families, multiplier +provenance, and the separately named proxy candidate. + +`rebuild-index` retains aggregate OTel staging for reconciliation. Confirmed +`reset-db --yes` clears both `otel_completion_events` and +`otel_completion_sources` along with the other tracker-owned rows. + `context` already returns JSON because it is an explicit on-demand context request. Treat `codex-usage-tracker-context-v1` output as sensitive local context even though it is redacted and size-limited by default. `max_entries=0` requests all matching entries and `max_chars=0` removes the character cap for that explicit request. Tool output and compacted replacement history are omitted unless explicitly requested. Compaction entries may include metadata such as `replacement_history_available`, `replacement_entry_count`, and `replacement_history_included`; replacement text appears only when `include_compaction_history` is true for that local request. Evidence responses include `action_timing`, derived from timestamps in the same selected-turn source scan, plus per-entry `action_timing` fields such as `since_turn_start_ms`, `since_previous_entry_ms`, and `reported_duration_ms` when available. MCP returns `codex-usage-tracker-context-disabled-v1` when raw context loading has not been explicitly enabled with `CODEX_USAGE_TRACKER_ALLOW_RAW_CONTEXT=1`. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 59fde34e..69944ffe 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -117,6 +117,11 @@ Use `Calls` view when you want to inspect individual model calls. - Time values are shown in your browser's local date/time format while sorting and time filtering still use the logged timestamp. - Calls include `Duration`, derived from turn start for a new turn or the previous same-turn call end for continuations, and `Prev gap`, the elapsed time since the previous call in the resolved thread. - Calls view token columns separate total tokens, cached input, uncached input, and output so the accounting can be scanned without expanding a row. +- The `Service Tier` column preserves the exact response value with distinct + labels for Priority/Fast, Fast, Default/Standard, Standard, Flex, Batch, and + other bounded explicit tiers. It shows `Unknown` when completion telemetry is + absent. The detail view keeps the older throughput heuristic labeled as a + separate Fast proxy candidate rather than historical proof. - Source pucks are call-level estimates derived from local event metadata. `User` means the token-count segment included a user message, `Codex` means it followed tool output, compaction, or agent-continuation metadata, and `Unknown` means the source event metadata was unavailable or ambiguous. - Click a column header like `Time`, `Thread`, `Tokens`, `Cost`, or `Cache` to sort. Use the sort menu for `Highest Codex credits`. Click the same header again to reverse the direction. - Hover a row to scan a compact aggregate preview in `Call Details`; click a Calls row to open the dedicated call investigator. @@ -136,6 +141,16 @@ Useful interpretation notes: - `Cached input` and `Uncached input` are split so cache behavior is visible without storing transcript text. - A cost with `*` means the pricing row is marked as a best-guess estimate. - Codex credits are estimated from aggregate input, cached-input, and output token counters. Direct model matches use the bundled OpenAI Codex rate-card snapshot; inferred labels are marked estimated, and local credit-rate overrides are marked user-provided. +- Confirmed Fast rows apply the source-stamped model-family multiplier to + ChatGPT-equivalent Codex credits. API-equivalent USD estimates instead select + the exact API tier table; the cost detail identifies whether the configured + billing basis is API tokens, ChatGPT credits, or Unknown without claiming + actual spend when the basis is unknown. +- CSV exports include exact tier/provenance, the separate Fast proxy, + Standard/Priority API cost scenarios, Standard/Fast credit scenarios, + `billing_basis`, and credit multiplier source URL/date/confidence. Missing + exact tier evidence remains blank/Unknown instead of being reconstructed from + duration or reasoning effort. - `Usage observed` is not a live account query. It uses the latest local Codex `token_count.rate_limits` snapshot when present; otherwise configure `~/.codex-usage-tracker/allowance.json` with values copied from Codex Settings > Usage, the Codex Usage dashboard, or `/status` when you want current remaining allowance context. ## Threads View diff --git a/docs/database-schema.md b/docs/database-schema.md index 890d465b..3f84a784 100644 --- a/docs/database-schema.md +++ b/docs/database-schema.md @@ -32,6 +32,39 @@ identity, parser coverage, replacement, and refresh revision. Changing or removi a source causes its owned physical rows/materializations to be replaced in a transaction rather than accumulated blindly. +## OTel Service-Tier Enrichment (Schemas 30–31) + +Schema 30 adds nullable `service_tier`, `fast`, `service_tier_source`, and +`service_tier_confidence` columns to `usage_events`. Null means the tracker does +not have exact tier evidence; it is not interpreted as Standard. + +`otel_completion_sources` records device/inode identity, size, the last complete +byte and line cursor, a bounded SHA-256 resume anchor, and an update timestamp +for each local `codex-completions*.jsonl` file. The default directory is the +`otel` sibling of the selected database (`~/.codex-usage-tracker/otel` for the +default database), so alternate databases do not ingest another tracker +instance's telemetry. +`otel_completion_events` stores one semantic +fingerprint plus aggregate matching fields, the exact normalized response tier, +derived Fast classification, tier provenance, a +bounded match status (`pending`, `matched`, `ambiguous`, `conflict`, or +`invalid`), and the matched aggregate record id when available. + +Append refresh resumes after the last complete JSONL line, retries a partial +trailing line, and restarts a cursor after rotation, truncation, or a mismatched +resume anchor. Schema 31 adds the anchor; a legacy cursor without one is safely +reread once and then anchored. Cursor identity, size, offsets, and anchor bytes +come from the open descriptor so a path rotation or same-inode replacement +cannot persist an offset against different content. Rebuild keeps +the aggregate OTel staging rows, resets their match pointers, and reconciles +them against the rebuilt canonical calls. A match requires conversation id plus +input, cached-input, output, and reasoning-output counters to resolve to exactly +one canonical group. Existing contradictory tier values are preserved and the +completion is marked as a conflict. + +`reset-db --yes` is intentionally different from rebuild: it deletes both OTel +staging tables and their source cursors along with the other tracker-owned rows. + ## Allowance Intelligence Materializations `allowance_observations` stores normalized structured weekly and 5-hour snapshots, @@ -68,7 +101,7 @@ Identical keys reuse a completed snapshot or the same in-flight job. ## Privacy And Rebuilds -Allowance materializations and logical identity contain aggregate metadata only. +Allowance materializations, OTel completion staging, and logical identity contain aggregate metadata only. They do not contain prompts, assistant text, tool output, or raw JSONL content. Rebuilds can recreate them from physical aggregate rows and source provenance. The local content index is a separate opt-in investigation layer and is not used diff --git a/docs/pricing-and-credits.md b/docs/pricing-and-credits.md index e6b05f1c..c01ffd74 100644 --- a/docs/pricing-and-credits.md +++ b/docs/pricing-and-credits.md @@ -14,7 +14,7 @@ Enable optional cost estimates: codex-usage-tracker update-pricing ``` -This fetches OpenAI text-token pricing from `https://developers.openai.com/api/docs/pricing.md`, parses the selected tier, and writes a source-stamped local cache to `~/.codex-usage-tracker/pricing.json`. The default tier is `standard`; other supported tiers are `batch`, `flex`, and `priority`. +This fetches OpenAI text-token pricing from `https://developers.openai.com/api/docs/pricing.md` once, parses the `standard`, `batch`, `flex`, and `priority` tables, and writes a source-stamped pricing-v2 cache to `~/.codex-usage-tracker/pricing.json`. The top-level `models` object remains the selected-tier compatibility projection, while `api_service_tiers` retains every published table so mixed historical calls can be priced by their observed response tier. The updater supports both the older four-value pricing rows and the newer five-value rows used by GPT-5.6 for input, cached input, cache writes, and output. GPT-5.6 Sol, Terra, and Luna are loaded from the published table, and the documented `gpt-5.6` alias resolves to `gpt-5.6-sol`. Current Codex logs do not expose a separate cache-write token counter, so cost estimates use the logged uncached input, cached input, and output counters without adding an explicit cache-write charge. @@ -39,6 +39,20 @@ codex-usage-tracker init-pricing Edit `~/.codex-usage-tracker/pricing.json` with USD-per-million-token rates for local overrides or models that are not present in the OpenAI pricing table. Normal reports never contact the network; only `update-pricing` refreshes the local pricing cache. +### Billing Basis And Tier Scenarios + +OTel `service_tier=priority` proves the response tier, but it does not prove whether the call used ChatGPT/Codex credits or an API key. Set the top-level local `billing_basis` only when you know the billing path: + +```json +{ + "billing_basis": "unknown" +} +``` + +Supported values are `unknown`, `chatgpt_credits`, and `api_tokens`. `update-pricing` preserves this local value. Invalid values make the pricing config invalid instead of silently guessing. + +`estimated_cost_usd` is always an API-token-equivalent estimate. With pricing-v2, it selects the exact observed API table for `priority`, `default`/`standard`, `flex`, or `batch`; it never applies a generic ChatGPT Fast multiplier. Each annotated row also exposes `standard_cost_usd`, `priority_cost_usd`, `pricing_service_tier`, `billing_basis`, and `cost_semantics` so an unknown billing path can be reported as bounded scenarios rather than false actual spend. Pricing-v1 files remain readable and keep their existing single-table behavior. + ## Codex Credits `Codex Credits` is a calculated usage number, not a dashboard-only unit. The tracker uses Codex's logged aggregate token counters and the bundled OpenAI Codex rate-card snapshot to estimate credits consumed by local Codex calls. @@ -62,6 +76,34 @@ codex-usage-tracker update-rate-card The local snapshot is written to `~/.codex-usage-tracker/rate-card.json`. Each bundled rate and alias includes source URL, fetched date, tier, confidence, and alias rationale where applicable. Use `--source-file` only when you have a reviewed replacement JSON snapshot you want the tracker to validate and use. +### Confirmed Fast Usage + +When a call has exact OTel evidence that `fast=1`, the tracker first computes +`standard_usage_credits`, then applies the documented model-family Fast +multiplier to produce `usage_credits`: + +- GPT-5.6 family: `2.5x` +- GPT-5.5 family: `2.5x` +- GPT-5.4 family: `2.0x` + +The row also exposes `fast_usage_credits`, `usage_credit_multiplier`, and +source URL/date/confidence fields so the adjustment is auditable independently +from OTel tier evidence. A confirmed +Fast call whose model has no documented multiplier stays at `1.0x` and is +marked `no_documented_fast_multiplier`; the tracker does not guess. Standard +and Unknown-tier calls also stay at `1.0x`. + +The bundled `fast_multipliers` rate-card entries are source-stamped. A local +`allowance.json` may override a model-family entry with `multiplier`, +`source_name`, `source_url`, and `fetched_at`; valid local entries are marked +`user_override`, while malformed entries do not replace the bundled value. + +This adjustment applies only to Codex/ChatGPT usage-credit estimates. It never +changes API USD estimates, which use the observed API service-tier table and +aggregate token counters. Allowance-drain calibration uses the +standard-credit baseline so a newly observed Fast label does not redefine the +historical credit-to-percentage relationship. + ## Usage Observed `Usage observed` is different from `Codex Credits`. The tracker cannot currently read your logged-in ChatGPT plan, live remaining credits, reset windows, or usage from other agentic surfaces automatically. @@ -90,6 +132,13 @@ Configure the usage component: - Codex upstream log formats can change, and parser compatibility may require tracker updates before new event shapes are fully understood. - Pricing and rate-card sources can change outside this project. Refresh or pin local files when reports need a known source snapshot. - Local Codex logs may not include usage from other ChatGPT agentic surfaces that share the same allowance. +- Service tier is exact only when a local completion explicitly reports it or + Codex `0.143.0` or newer establishes Standard through omission. Older or + unmatched history remains Unknown; latency and reasoning effort cannot prove + Fast usage. +- `priority` can describe ChatGPT Fast mode or API Priority processing. The + retained telemetry does not identify authentication or billing, so leave + `billing_basis` as `unknown` unless you have independent evidence. - Live account allowance cannot be read automatically by this local tracker, and the dashboard does not infer live remaining allowance from the logged-in account plan. - Observed local-log snapshots may be stale until Codex records another model call, and may omit other agentic surfaces that share the same allowance. - Pricing can change after a report is generated. Use `pin-pricing` when you need reproducible historical cost estimates. diff --git a/docs/privacy.md b/docs/privacy.md index 4fca5c42..d2f1fa3a 100644 --- a/docs/privacy.md +++ b/docs/privacy.md @@ -22,11 +22,29 @@ The local SQLite database is stored at `~/.codex-usage-tracker/usage.sqlite3` by quality/coverage metrics, source revisions, and persisted statistical results - diagnostic fact labels tied to aggregate call records, safe event categories, payload type labels, counts, timestamps, and line ranges - pricing, credit, allowance, recommendation, and project metadata derived from aggregate fields +- aggregate OTel completion identifiers, token counters, normalized service + tier/provenance, application version, semantic fingerprints, source cursors, + and bounded reconciliation status from local + `~/.codex-usage-tracker/otel/codex-completions*.jsonl` files - normalized local content-index rows for local investigation, including conversation turns, bounded content fragments, tool calls, command runs, file events, source provenance, parser adapter metadata, parser warnings, and FTS5 search rows when SQLite supports FTS5 - bounded local investigation run summaries, including run kind, schema id, content-mode flags, strict summary JSON, branch counts, evidence counts, and timestamps The content index is intended for local MCP/API exploration. It is not a hosted collection system and it does not change where the original Codex logs live. Normalized command and file-event rows store command roots/labels plus path hashes/basenames rather than full shell commands or full paths; bounded raw snippets remain confined to `content_fragments`. +The OTel completion reader accepts only `codex.sse_event` / +`response.completed` aggregate fields needed for conservative call matching and +tier classification. It never persists OTLP response bodies or arbitrary +attributes. Default exports do not expose staging fingerprints, source paths, +or matched record pointers. Support bundles report only whether the configured +completion directory exists and bounded aggregate refresh/reconciliation counts. +The exact response tier does not reveal whether ChatGPT credits or an API key +paid for the call; the tracker keeps that billing basis as separate local +configuration and defaults it to Unknown. + +`rebuild-index` retains aggregate OTel staging so it can reconcile tiers after +canonical rows are recreated. Confirmed `reset-db --yes` deletes that staging +and its cursors. Neither operation changes or deletes the source exporter files. + Cloned Codex tasks can copy historical calls into another local JSONL file. The tracker keeps every physical row and its source metadata in SQLite, but default dashboard, CLI, MCP, report, recommendation, allowance, compression, and export totals use one canonical representative for strict fingerprint matches. Similar calls are never excluded on fuzzy evidence. Deduplication diagnostics expose aggregate totals plus bounded provenance metadata for excluded rows; they never include prompt, response, tool-output, or raw-log content. ## Shareable Outputs @@ -42,6 +60,11 @@ Default shareable outputs omit indexed or raw content. That includes: - allowance exports - source coverage reports +Tier-aware shareable rows may include exact service-tier labels, API-equivalent +Standard/Priority cost scenarios, ChatGPT-equivalent credit scenarios, billing +basis, and source-stamped multiplier metadata. These are aggregate pricing +annotations and do not include authentication tokens or raw OTel payloads. + Those outputs should not include prompts, assistant messages, tool output, pasted secrets, raw snippets, reconstructed transcript evidence, indexed fragments, or full JSONL records unless a future command is explicitly documented as a local raw/content export. Default totals and allowance intelligence use canonical usage rows. High-confidence @@ -112,7 +135,7 @@ The report pack contains aggregate report definitions, linked aggregate call rows, a schema marker, and a server generation timestamp. It does not include prompts, assistant text, tool output, command output, or indexed content. -Source JSONL reads happen during refresh/indexing, explicit on-demand context loading for one selected call, and explicit synthetic benchmark/diagnostic runs. Live refresh records source metadata and parser cursors so unchanged logs can be skipped and append-only growth can parse from the last indexed byte. Live aggregate APIs do not return indexed content unless an endpoint is explicitly documented as a local content investigation endpoint. +Source JSONL reads happen during refresh/indexing, explicit on-demand context loading for one selected call, and explicit synthetic benchmark/diagnostic runs. Refresh also reads only local `codex-completions*.jsonl` OTLP exporter files for aggregate service-tier enrichment. Live refresh records source metadata and parser cursors so unchanged logs can be skipped and append-only growth can parse from the last indexed byte. Live aggregate APIs do not return indexed content unless an endpoint is explicitly documented as a local content investigation endpoint. ## Privacy Modes @@ -142,7 +165,7 @@ Strict mode redacts local diagnostic path strings in bundle doctor details while Cost estimates are calculated only from aggregate token fields and your local pricing config. They are omitted when no matching model price is configured. Pricing refreshes pull only OpenAI's public pricing markdown and do not send local usage data anywhere. -Codex credit estimates are calculated only from aggregate token fields and bundled or locally configured rate-card values. The optional allowance config stores only remaining percentages, reset times, and credit totals you manually enter. Observed rate-limit snapshots, when present in Codex token-count logs, store only structured percentages, window lengths, reset times, plan type, and limit id. +Codex credit estimates are calculated only from aggregate token fields and bundled or locally configured rate-card values. Confirmed Fast calls may apply the documented model-family credit multiplier; this does not change USD token-cost estimates. The optional allowance config stores only remaining percentages, reset times, and credit totals you manually enter. Observed rate-limit snapshots, when present in Codex token-count logs, store only structured percentages, window lengths, reset times, plan type, and limit id. Allowance calibration and forecasts use canonical aggregate credits and observed percentages. A displayed credits-per-percent value is a personal historical diff --git a/docs/superpowers/plans/2026-07-16-otel-fast-usage-ingestion.md b/docs/superpowers/plans/2026-07-16-otel-fast-usage-ingestion.md new file mode 100644 index 00000000..b2659bc4 --- /dev/null +++ b/docs/superpowers/plans/2026-07-16-otel-fast-usage-ingestion.md @@ -0,0 +1,1685 @@ +# OTel Fast Usage Ingestion Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Enrich canonical Codex usage calls with exact Fast-versus-Standard service-tier metadata from the local OTLP completion stream and apply confirmed Fast multipliers to ChatGPT credit estimates. + +**Architecture:** Session JSONL remains authoritative for usage calls and token totals. A pure parser converts allowlisted OTLP completion attributes into aggregate records, a persistent sidecar ingests them incrementally, and a conservative reconciler enriches one canonical usage group only when conversation and token identity are unique. Pricing and dashboard layers consume the additive tier fields while USD token estimates remain unchanged. + +**Tech Stack:** Python 3.10+, dataclasses, SQLite, pytest, TypeScript, React, Vitest, existing Vite dashboard build. + +## Global Constraints + +- Persist only `conversation.id`, four token counters, model, effort, service tier, app version, timestamp, source path/line, fingerprint, and match state from OTLP. +- Never persist or expose OTLP bodies, arbitrary attributes, resource attributes, prompts, account data, tool content, trace IDs, or span IDs. +- Treat timestamp as a fingerprint and diagnostic field only; never use timestamp proximity for correlation. +- Enrich only when conversation ID and all four token counters match and candidates resolve to exactly one canonical usage group. +- Normalize explicit `priority` or `fast` as confirmed Fast; explicit `default` or `standard` as confirmed Standard. +- Treat omitted tier as protocol-confirmed Standard only for parseable `app.version >= 0.143.0`; older or unparseable omissions remain unknown. +- Preserve session JSONL as the canonical source of call identity and token totals. +- Apply Fast credit multipliers only to confirmed Fast calls: GPT-5.6 `2.5`, GPT-5.5 `2.5`, GPT-5.4 `2.0`. +- Do not change USD token-cost estimates from service-tier enrichment. +- Keep committed fixtures entirely synthetic and keep aggregate-only default output behavior. + +--- + +## File structure + +- `src/codex_usage_tracker/parser/otel.py`: pure OTLP traversal, allowlisted value coercion, version-aware tier normalization, and semantic fingerprinting. +- `src/codex_usage_tracker/store/otel_schema.py`: schema version 30 sidecar tables and indexes. +- `src/codex_usage_tracker/store/otel_ingest.py`: deterministic discovery, cursor handling, complete-line reads, and normalized staging upserts. +- `src/codex_usage_tracker/store/otel_reconciliation.py`: unique canonical-group matching, clone propagation, conflict handling, and match reset. +- `src/codex_usage_tracker/pricing/fast_tier.py`: shared documented Fast multiplier policy and credit provenance. +- Existing core/store/refresh/report modules: additive usage columns, orchestration, rebuild semantics, metadata, and privacy-safe support coverage. +- Existing React source: typed tier decoding, exact/proxy distinction, Calls column, detail labels, and CSV fields. +- `tests/otel_helpers.py`: synthetic UsageEvent, session JSONL, and OTLP factories shared by focused tests. +- Focused parser/store/pricing/dashboard tests: synthetic OTLP batches and databases only. + +### Task 1: Add the aggregate tier schema and persistent sidecar tables + +**Files:** +- Modify: `src/codex_usage_tracker/core/paths.py` +- Modify: `src/codex_usage_tracker/core/models.py` +- Modify: `src/codex_usage_tracker/core/schema.py` +- Create: `src/codex_usage_tracker/store/otel_schema.py` +- Modify: `src/codex_usage_tracker/store/schema.py` +- Test: `tests/core/test_schema.py` +- Create: `tests/store/test_otel_schema.py` +- Modify: `tests/store/test_store_migrations.py` + +**Interfaces:** +- Produces: `DEFAULT_OTEL_COMPLETIONS_DIR: Path`. +- Produces: nullable `UsageEvent.service_tier`, `fast`, `service_tier_source`, and `service_tier_confidence` fields. +- Produces: `otel_completion_sources` and `otel_completion_events` tables through schema migration 30. +- Consumes: existing `UsageColumn`, migration recording, and repair-column behavior. + +- [ ] **Step 1: Write failing schema-shape and migration tests** + +```python +def test_usage_event_schema_includes_nullable_service_tier_fields() -> None: + row = usage_event_fixture().to_row() + assert row["service_tier"] is None + assert row["fast"] is None + assert row["service_tier_source"] is None + assert row["service_tier_confidence"] is None + + +def test_schema_migration_creates_otel_sidecar_tables(tmp_path: Path) -> None: + with connect(tmp_path / "usage.sqlite3") as conn: + init_db(conn) + source_columns = { + str(row["name"]) for row in conn.execute("PRAGMA table_info(otel_completion_sources)") + } + event_columns = { + str(row["name"]) for row in conn.execute("PRAGMA table_info(otel_completion_events)") + } + assert {"source_path", "device", "inode", "size", "parsed_offset", "parsed_line"} <= source_columns + assert {"fingerprint", "conversation_id", "service_tier", "fast", "match_status"} <= event_columns + + +def test_legacy_migration_exports_unknown_tier_fields_as_empty(tmp_path: Path) -> None: + db_path = tmp_path / "usage.sqlite3" + csv_path = tmp_path / "usage.csv" + _write_legacy_usage_database(db_path) + export_usage_csv(csv_path, db_path=db_path) + with csv_path.open(encoding="utf-8", newline="") as handle: + row = next(csv.DictReader(handle)) + assert row["service_tier"] == "" + assert row["fast"] == "" + assert row["service_tier_source"] == "" + assert row["service_tier_confidence"] == "" +``` + +- [ ] **Step 2: Run the focused tests and verify the missing fields/tables fail** + +Run: `.venv/bin/python -m pytest tests/core/test_schema.py tests/store/test_otel_schema.py -q` + +Expected: FAIL because the four `UsageEvent` fields and OTel sidecar tables do not exist. + +- [ ] **Step 3: Add the paths, nullable columns, and migration** + +```python +# core/paths.py +DEFAULT_OTEL_COMPLETIONS_DIR = APP_DIR / "otel" + +# core/models.py, after duplicate_reason +service_tier: str | None = None +fast: int | None = None +service_tier_source: str | None = None +service_tier_confidence: str | None = None + +# core/schema.py, before derived numeric columns +UsageColumn("service_tier", "TEXT", "TEXT", repairable=True), +UsageColumn("fast", "INTEGER", "INTEGER", repairable=True), +UsageColumn("service_tier_source", "TEXT", "TEXT", repairable=True), +UsageColumn("service_tier_confidence", "TEXT", "TEXT", repairable=True), +``` + +Create `store/otel_schema.py` with one idempotent migration: + +```python +MIGRATION_NAMES = {30: "persist OTel completion tier enrichment"} + +OTEL_MATCH_STATUSES = ("pending", "matched", "ambiguous", "conflict", "invalid") + + +def migrate_otel_completion_tiers(conn: sqlite3.Connection) -> None: + existing_usage_columns = { + str(row["name"]) for row in conn.execute("PRAGMA table_info(usage_events)") + } + for column, column_type in { + "service_tier": "TEXT", + "fast": "INTEGER", + "service_tier_source": "TEXT", + "service_tier_confidence": "TEXT", + }.items(): + if column not in existing_usage_columns: + conn.execute( # nosec B608 - fixed migration column names + f"ALTER TABLE usage_events ADD COLUMN {column} {column_type}" + ) + conn.executescript( + """ + CREATE TABLE IF NOT EXISTS otel_completion_sources ( + source_path TEXT PRIMARY KEY, + device INTEGER NOT NULL, + inode INTEGER NOT NULL, + size INTEGER NOT NULL, + parsed_offset INTEGER NOT NULL, + parsed_line INTEGER NOT NULL, + updated_at TEXT NOT NULL + ); + CREATE TABLE IF NOT EXISTS otel_completion_events ( + fingerprint TEXT PRIMARY KEY, + conversation_id TEXT, + event_timestamp TEXT, + input_tokens INTEGER, + cached_input_tokens INTEGER, + output_tokens INTEGER, + reasoning_output_tokens INTEGER, + model TEXT, + effort TEXT, + service_tier TEXT, + fast INTEGER, + service_tier_source TEXT, + service_tier_confidence TEXT, + app_version TEXT, + source_path TEXT NOT NULL, + source_line INTEGER NOT NULL, + match_status TEXT NOT NULL CHECK ( + match_status IN ('pending', 'matched', 'ambiguous', 'conflict', 'invalid') + ), + matched_record_id TEXT + ); + CREATE INDEX IF NOT EXISTS idx_otel_completion_match_status + ON otel_completion_events(match_status); + CREATE INDEX IF NOT EXISTS idx_otel_completion_identity + ON otel_completion_events( + conversation_id, input_tokens, cached_input_tokens, + output_tokens, reasoning_output_tokens + ); + """ + ) +``` + +Set `SCHEMA_VERSION = 30`, merge `otel_schema.MIGRATION_NAMES`, and append `(30, otel_schema.migrate_otel_completion_tiers)` to `_schema_migrations()`. + +- [ ] **Step 4: Run the focused schema tests and verify they pass** + +Run: `.venv/bin/python -m pytest tests/core/test_schema.py tests/store/test_otel_schema.py tests/store/test_store_migrations.py -q` + +Expected: PASS. + +- [ ] **Step 5: Commit the schema slice** + +```bash +git add -- src/codex_usage_tracker/core/paths.py src/codex_usage_tracker/core/models.py src/codex_usage_tracker/core/schema.py src/codex_usage_tracker/store/otel_schema.py src/codex_usage_tracker/store/schema.py tests/core/test_schema.py tests/store/test_otel_schema.py tests/store/test_store_migrations.py +git commit -m "feat: add OTel completion tier schema" +``` + +### Task 2: Parse and normalize aggregate OTLP completions + +**Files:** +- Create: `src/codex_usage_tracker/parser/otel.py` +- Modify: `src/codex_usage_tracker/parser/__init__.py` +- Create: `tests/otel_helpers.py` +- Create: `tests/parser/test_otel_parser.py` + +**Interfaces:** +- Produces: `OtelCompletion` and `OtelParseResult` frozen dataclasses. +- Produces: `parse_otlp_json_line(raw: str) -> OtelParseResult`. +- Produces: `OTEL_DIAGNOSTIC_KEYS` for bounded refresh metadata. +- Consumes: one JSON line only; it never receives a database connection. + +- [ ] **Step 1: Write failing parser tests for traversal, normalization, omission semantics, and privacy** + +```python +def test_parse_otlp_batch_extracts_only_completion_allowlist() -> None: + raw = synthetic_otlp_line( + attributes={ + "event.name": "codex.sse_event", + "event.kind": "response.completed", + "conversation.id": "synthetic-conversation", + "input_token_count": 120, + "cached_token_count": 40, + "output_token_count": 30, + "reasoning_token_count": 10, + "model": "gpt-5.6-sol", + "model_reasoning_effort": "high", + "service_tier": "priority", + "app.version": "0.143.0", + "secret.attribute": "must-not-survive", + }, + body="synthetic private body that must not survive", + ) + result = parse_otlp_json_line(raw) + assert len(result.completions) == 1 + completion = result.completions[0] + assert completion.service_tier == "fast" + assert completion.fast == 1 + assert completion.service_tier_confidence == "exact" + assert "private body" not in repr(completion) + assert "secret.attribute" not in repr(completion) + + +@pytest.mark.parametrize( + ("version", "tier", "fast", "confidence"), + [("0.143.0", "standard", 0, "protocol"), ("0.142.9", None, None, None), ("bad", None, None, None)], +) +def test_missing_service_tier_uses_versioned_protocol_semantics( + version: str, + tier: str | None, + fast: int | None, + confidence: str | None, +) -> None: + result = parse_otlp_json_line( + synthetic_otlp_line(attributes=completion_attributes(app_version=version, service_tier=None)) + ) + completion = result.completions[0] + assert (completion.service_tier, completion.fast, completion.service_tier_confidence) == ( + tier, + fast, + confidence, + ) +``` + +Add these concrete cases in the same test file: + +```python +@pytest.mark.parametrize( + ("raw_tier", "normalized_tier", "fast"), + [ + ("fast", "fast", 1), + ("default", "standard", 0), + ("standard", "standard", 0), + ("flex", "flex", 0), + ], +) +def test_explicit_tier_aliases(raw_tier: str, normalized_tier: str, fast: int) -> None: + attributes = completion_attributes(service_tier=raw_tier) + completion = parse_otlp_json_line(synthetic_otlp_line(attributes=attributes)).completions[0] + assert (completion.service_tier, completion.fast) == (normalized_tier, fast) + + +def test_multiple_resource_and_scope_groups_are_traversed() -> None: + first = json.loads(synthetic_otlp_line(attributes=completion_attributes(conversation_id="a"))) + second = json.loads(synthetic_otlp_line(attributes=completion_attributes(conversation_id="b"))) + first["resourceLogs"].extend(second["resourceLogs"]) + result = parse_otlp_json_line(json.dumps(first)) + assert [item.conversation_id for item in result.completions] == ["a", "b"] + + +@pytest.mark.parametrize("raw", ["{", "[]", json.dumps({"resourceLogs": "bad"})]) +def test_malformed_payloads_return_bounded_diagnostics(raw: str) -> None: + result = parse_otlp_json_line(raw) + assert not result.completions + assert sum(result.diagnostics.values()) >= 1 + + +def test_non_completion_and_missing_identity_never_become_pending_matches() -> None: + unrelated = completion_attributes() + unrelated["event.kind"] = "response.created" + missing_identity = completion_attributes() + missing_identity.pop("conversation.id") + assert not parse_otlp_json_line(synthetic_otlp_line(attributes=unrelated)).completions + completion = parse_otlp_json_line( + synthetic_otlp_line(attributes=missing_identity) + ).completions[0] + assert completion.match_status == "invalid" +``` + +- [ ] **Step 2: Run the parser tests and verify the import fails** + +Run: `.venv/bin/python -m pytest tests/parser/test_otel_parser.py -q` + +Expected: FAIL because `codex_usage_tracker.parser.otel` does not exist. + +- [ ] **Step 3: Implement pure allowlisted parsing and semantic fingerprinting** + +```python +OTEL_DIAGNOSTIC_KEYS = ( + "otel_invalid_json", + "otel_invalid_record", + "otel_invalid_integer", + "otel_unsupported_version", + "otel_non_completion", +) + + +@dataclass(frozen=True) +class OtelCompletion: + fingerprint: str + conversation_id: str | None + event_timestamp: str | None + input_tokens: int | None + cached_input_tokens: int | None + output_tokens: int | None + reasoning_output_tokens: int | None + model: str | None + effort: str | None + service_tier: str | None + fast: int | None + service_tier_source: str | None + service_tier_confidence: str | None + app_version: str | None + match_status: str + + +@dataclass(frozen=True) +class OtelParseResult: + completions: Sequence[OtelCompletion] + diagnostics: dict[str, int] + + +def parse_otlp_json_line(raw: str) -> OtelParseResult: + try: + payload = json.loads(raw) + except (json.JSONDecodeError, UnicodeDecodeError): + return OtelParseResult((), {"otel_invalid_json": 1}) + completions: list[OtelCompletion] = [] + diagnostics: Counter[str] = Counter() + for record in _log_records(payload): + attributes = _allowlisted_attributes(record.get("attributes")) + if attributes.get("event.name") != "codex.sse_event" or attributes.get("event.kind") != "response.completed": + diagnostics["otel_non_completion"] += 1 + continue + completion, completion_diagnostics = _completion_from_attributes(record, attributes) + diagnostics.update(completion_diagnostics) + completions.append(completion) + return OtelParseResult(tuple(completions), dict(diagnostics)) +``` + +`_allowlisted_attributes()` must decode only the ten approved keys; `_otlp_value()` must accept only scalar `stringValue`, `intValue`, `doubleValue`, and `boolValue`. `_normalize_tier()` returns `(tier, fast, source, confidence)` and `_semantic_fingerprint()` hashes a versioned, sorted JSON object containing only the normalized allowlist fields. A syntactically valid completion without usable identity or tier semantics receives `match_status = "invalid"`. + +Create `tests/otel_helpers.py` with synthetic-only factories used by later tasks: + +```python +def completion_attributes( + *, + conversation_id: str = "synthetic-conversation", + tokens: tuple[int, int, int, int] = (120, 40, 30, 10), + model: str = "gpt-5.6-sol", + effort: str = "high", + service_tier: str | None = "priority", + app_version: str = "0.143.0", +) -> dict[str, object]: + input_tokens, cached_tokens, output_tokens, reasoning_tokens = tokens + values: dict[str, object] = { + "event.name": "codex.sse_event", + "event.kind": "response.completed", + "conversation.id": conversation_id, + "input_token_count": input_tokens, + "cached_token_count": cached_tokens, + "output_token_count": output_tokens, + "reasoning_token_count": reasoning_tokens, + "model": model, + "model_reasoning_effort": effort, + "app.version": app_version, + } + if service_tier is not None: + values["service_tier"] = service_tier + return values + + +def synthetic_otlp_line(*, attributes: dict[str, object], body: str = "synthetic body") -> str: + encoded = [ + {"key": key, "value": {"intValue" if isinstance(value, int) else "stringValue": str(value)}} + for key, value in attributes.items() + ] + return json.dumps( + {"resourceLogs": [{"scopeLogs": [{"logRecords": [{ + "timeUnixNano": "1784160000000000000", + "body": {"stringValue": body}, + "attributes": encoded, + }]}]}]} + ) + + +def synthetic_fast_completion(conversation_id: str, input_tokens: int) -> str: + return synthetic_otlp_line( + attributes=completion_attributes( + conversation_id=conversation_id, + tokens=(input_tokens, 0, 20, 5), + service_tier="priority", + ) + ) + + +def synthetic_standard_completion(conversation_id: str, input_tokens: int) -> str: + return synthetic_otlp_line( + attributes=completion_attributes( + conversation_id=conversation_id, + tokens=(input_tokens, 0, 20, 5), + service_tier=None, + ) + ) + + +def write_lines(path: Path, lines: list[str]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("".join(f"{line}\n" for line in lines), encoding="utf-8") + + +def append_text(path: Path, value: str) -> None: + with path.open("a", encoding="utf-8") as handle: + handle.write(value) + + +@contextmanager +def initialized_connection(tmp_path: Path) -> Iterator[sqlite3.Connection]: + with connect(tmp_path / "usage.sqlite3") as conn: + init_db(conn) + yield conn +``` + +- [ ] **Step 4: Run parser tests and static checks** + +Run: `.venv/bin/python -m pytest tests/parser/test_otel_parser.py -q` + +Run: `.venv/bin/python -m ruff check src/codex_usage_tracker/parser/otel.py tests/parser/test_otel_parser.py` + +Expected: PASS. + +- [ ] **Step 5: Commit the parser slice** + +```bash +git add -- src/codex_usage_tracker/parser/otel.py src/codex_usage_tracker/parser/__init__.py tests/otel_helpers.py tests/parser/test_otel_parser.py +git commit -m "feat: parse aggregate OTel completions" +``` + +### Task 3: Incrementally ingest current and rotated completion files + +**Files:** +- Create: `src/codex_usage_tracker/store/otel_ingest.py` +- Create: `tests/store/test_otel_ingest.py` + +**Interfaces:** +- Consumes: `parse_otlp_json_line()` and an initialized SQLite connection. +- Produces: `discover_otel_sources(directory: Path) -> list[Path]`. +- Produces: `ingest_otel_completion_files(conn, directory: Path) -> OtelIngestResult`. +- Persists: normalized rows in `otel_completion_events` and complete-line cursors in `otel_completion_sources`. + +- [ ] **Step 1: Write failing ingestion tests for append, partial lines, rotation, truncation, and semantic deduplication** + +```python +def test_incremental_ingest_reads_each_complete_line_once(tmp_path: Path) -> None: + directory = tmp_path / "otel" + source = directory / "codex-completions.jsonl" + write_lines(source, [synthetic_fast_completion("conversation-a", 100)]) + with initialized_connection(tmp_path) as conn: + first = ingest_otel_completion_files(conn, directory) + append_text(source, synthetic_standard_completion("conversation-b", 200) + "\n") + second = ingest_otel_completion_files(conn, directory) + rows = conn.execute("SELECT fingerprint FROM otel_completion_events").fetchall() + assert first.imported == 1 + assert second.imported == 1 + assert len(rows) == 2 + + +def test_partial_last_line_is_retried_after_append(tmp_path: Path) -> None: + directory = tmp_path / "otel" + source = directory / "codex-completions.jsonl" + complete = synthetic_otlp_line(attributes=completion_attributes()) + source.parent.mkdir(parents=True) + source.write_text(complete[:-1], encoding="utf-8") + with initialized_connection(tmp_path) as conn: + assert ingest_otel_completion_files(conn, directory).imported == 0 + source.write_text(complete + "\n", encoding="utf-8") + assert ingest_otel_completion_files(conn, directory).imported == 1 + + +def test_rotation_and_reread_do_not_duplicate_semantic_completion(tmp_path: Path) -> None: + directory = tmp_path / "otel" + current = directory / "codex-completions.jsonl" + rotated = directory / "codex-completions-20260716.jsonl" + line = synthetic_otlp_line(attributes=completion_attributes()) + "\n" + current.parent.mkdir(parents=True) + current.write_text(line, encoding="utf-8") + with initialized_connection(tmp_path) as conn: + ingest_otel_completion_files(conn, directory) + current.replace(rotated) + current.write_text(line, encoding="utf-8") + result = ingest_otel_completion_files(conn, directory) + count = conn.execute("SELECT COUNT(*) FROM otel_completion_events").fetchone()[0] + assert count == 1 + assert result.duplicates >= 1 + + +def test_truncation_or_inode_replacement_resets_cursor_safely(tmp_path: Path) -> None: + directory = tmp_path / "otel" + source = directory / "codex-completions.jsonl" + source.parent.mkdir(parents=True) + source.write_text( + synthetic_otlp_line(attributes=completion_attributes(conversation_id="synthetic-a")) + "\n", + encoding="utf-8", + ) + with initialized_connection(tmp_path) as conn: + ingest_otel_completion_files(conn, directory) + source.unlink() + source.write_text( + synthetic_otlp_line(attributes=completion_attributes(conversation_id="synthetic-b")) + "\n", + encoding="utf-8", + ) + assert ingest_otel_completion_files(conn, directory).imported == 1 + + +def test_ingest_never_persists_body_or_arbitrary_attributes(tmp_path: Path) -> None: + directory = tmp_path / "otel" + attributes = completion_attributes() + attributes["secret.attribute"] = "synthetic-secret-value" + write_lines( + directory / "codex-completions.jsonl", + [synthetic_otlp_line(attributes=attributes, body="synthetic-private-body")], + ) + with initialized_connection(tmp_path) as conn: + ingest_otel_completion_files(conn, directory) + rows = conn.execute("SELECT * FROM otel_completion_events").fetchall() + encoded = json.dumps([dict(row) for row in rows], sort_keys=True) + assert "synthetic-private-body" not in encoded + assert "synthetic-secret-value" not in encoded + assert "secret.attribute" not in encoded +``` + +- [ ] **Step 2: Run the ingestion tests and verify the module is missing** + +Run: `.venv/bin/python -m pytest tests/store/test_otel_ingest.py -q` + +Expected: FAIL because `store.otel_ingest` does not exist. + +- [ ] **Step 3: Implement deterministic discovery, cursor reset, and staging upserts** + +```python +@dataclass(frozen=True) +class OtelIngestResult: + files_scanned: int = 0 + imported: int = 0 + duplicates: int = 0 + diagnostics: dict[str, int] = field(default_factory=dict) + + +def discover_otel_sources(directory: Path) -> list[Path]: + if not directory.is_dir(): + return [] + return sorted(path for path in directory.glob("codex-completions*.jsonl") if path.is_file()) + + +def ingest_otel_completion_files(conn: sqlite3.Connection, directory: Path) -> OtelIngestResult: + totals = _MutableIngestTotals() + for path in discover_otel_sources(directory): + state = _source_state(conn, path) + stat = path.stat() + offset, line_number = _resume_position(state, stat) + next_offset, next_line = _ingest_complete_lines(conn, path, offset, line_number, totals) + _upsert_source_cursor(conn, path, stat, next_offset, next_line) + totals.files_scanned += 1 + return totals.freeze() +``` + +Open sources in binary mode, seek to the stored offset, and advance the cursor only after a newline-terminated record is decoded and handled. Insert each completion with `ON CONFLICT(fingerprint) DO NOTHING`; count a zero rowcount as a semantic duplicate. Store the source path and line only in the sidecar table, never in public payloads. + +- [ ] **Step 4: Run ingestion and migration tests** + +Run: `.venv/bin/python -m pytest tests/store/test_otel_ingest.py tests/store/test_otel_schema.py -q` + +Expected: PASS. + +- [ ] **Step 5: Commit the incremental ingestion slice** + +```bash +git add -- src/codex_usage_tracker/store/otel_ingest.py tests/store/test_otel_ingest.py +git commit -m "feat: ingest OTel completions incrementally" +``` + +### Task 4: Reconcile exactly one canonical call group and preserve enrichment + +**Files:** +- Create: `src/codex_usage_tracker/store/otel_reconciliation.py` +- Modify: `src/codex_usage_tracker/store/api.py` +- Modify: `src/codex_usage_tracker/store/source_replacement.py` +- Create: `tests/store/test_otel_reconciliation.py` + +**Interfaces:** +- Consumes: staged rows with `match_status IN ('pending', 'ambiguous', 'matched')`. +- Produces: `reconcile_otel_completions(conn) -> OtelReconciliationResult`. +- Produces: `reset_otel_completion_matches(conn) -> None` for rebuilds. +- Preserves: non-null tier fields during record upsert and source replacement. + +- [ ] **Step 1: Write failing reconciliation tests for uniqueness, clones, ambiguity, conflicts, and preservation** + +```python +def test_unique_token_identity_enriches_every_clone_in_one_canonical_group(tmp_path: Path) -> None: + with initialized_connection(tmp_path) as conn: + insert_usage_clone_group(conn, conversation_id="conversation-a", tokens=(100, 40, 30, 10)) + stage_completion(conn, conversation_id="conversation-a", tokens=(100, 40, 30, 10), fast=1) + result = reconcile_otel_completions(conn) + rows = conn.execute("SELECT service_tier, fast FROM usage_events ORDER BY record_id").fetchall() + assert result.matched == 1 + assert [(row["service_tier"], row["fast"]) for row in rows] == [("fast", 1), ("fast", 1)] + + +def test_same_tokens_in_two_canonical_groups_remain_ambiguous(tmp_path: Path) -> None: + with initialized_connection(tmp_path) as conn: + insert_usage_clone_group(conn, "conversation-a", (100, 40, 30, 10), canonical="canonical-a") + insert_usage_clone_group(conn, "conversation-a", (100, 40, 30, 10), canonical="canonical-b") + fingerprint = stage_completion(conn, "conversation-a", (100, 40, 30, 10), fast=1) + reconcile_otel_completions(conn) + status = conn.execute( + "SELECT match_status FROM otel_completion_events WHERE fingerprint = ?", (fingerprint,) + ).fetchone()[0] + tiers = conn.execute("SELECT service_tier FROM usage_events").fetchall() + assert status == "ambiguous" + assert all(row[0] is None for row in tiers) + + +def test_timestamp_distance_does_not_affect_matching(tmp_path: Path) -> None: + with initialized_connection(tmp_path) as conn: + insert_usage_clone_group(conn, "conversation-a", (100, 40, 30, 10)) + stage_completion( + conn, + "conversation-a", + (100, 40, 30, 10), + fast=1, + event_timestamp="2099-01-01T00:00:00Z", + ) + assert reconcile_otel_completions(conn).matched == 1 + + +def test_model_or_effort_mismatch_prevents_match_when_both_are_present(tmp_path: Path) -> None: + with initialized_connection(tmp_path) as conn: + insert_usage_clone_group(conn, "conversation-a", (100, 40, 30, 10), model="gpt-5.5") + stage_completion(conn, "conversation-a", (100, 40, 30, 10), fast=1, model="gpt-5.6") + assert reconcile_otel_completions(conn).pending == 1 + + +def test_contradictory_existing_tier_is_preserved_and_marks_conflict(tmp_path: Path) -> None: + with initialized_connection(tmp_path) as conn: + insert_usage_clone_group( + conn, "conversation-a", (100, 40, 30, 10), service_tier="standard", fast=0 + ) + fingerprint = stage_completion(conn, "conversation-a", (100, 40, 30, 10), fast=1) + assert reconcile_otel_completions(conn).conflicts == 1 + row = conn.execute("SELECT service_tier, fast FROM usage_events LIMIT 1").fetchone() + status = conn.execute( + "SELECT match_status FROM otel_completion_events WHERE fingerprint = ?", (fingerprint,) + ).fetchone()[0] + assert tuple(row) == ("standard", 0) + assert status == "conflict" + + +def test_usage_upsert_and_source_replacement_preserve_non_null_tier(tmp_path: Path) -> None: + db_path = tmp_path / "usage.sqlite3" + original = synthetic_usage_event("record-a", "conversation-a", (100, 40, 30, 10), fast=1) + upsert_usage_events([original], db_path=db_path) + reparsed = replace(original, thread_name="reparsed", service_tier=None, fast=None) + upsert_usage_events([reparsed], db_path=db_path, replace_source_files=[Path(original.source_file)]) + row = query_usage_record(db_path=db_path, record_id="record-a") + assert row is not None + assert (row["service_tier"], row["fast"]) == ("fast", 1) +``` + +Extend `tests/otel_helpers.py` with the complete aggregate-row factory: + +```python +def synthetic_usage_event( + record_id: str, + conversation_id: str, + tokens: tuple[int, int, int, int], + *, + canonical: str = "canonical-a", + model: str = "gpt-5.6-sol", + effort: str = "high", + service_tier: str | None = None, + fast: int | None = None, + duplicate: int = 0, +) -> UsageEvent: + input_tokens, cached_tokens, output_tokens, reasoning_tokens = tokens + total_tokens = input_tokens + output_tokens + return UsageEvent( + record_id=record_id, + session_id=conversation_id, + thread_name="Synthetic thread", + session_updated_at="2026-07-16T00:00:00Z", + event_timestamp="2026-07-16T00:00:00Z", + source_file="/synthetic/session.jsonl", + line_number=1, + turn_id="synthetic-turn", + turn_timestamp="2026-07-16T00:00:00Z", + cwd="/synthetic/project", + model=model, + effort=effort, + current_date="2026-07-16", + timezone="UTC", + call_initiator="user", + call_initiator_reason="user_message", + call_initiator_confidence="high", + is_archived=0, + thread_key="thread:Synthetic", + thread_call_index=None, + previous_record_id=None, + next_record_id=None, + thread_source="user", + subagent_type=None, + agent_role=None, + agent_nickname=None, + parent_session_id=None, + parent_thread_name=None, + parent_session_updated_at=None, + model_context_window=258_400, + input_tokens=input_tokens, + cached_input_tokens=cached_tokens, + output_tokens=output_tokens, + reasoning_output_tokens=reasoning_tokens, + total_tokens=total_tokens, + cumulative_input_tokens=input_tokens, + cumulative_cached_input_tokens=cached_tokens, + cumulative_output_tokens=output_tokens, + cumulative_reasoning_output_tokens=reasoning_tokens, + cumulative_total_tokens=total_tokens, + usage_fingerprint=f"synthetic-fingerprint-{canonical}", + canonical_record_id=canonical, + is_duplicate=duplicate, + duplicate_reason="copied_usage_fingerprint" if duplicate else None, + service_tier=service_tier or ("fast" if fast == 1 else "standard" if fast == 0 else None), + fast=fast, + service_tier_source="otel_response_completed" if fast is not None else None, + service_tier_confidence="exact" if fast is not None else None, + ) +``` + +Define `insert_usage_clone_group()` and `stage_completion()` in `test_otel_reconciliation.py`; insert rows using `UsageEvent.to_row()`/`EVENT_COLUMNS` and the exact sidecar columns from Task 1. `insert_usage_clone_group()` creates two physical rows with one canonical ID and `is_duplicate` values `0` and `1`; `stage_completion()` returns its deterministic synthetic fingerprint. These helpers contain no raw-log fields. + +```python +def insert_usage_clone_group( + conn: sqlite3.Connection, + conversation_id: str, + tokens: tuple[int, int, int, int], + *, + canonical: str = "canonical-a", + model: str = "gpt-5.6-sol", + service_tier: str | None = None, + fast: int | None = None, +) -> None: + events = [ + synthetic_usage_event( + f"{canonical}-record-{index}", + conversation_id, + tokens, + canonical=canonical, + model=model, + service_tier=service_tier, + fast=fast, + duplicate=int(index == 1), + ) + for index in range(2) + ] + placeholders = ", ".join("?" for _column in EVENT_COLUMNS) + conn.executemany( + f"INSERT INTO usage_events ({', '.join(EVENT_COLUMNS)}) VALUES ({placeholders})", + [[event.to_row()[column] for column in EVENT_COLUMNS] for event in events], + ) + + +def stage_completion( + conn: sqlite3.Connection, + conversation_id: str, + tokens: tuple[int, int, int, int], + *, + fast: int, + model: str = "gpt-5.6-sol", + event_timestamp: str = "2026-07-16T00:00:00Z", +) -> str: + input_tokens, cached_tokens, output_tokens, reasoning_tokens = tokens + fingerprint = hashlib.sha256( + repr((conversation_id, tokens, fast, model, event_timestamp)).encode("utf-8") + ).hexdigest() + conn.execute( + """ + INSERT INTO otel_completion_events ( + fingerprint, conversation_id, event_timestamp, + input_tokens, cached_input_tokens, output_tokens, reasoning_output_tokens, + model, effort, service_tier, fast, service_tier_source, + service_tier_confidence, app_version, source_path, source_line, + match_status, matched_record_id + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'high', ?, ?, 'otel_response_completed', + 'exact', '0.143.0', '/synthetic/codex-completions.jsonl', 1, 'pending', NULL) + """, + ( + fingerprint, + conversation_id, + event_timestamp, + input_tokens, + cached_tokens, + output_tokens, + reasoning_tokens, + model, + "fast" if fast == 1 else "standard", + fast, + ), + ) + return fingerprint +``` + +- [ ] **Step 2: Run the focused tests and verify they fail** + +Run: `.venv/bin/python -m pytest tests/store/test_otel_reconciliation.py -q` + +Expected: FAIL because the reconciler and enrichment-preserving merge behavior do not exist. + +- [ ] **Step 3: Implement canonical matching, clone propagation, and conflict state** + +```python +@dataclass(frozen=True) +class OtelReconciliationResult: + matched: int = 0 + pending: int = 0 + ambiguous: int = 0 + conflicts: int = 0 + updated_usage_rows: int = 0 + + +def reconcile_otel_completions(conn: sqlite3.Connection) -> OtelReconciliationResult: + totals = _MutableReconciliationTotals() + rows = conn.execute( + "SELECT * FROM otel_completion_events " + "WHERE match_status IN ('pending', 'ambiguous', 'matched') ORDER BY source_path, source_line" + ).fetchall() + for completion in rows: + candidates = _candidate_rows(conn, completion) + group_ids = {str(row["canonical_record_id"] or row["record_id"]) for row in candidates} + if not candidates: + _set_match_state(conn, completion["fingerprint"], "pending", None) + totals.pending += 1 + elif len(group_ids) != 1: + _set_match_state(conn, completion["fingerprint"], "ambiguous", None) + totals.ambiguous += 1 + else: + _apply_to_canonical_group(conn, completion, group_ids.pop(), totals) + return totals.freeze() +``` + +`_candidate_rows()` must require exact conversation and token equality, add model/effort equality only when both sides are non-null, and omit timestamp entirely. `_apply_to_canonical_group()` must inspect all physical clones before writing: agreement is idempotent, all-null values receive the normalized tier, and any contradictory non-null value marks the staged completion `conflict` without changing usage rows. + +```python +def _candidate_rows(conn: sqlite3.Connection, completion: sqlite3.Row) -> list[sqlite3.Row]: + return conn.execute( + """ + SELECT record_id, canonical_record_id + FROM usage_events + WHERE session_id = ? + AND input_tokens = ? + AND cached_input_tokens = ? + AND output_tokens = ? + AND reasoning_output_tokens = ? + AND (? IS NULL OR model IS NULL OR lower(model) = lower(?)) + AND (? IS NULL OR effort IS NULL OR lower(effort) = lower(?)) + """, + ( + completion["conversation_id"], + completion["input_tokens"], + completion["cached_input_tokens"], + completion["output_tokens"], + completion["reasoning_output_tokens"], + completion["model"], + completion["model"], + completion["effort"], + completion["effort"], + ), + ).fetchall() + + +def _apply_to_canonical_group( + conn: sqlite3.Connection, + completion: sqlite3.Row, + canonical_id: str, + totals: _MutableReconciliationTotals, +) -> None: + clones = conn.execute( + """ + SELECT record_id, service_tier, fast, service_tier_source, service_tier_confidence + FROM usage_events + WHERE coalesce(nullif(canonical_record_id, ''), record_id) = ? + ORDER BY record_id + """, + (canonical_id,), + ).fetchall() + desired = ( + completion["service_tier"], + completion["fast"], + completion["service_tier_source"], + completion["service_tier_confidence"], + ) + tier_columns = ( + "service_tier", "fast", "service_tier_source", "service_tier_confidence" + ) + contradiction = any( + row[column] is not None and row[column] != expected + for row in clones + for column, expected in zip(tier_columns, desired, strict=True) + ) + if contradiction: + _set_match_state(conn, completion["fingerprint"], "conflict", None) + totals.conflicts += 1 + return + cursor = conn.execute( + """ + UPDATE usage_events + SET service_tier = coalesce(service_tier, ?), + fast = coalesce(fast, ?), + service_tier_source = coalesce(service_tier_source, ?), + service_tier_confidence = coalesce(service_tier_confidence, ?) + WHERE coalesce(nullif(canonical_record_id, ''), record_id) = ? + """, + (*desired, canonical_id), + ) + _set_match_state(conn, completion["fingerprint"], "matched", str(clones[0]["record_id"])) + totals.matched += 1 + totals.updated_usage_rows += max(cursor.rowcount, 0) +``` + +- [ ] **Step 4: Add enrichment-owned merge semantics to existing upserts and replacements** + +```python +OTEL_ENRICHMENT_COLUMNS = { + "service_tier", "fast", "service_tier_source", "service_tier_confidence" +} + + +def _usage_event_upsert_sql() -> str: + placeholders = ", ".join("?" for _column in EVENT_COLUMNS) + update_clause = ", ".join( + f"{column}=COALESCE(usage_events.{column}, excluded.{column})" + if column in OTEL_ENRICHMENT_COLUMNS + else f"{column}=excluded.{column}" + for column in EVENT_COLUMNS + if column != "record_id" + ) + return ( + f"INSERT INTO usage_events ({', '.join(EVENT_COLUMNS)}) " + f"VALUES ({placeholders}) " + f"ON CONFLICT(record_id) DO UPDATE SET {update_clause}" + ) +``` + +Before `_delete_usage_events_for_source_files()`, capture one consistent tier tuple per `canonical_record_id`. After `_insert_usage_event_rows()`, restore those tuples to all rows in the same canonical group. If the captured group is internally inconsistent, do not restore it; the staged completion will be re-evaluated by reconciliation. + +- [ ] **Step 5: Run reconciliation, deduplication, and source replacement tests** + +Run: `.venv/bin/python -m pytest tests/store/test_otel_reconciliation.py tests/store/test_usage_deduplication.py tests/store/test_store_large_batches.py -q` + +Expected: PASS. + +- [ ] **Step 6: Commit the correlation slice** + +```bash +git add -- src/codex_usage_tracker/store/otel_reconciliation.py src/codex_usage_tracker/store/api.py src/codex_usage_tracker/store/source_replacement.py tests/store/test_otel_reconciliation.py +git commit -m "feat: correlate OTel tiers to canonical calls" +``` + +### Task 5: Integrate OTel ingestion with refresh, rebuild, metadata, and support diagnostics + +**Files:** +- Modify: `src/codex_usage_tracker/store/refresh.py` +- Modify: `src/codex_usage_tracker/store/api.py` +- Modify: `src/codex_usage_tracker/core/api_payloads.py` +- Modify: `src/codex_usage_tracker/reports/support.py` +- Create: `tests/store/test_otel_refresh.py` +- Modify: `tests/reports/test_support.py` +- Modify: `tests/core/test_api_payloads.py` + +**Interfaces:** +- Extends: `refresh_usage_index()` with keyword `otel_dir: Path = DEFAULT_OTEL_COMPLETIONS_DIR`. +- Extends: `rebuild_usage_index()` with keyword `otel_dir: Path = DEFAULT_OTEL_COMPLETIONS_DIR`. +- Extends: `record_refresh_metadata()` with keyword `otel_diagnostics: dict[str, int] | None = None`. +- Keeps: existing refresh-result JSON fields and adds non-zero `otel_*` keys under `parser_diagnostics`. + +- [ ] **Step 1: Write failing refresh-order, no-op, rebuild, and support privacy tests** + +```python +def test_refresh_ingests_session_rows_before_reconciling_otel(tmp_path: Path) -> None: + codex_home = write_usage_session(tmp_path, conversation_id="conversation-a", tokens=(100, 40, 30, 10)) + otel_dir = tmp_path / "otel" + write_lines( + otel_dir / "codex-completions.jsonl", + [synthetic_otlp_line(attributes=completion_attributes( + conversation_id="conversation-a", tokens=(100, 40, 30, 10), service_tier="priority" + ))], + ) + result = refresh_usage_index(codex_home=codex_home, db_path=tmp_path / "usage.sqlite3", otel_dir=otel_dir) + with connect(tmp_path / "usage.sqlite3") as conn: + record_id = str(conn.execute("SELECT record_id FROM usage_events").fetchone()[0]) + row = query_usage_record(db_path=tmp_path / "usage.sqlite3", record_id=record_id) + assert row is not None + assert row["service_tier"] == "fast" + assert result.parser_diagnostics["otel_matched"] == 1 + + +def test_absent_otel_directory_is_a_supported_noop(tmp_path: Path) -> None: + result = refresh_usage_index( + codex_home=tmp_path / "codex", db_path=tmp_path / "usage.sqlite3", otel_dir=tmp_path / "missing" + ) + assert result.parser_diagnostics.get("otel_files_scanned", 0) == 0 + + +def test_refresh_records_protocol_confirmed_standard(tmp_path: Path) -> None: + codex_home = write_usage_session(tmp_path, "conversation-standard", (100, 40, 30, 10)) + otel_dir = tmp_path / "otel" + write_lines( + otel_dir / "codex-completions.jsonl", + [synthetic_otlp_line(attributes=completion_attributes( + conversation_id="conversation-standard", + tokens=(100, 40, 30, 10), + service_tier=None, + app_version="0.143.0", + ))], + ) + db_path = tmp_path / "usage.sqlite3" + refresh_usage_index(codex_home=codex_home, db_path=db_path, otel_dir=otel_dir) + with connect(db_path) as conn: + row = conn.execute( + "SELECT service_tier, fast, service_tier_confidence FROM usage_events" + ).fetchone() + assert tuple(row) == ("standard", 0, "protocol") + + +def test_refresh_keeps_older_omitted_tier_unknown(tmp_path: Path) -> None: + codex_home = write_usage_session(tmp_path, "conversation-legacy", (100, 40, 30, 10)) + otel_dir = tmp_path / "otel" + write_lines( + otel_dir / "codex-completions.jsonl", + [synthetic_otlp_line(attributes=completion_attributes( + conversation_id="conversation-legacy", + tokens=(100, 40, 30, 10), + service_tier=None, + app_version="0.142.9", + ))], + ) + db_path = tmp_path / "usage.sqlite3" + refresh_usage_index(codex_home=codex_home, db_path=db_path, otel_dir=otel_dir) + with connect(db_path) as conn: + row = conn.execute("SELECT service_tier, fast FROM usage_events").fetchone() + assert tuple(row) == (None, None) + + +def test_otel_before_jsonl_matches_on_a_later_refresh(tmp_path: Path) -> None: + otel_dir = tmp_path / "otel" + write_lines( + otel_dir / "codex-completions.jsonl", + [synthetic_otlp_line(attributes=completion_attributes( + conversation_id="conversation-a", tokens=(100, 40, 30, 10) + ))], + ) + db_path = tmp_path / "usage.sqlite3" + refresh_usage_index(codex_home=tmp_path / "empty", db_path=db_path, otel_dir=otel_dir) + codex_home = write_usage_session(tmp_path, "conversation-a", (100, 40, 30, 10)) + refresh_usage_index(codex_home=codex_home, db_path=db_path, otel_dir=otel_dir) + with connect(db_path) as conn: + assert conn.execute("SELECT service_tier FROM usage_events").fetchone()[0] == "fast" + + +def test_rebuild_retains_staging_resets_match_pointer_and_reapplies_tier(tmp_path: Path) -> None: + codex_home = write_usage_session(tmp_path, "conversation-a", (100, 40, 30, 10)) + otel_dir = write_otel_directory(tmp_path, "conversation-a", (100, 40, 30, 10)) + db_path = tmp_path / "usage.sqlite3" + refresh_usage_index(codex_home=codex_home, db_path=db_path, otel_dir=otel_dir) + rebuild_usage_index(codex_home=codex_home, db_path=db_path, otel_dir=otel_dir) + with connect(db_path) as conn: + assert conn.execute("SELECT COUNT(*) FROM otel_completion_events").fetchone()[0] == 1 + assert conn.execute("SELECT service_tier FROM usage_events").fetchone()[0] == "fast" + + +def test_support_bundle_exposes_counts_without_otel_identifiers(tmp_path: Path) -> None: + payload = support_bundle_payload(db_path=tmp_path / "usage.sqlite3", codex_home=tmp_path / "codex") + encoded = json.dumps(payload, sort_keys=True) + assert payload["otel"]["completion_directory_exists"] in {True, False} + assert "conversation-a" not in encoded + assert "fingerprint" not in encoded + assert "source_path" not in encoded +``` + +Extend `tests/otel_helpers.py` with concrete session and directory factories: + +```python +def write_usage_session( + tmp_path: Path, + conversation_id: str, + tokens: tuple[int, int, int, int], +) -> Path: + input_tokens, cached_tokens, output_tokens, reasoning_tokens = tokens + total_tokens = input_tokens + output_tokens + codex_home = tmp_path / "codex" + log_path = codex_home / "sessions" / "2026" / "07" / "16" / "synthetic.jsonl" + rows = [ + { + "timestamp": "2026-07-16T00:00:00.000Z", + "type": "session_meta", + "payload": {"id": conversation_id}, + }, + { + "timestamp": "2026-07-16T00:00:01.000Z", + "type": "event_msg", + "payload": { + "type": "token_count", + "info": { + "total_token_usage": { + "input_tokens": input_tokens, + "cached_input_tokens": cached_tokens, + "output_tokens": output_tokens, + "reasoning_output_tokens": reasoning_tokens, + "total_tokens": total_tokens, + }, + "last_token_usage": { + "input_tokens": input_tokens, + "cached_input_tokens": cached_tokens, + "output_tokens": output_tokens, + "reasoning_output_tokens": reasoning_tokens, + "total_tokens": total_tokens, + }, + "model_context_window": 258_400, + }, + }, + }, + ] + log_path.parent.mkdir(parents=True, exist_ok=True) + log_path.write_text("".join(json.dumps(row) + "\n" for row in rows), encoding="utf-8") + return codex_home + + +def write_otel_directory( + tmp_path: Path, + conversation_id: str, + tokens: tuple[int, int, int, int], +) -> Path: + directory = tmp_path / "otel" + write_lines( + directory / "codex-completions.jsonl", + [synthetic_otlp_line(attributes=completion_attributes( + conversation_id=conversation_id, + tokens=tokens, + service_tier="priority", + ))], + ) + return directory +``` + +- [ ] **Step 2: Run focused refresh and support tests and verify failures** + +Run: `.venv/bin/python -m pytest tests/store/test_otel_refresh.py tests/reports/test_support.py tests/core/test_api_payloads.py -q` + +Expected: FAIL because refresh does not run the OTel phases or expose bounded diagnostics. + +- [ ] **Step 3: Add the refresh phase after session persistence** + +```python +def _refresh_otel_completions(*, db_path: Path, otel_dir: Path) -> dict[str, int]: + with connect(db_path) as conn: + init_db(conn) + ingest = ingest_otel_completion_files(conn, otel_dir) + reconciled = reconcile_otel_completions(conn) + if reconciled.updated_usage_rows: + touch_compression_revisions(conn, {"calls", "threads"}) + return { + "otel_files_scanned": ingest.files_scanned, + "otel_imported": ingest.imported, + "otel_duplicates": ingest.duplicates, + "otel_matched": reconciled.matched, + "otel_pending": reconciled.pending, + "otel_ambiguous": reconciled.ambiguous, + "otel_conflicts": reconciled.conflicts, + **ingest.diagnostics, + } +``` + +Define the persistence allowlist once and use it in `record_refresh_metadata()`: + +```python +OTEL_REFRESH_COUNTER_KEYS = ( + "otel_files_scanned", + "otel_imported", + "otel_duplicates", + "otel_matched", + "otel_pending", + "otel_ambiguous", + "otel_conflicts", + *OTEL_DIAGNOSTIC_KEYS, +) +``` + +Call this after `write_refresh_stream()` and before `_finalize_refresh_result()`. Merge only non-zero OTel counters into `RefreshResult.parser_diagnostics`, and store every allowlisted OTel counter under `refresh_meta` with its existing `otel_` name. Add an `otel` progress phase without changing existing phase meanings. + +- [ ] **Step 4: Preserve staging during rebuild and add support presence** + +In `rebuild_usage_index()`, call `reset_otel_completion_matches(conn)` before deleting canonical usage tables and deliberately omit both OTel sidecar tables from the deletion list. Pass `otel_dir` through to the final refresh. In support payloads, add only: + +```python +"otel": { + "completion_directory_exists": DEFAULT_OTEL_COMPLETIONS_DIR.is_dir(), + "refresh_counts": {key: value for key, value in refresh.items() if key.startswith("otel_")}, +} +``` + +Do not include the directory path, source paths, fingerprints, record IDs, conversations, timestamps, or token tuples. + +- [ ] **Step 5: Run refresh, payload, support, and callback tests** + +Run: `.venv/bin/python -m pytest tests/store/test_otel_refresh.py tests/store/test_refresh_callbacks.py tests/core/test_api_payloads.py tests/reports/test_support.py -q` + +Expected: PASS. + +- [ ] **Step 6: Commit the refresh integration slice** + +```bash +git add -- src/codex_usage_tracker/store/refresh.py src/codex_usage_tracker/store/api.py src/codex_usage_tracker/core/api_payloads.py src/codex_usage_tracker/reports/support.py tests/store/test_otel_refresh.py tests/reports/test_support.py tests/core/test_api_payloads.py +git commit -m "feat: reconcile OTel tiers during refresh" +``` + +### Task 6: Apply confirmed Fast credit multipliers without changing USD estimates + +**Files:** +- Create: `src/codex_usage_tracker/pricing/fast_tier.py` +- Modify: `src/codex_usage_tracker/pricing/allowance_usage.py` +- Modify: `src/codex_usage_tracker/pricing/allowance.py` +- Modify: `src/codex_usage_tracker/usage_drain/types.py` +- Modify: `src/codex_usage_tracker/usage_drain/spans.py` +- Modify: `tests/pricing/test_allowance.py` +- Modify: `tests/pricing/test_pricing.py` +- Modify: `tests/usage_drain/test_usage_drain_model.py` + +**Interfaces:** +- Produces: `DOCUMENTED_FAST_CREDIT_MULTIPLIERS` and `documented_fast_credit_multiplier(model)`. +- Produces: `usage_credit_multiplier`, `usage_credit_multiplier_source`, and `standard_usage_credits` annotations. +- Preserves: `estimated_cost_usd` and usage-drain Standard-baseline modeling. + +- [ ] **Step 1: Write failing multiplier, unknown, Standard, USD, and usage-drain compatibility tests** + +```python +@pytest.mark.parametrize( + ("model", "multiplier"), + [("gpt-5.6", 2.5), ("gpt-5.6-sol", 2.5), ("gpt-5.5", 2.5), ("gpt-5.4", 2.0)], +) +def test_confirmed_fast_multiplies_standard_credit_estimate(model: str, multiplier: float) -> None: + row = credit_row(model=model, fast=1, service_tier="fast") + annotated = annotate_rows_with_allowance([row], synthetic_allowance_config())[0] + assert annotated["usage_credits"] == pytest.approx(annotated["standard_usage_credits"] * multiplier) + assert annotated["usage_credit_multiplier"] == multiplier + assert annotated["usage_credit_multiplier_source"] == "otel_response_completed" + + +@pytest.mark.parametrize("fast", [0, None]) +def test_standard_and_unknown_rows_keep_multiplier_one(fast: int | None) -> None: + row = credit_row(model="gpt-5.6", fast=fast, service_tier="standard" if fast == 0 else None) + annotated = annotate_rows_with_allowance([row], synthetic_allowance_config())[0] + assert annotated["usage_credits"] == annotated["standard_usage_credits"] + assert annotated["usage_credit_multiplier"] == 1.0 + + +def test_confirmed_fast_unknown_model_does_not_invent_multiplier() -> None: + row = credit_row(model="synthetic-unknown", fast=1, service_tier="fast") + annotated = annotate_rows_with_allowance([row], synthetic_allowance_config())[0] + assert annotated["usage_credit_multiplier"] == 1.0 + assert annotated["usage_credit_multiplier_source"] == "no_documented_fast_multiplier" + + +def test_service_tier_does_not_change_estimated_cost_usd() -> None: + pricing = synthetic_pricing_config() + row = credit_row(model="gpt-5.6", fast=0, service_tier="standard") + standard = estimate_cost_usd(row, pricing) + fast = estimate_cost_usd({**row, "fast": 1, "service_tier": "fast"}, pricing) + assert fast == standard + + +def test_usage_drain_uses_standard_usage_credits_after_fast_annotation() -> None: + row = annotate_rows_with_allowance( + [credit_row(model="gpt-5.6", fast=1, service_tier="fast")], + synthetic_allowance_config(), + )[0] + span = span_from_single_row_for_test(row) + assert span.standard_usage_credits == row["standard_usage_credits"] +``` + +Use these local synthetic helpers in the focused tests: + +```python +def credit_row(*, model: str, fast: int | None, service_tier: str | None) -> dict[str, object]: + return { + "model": model, + "input_tokens": 100, + "cached_input_tokens": 20, + "uncached_input_tokens": 80, + "output_tokens": 10, + "total_tokens": 110, + "fast": fast, + "service_tier": service_tier, + "service_tier_source": "otel_response_completed" if fast is not None else None, + "service_tier_confidence": "exact" if fast is not None else None, + } + + +def synthetic_allowance_config() -> UsageAllowanceConfig: + rates = {"input_per_million": 10.0, "cached_input_per_million": 1.0, "output_per_million": 50.0} + models = ("gpt-5.6", "gpt-5.6-sol", "gpt-5.5", "gpt-5.4", "synthetic-unknown") + return UsageAllowanceConfig( + path=Path("/synthetic/allowance.json"), + rate_card_path=Path("/synthetic/rate-card.json"), + credit_rates={model: dict(rates) for model in models}, + aliases={}, + rate_metadata={model: {} for model in models}, + alias_metadata={}, + windows=[], + loaded=True, + rate_card_loaded=True, + source={"name": "Synthetic credit rates"}, + ) + + +def synthetic_pricing_config() -> PricingConfig: + return PricingConfig( + path=Path("/synthetic/pricing.json"), + models={"gpt-5.6": { + "input_per_million": 10.0, + "cached_input_per_million": 1.0, + "output_per_million": 50.0, + }}, + loaded=True, + ) + + +def span_from_single_row_for_test(row: dict[str, object]) -> UsageDeltaSpan: + return _span_from_rows([row], baseline_percent=0.0, end_used_percent=1.0, proxies={}) +``` + +- [ ] **Step 2: Run focused pricing tests and verify failures** + +Run: `.venv/bin/python -m pytest tests/pricing/test_allowance.py tests/pricing/test_pricing.py tests/usage_drain/test_usage_drain_model.py -q` + +Expected: FAIL because credit annotation has no exact-tier multiplier policy. + +- [ ] **Step 3: Implement shared documented model-family multipliers** + +```python +DOCUMENTED_FAST_CREDIT_MULTIPLIERS = { + "gpt-5.6": 2.5, + "gpt-5.5": 2.5, + "gpt-5.4": 2.0, +} + + +def documented_fast_credit_multiplier(model: object) -> float | None: + normalized = str(model or "").strip().lower() + for family, multiplier in DOCUMENTED_FAST_CREDIT_MULTIPLIERS.items(): + if normalized == family or normalized.startswith(f"{family}-") or normalized.startswith(f"{family} "): + return multiplier + return None + + +def credit_multiplier_for_row(row: Mapping[str, object]) -> tuple[float, str]: + if row.get("fast") != 1: + fallback = "confirmed_standard" if row.get("fast") == 0 else "tier_unknown" + return 1.0, str(row.get("service_tier_source") or fallback) + multiplier = documented_fast_credit_multiplier(row.get("model")) + if multiplier is None: + return 1.0, "no_documented_fast_multiplier" + return multiplier, str(row.get("service_tier_source") or "confirmed_fast") +``` + +Move the existing usage-drain constant/function to this pricing module and re-export them from `usage_drain/types.py` to preserve imports. + +- [ ] **Step 4: Split Standard and effective credit estimates** + +```python +standard_credits = estimate_standard_usage_credits(copy, rates) +multiplier, multiplier_source = credit_multiplier_for_row(copy) +copy.update( + usage_credits=standard_credits * multiplier, + standard_usage_credits=standard_credits, + usage_credit_multiplier=multiplier, + usage_credit_multiplier_source=multiplier_source, +) +``` + +Rename the existing calculation body to `estimate_standard_usage_credits()` and keep `estimate_usage_credits()` as the effective wrapper for compatibility. Add `standard_usage_credits`, `usage_credit_multiplier`, and `usage_credit_multiplier_source` in both priced and unpriced annotation branches; unpriced rows keep `usage_credits = None` and `standard_usage_credits = None`. In usage-drain span construction, read `standard_usage_credits` with a fallback to `usage_credits` for older injected rows. Do not modify `pricing/costing.py`. + +- [ ] **Step 5: Run pricing and usage-drain tests** + +Run: `.venv/bin/python -m pytest tests/pricing tests/usage_drain/test_usage_drain_model.py tests/usage_drain/test_usage_drain_reports.py -q` + +Expected: PASS. + +- [ ] **Step 6: Commit the credit-accounting slice** + +```bash +git add -- src/codex_usage_tracker/pricing/fast_tier.py src/codex_usage_tracker/pricing/allowance_usage.py src/codex_usage_tracker/pricing/allowance.py src/codex_usage_tracker/usage_drain/types.py src/codex_usage_tracker/usage_drain/spans.py tests/pricing/test_allowance.py tests/pricing/test_pricing.py tests/usage_drain/test_usage_drain_model.py +git commit -m "feat: price confirmed Fast credit usage" +``` + +### Task 7: Expose exact tiers in calls, exports, and the dashboard + +**Files:** +- Modify: `tests/store/test_content_query_exports.py` +- Modify: `tests/dashboard/test_dashboard_payload.py` +- Modify: `tests/server/test_server_call_detail.py` +- Modify: `frontend/dashboard/src/api/types.ts` +- Modify: `frontend/dashboard/src/api/client.ts` +- Modify: `frontend/dashboard/src/api/client.test.ts` +- Modify: `frontend/dashboard/src/test-fixtures/dashboardFixture.ts` +- Create: `frontend/dashboard/src/features/calls/serviceTier.ts` +- Create: `frontend/dashboard/src/features/calls/serviceTier.test.ts` +- Modify: `frontend/dashboard/src/features/shared/tables.tsx` +- Modify: `frontend/dashboard/src/features/shared/tables.test.ts` +- Modify: `frontend/dashboard/src/features/calls/CallInspector.tsx` +- Modify: `frontend/dashboard/src/features/call-investigator/CallInvestigatorPage.tsx` +- Regenerate: `src/codex_usage_tracker/plugin_data/dashboard/react/` + +**Interfaces:** +- Consumes: additive backend row fields already selected by `usage_events.*` and exported through schema-driven CSV. +- Produces: nullable exact `fast`, `serviceTier`, `serviceTierSource`, `serviceTierConfidence`, and separate `fastProxyCandidate` in `CallRow`. +- Produces: Calls table/CSV tier labels and exact-first detail wording. + +- [ ] **Step 1: Write failing backend export and frontend decoding tests** + +```python +def test_csv_export_includes_additive_service_tier_fields(tmp_path: Path) -> None: + db_path = tmp_path / "usage.sqlite3" + upsert_usage_events( + [synthetic_usage_event("record-a", "conversation-a", (100, 40, 30, 10), fast=1)], + db_path=db_path, + ) + output_path = tmp_path / "usage.csv" + export_usage_csv(output_path, db_path=db_path) + header = next(csv.reader(output_path.read_text(encoding="utf-8").splitlines())) + assert [name for name in header if name.startswith("service_tier") or name == "fast"] == [ + "service_tier", "fast", "service_tier_source", "service_tier_confidence" + ] + + +def test_dashboard_payload_keeps_tier_fields_aggregate_only(tmp_path: Path) -> None: + db_path = tmp_path / "usage.sqlite3" + upsert_usage_events( + [synthetic_usage_event("record-a", "conversation-a", (100, 40, 30, 10), fast=1)], + db_path=db_path, + ) + row = dashboard_payload(db_path=db_path)["rows"][0] + assert row["service_tier"] == "fast" + assert row["fast"] == 1 + assert "otel_source_path" not in row + + +def test_call_detail_exposes_tier_without_sidecar_identity(tmp_path: Path) -> None: + db_path = tmp_path / "usage.sqlite3" + upsert_usage_events( + [synthetic_usage_event("record-a", "conversation-a", (100, 40, 30, 10), fast=1)], + db_path=db_path, + ) + payload = call_detail_payload( + "record_id=record-a", db_path=db_path, annotate_rows=lambda rows: rows + ) + record = payload["record"] + assert record["service_tier"] == "fast" + assert record["service_tier_confidence"] == "exact" + assert "source_path" not in record +``` + +```typescript +it('keeps exact tier separate from throughput proxy', () => { + const call = usageRowToCall({ service_tier: 'standard', fast: 0, service_tier_confidence: 'protocol', duration_seconds: 1, total_tokens: 9000 }, 0); + expect(call.fast).toBe(false); + expect(call.fastProxyCandidate).toBe(true); + expect(serviceTierDetail(call)).toBe('confirmed Standard · protocol'); +}); +``` + +- [ ] **Step 2: Run backend and frontend focused tests and verify failures** + +Run: `.venv/bin/python -m pytest tests/store/test_content_query_exports.py tests/dashboard/test_dashboard_payload.py tests/server/test_server_call_detail.py -q` + +Run: `npm run dashboard:test -- --run frontend/dashboard/src/api/client.test.ts frontend/dashboard/src/features/calls/serviceTier.test.ts frontend/dashboard/src/features/shared/tables.test.ts` + +Expected: FAIL because the dashboard model does not decode or label exact service tiers. + +- [ ] **Step 3: Decode exact tier fields and retain the historical proxy separately** + +```typescript +// UsageRow +service_tier?: string | null; +fast?: number | boolean | null; +service_tier_source?: string | null; +service_tier_confidence?: string | null; +standard_usage_credits?: number | null; +usage_credit_multiplier?: number | null; +usage_credit_multiplier_source?: string | null; + +// CallRow +serviceTier: string; +fast: boolean | null; +serviceTierSource: string; +serviceTierConfidence: string; +fastProxyCandidate: boolean; +usageCreditMultiplier: number; +usageCreditMultiplierSource: string; + +// usageRowToCall() +const rawFast = row.fast; +const exactFast = rawFast === true || rawFast === 1 + ? true + : rawFast === false || rawFast === 0 + ? false + : null; + +serviceTier: String(row.service_tier ?? ''), +fast: exactFast, +serviceTierSource: String(row.service_tier_source ?? ''), +serviceTierConfidence: String(row.service_tier_confidence ?? ''), +fastProxyCandidate: durationSeconds > 0 && totalTokens / Math.max(durationSeconds, 1) > 4_000, +usageCreditMultiplier: Number(row.usage_credit_multiplier ?? 1), +usageCreditMultiplierSource: String(row.usage_credit_multiplier_source ?? ''), +``` + +In `usageRowToCall()`, decode `fast` as `true`, `false`, or `null`; compute the current throughput heuristic into `fastProxyCandidate`; never let the proxy overwrite exact `fast`. Add the required defaults to `test-fixtures/dashboardFixture.ts` so hand-built `CallRow` fixtures remain type-complete. + +- [ ] **Step 4: Add exact-first labels, Calls column, details, and CSV fields** + +```typescript +export function serviceTierLabel(call: CallRow): 'Fast' | 'Standard' | 'Unknown' { + if (call.fast === true) return 'Fast'; + if (call.fast === false) return 'Standard'; + return 'Unknown'; +} + +export function serviceTierDetail(call: CallRow): string { + if (call.fast !== null) { + return `confirmed ${serviceTierLabel(call)} · ${call.serviceTierConfidence || 'exact'}`; + } + return call.fastProxyCandidate ? 'tier unknown · Fast proxy candidate' : 'tier unknown · normal throughput proxy'; +} +``` + +Add a `Service Tier` Calls column, add the four persisted tier fields plus credit multiplier fields to `callCsvColumns`, and replace both existing duration details with `serviceTierDetail(call)`. Keep duration itself unchanged. + +- [ ] **Step 5: Run focused frontend tests, typecheck, and regenerate bundled assets** + +Run: `npm run dashboard:test -- --run frontend/dashboard/src/api/client.test.ts frontend/dashboard/src/features/calls/serviceTier.test.ts frontend/dashboard/src/features/shared/tables.test.ts` + +Run: `npm run dashboard:typecheck` + +Run: `npm run dashboard:build` + +Expected: PASS and deterministic assets regenerated under `src/codex_usage_tracker/plugin_data/dashboard/react/`. + +- [ ] **Step 6: Run backend payload/export privacy tests** + +Run: `.venv/bin/python -m pytest tests/store/test_content_query_exports.py tests/dashboard/test_dashboard_payload.py tests/dashboard/test_dashboard_payload_privacy.py tests/server/test_server_call_detail.py tests/server/test_server_dashboard_shell.py -q` + +Expected: PASS. + +- [ ] **Step 7: Commit the public-surface slice** + +```bash +git add -- tests/store/test_content_query_exports.py tests/dashboard/test_dashboard_payload.py tests/server/test_server_call_detail.py frontend/dashboard/src/api/types.ts frontend/dashboard/src/api/client.ts frontend/dashboard/src/api/client.test.ts frontend/dashboard/src/test-fixtures/dashboardFixture.ts frontend/dashboard/src/features/calls/serviceTier.ts frontend/dashboard/src/features/calls/serviceTier.test.ts frontend/dashboard/src/features/shared/tables.tsx frontend/dashboard/src/features/shared/tables.test.ts frontend/dashboard/src/features/calls/CallInspector.tsx frontend/dashboard/src/features/call-investigator/CallInvestigatorPage.tsx src/codex_usage_tracker/plugin_data/dashboard/react +git commit -m "feat: show exact Fast tiers in Calls" +``` + +### Task 8: Document the behavior and run the complete verification gate + +**Files:** +- Modify: `docs/architecture.md` +- Modify: `docs/database-schema.md` +- Modify: `docs/pricing-and-credits.md` +- Modify: `docs/privacy.md` +- Modify: `docs/dashboard-guide.md` +- Modify: `docs/cli-json-schemas.md` +- Modify: `CHANGELOG.md` + +**Interfaces:** +- Documents: exact-versus-proxy behavior, the v30 tables/columns, cursor/rebuild semantics, credit multiplier provenance, unchanged USD estimates, and aggregate-only privacy guarantees. +- Verifies: Python, dashboard, packaging, schema, privacy, generated assets, and release readiness. + +- [ ] **Step 1: Update user and maintainer documentation with exact semantics** + +Document these concrete points in the named files: + +```text +The tracker reads only local codex-completions*.jsonl OTLP exporter files. +Fast/Standard is exact only when service_tier is explicit or Standard is established by Codex >= 0.143.0 omission semantics. +Older unmatched history remains Unknown; latency and reasoning effort are not proof of Fast. +Confirmed Fast changes ChatGPT credit estimates by the documented model multiplier but never changes USD token estimates. +Raw OTLP bodies and arbitrary attributes are neither persisted nor exported. +``` + +Add an Unreleased changelog entry covering local OTel ingestion, exact tier labels, conservative correlation, and credit-accounting changes. + +- [ ] **Step 2: Run focused Python and frontend suites** + +Run: `.venv/bin/python -m pytest tests/parser/test_otel_parser.py tests/store/test_otel_schema.py tests/store/test_otel_ingest.py tests/store/test_otel_reconciliation.py tests/store/test_otel_refresh.py tests/pricing/test_allowance.py tests/reports/test_support.py tests/dashboard/test_dashboard_payload.py -q` + +Run: `npm run dashboard:test -- --run frontend/dashboard/src/api/client.test.ts frontend/dashboard/src/features/calls/serviceTier.test.ts frontend/dashboard/src/features/shared/tables.test.ts` + +Expected: PASS. + +- [ ] **Step 3: Run the complete Python and dashboard gates** + +Run: `.venv/bin/python -m ruff check .` + +Run: `.venv/bin/python -m mypy` + +Run: `.venv/bin/python -m pytest` + +Run: `.venv/bin/python -m pytest --cov=codex_usage_tracker --cov-report=term-missing` + +Run: `.venv/bin/python -m compileall src` + +Run: `npm run dashboard:verify` + +Run: `for file in src/codex_usage_tracker/plugin_data/dashboard/dashboard*.js; do node --check "$file"; done` + +Run: `.venv/bin/python scripts/check_release.py` + +Run: `git diff --check` + +Expected: every command exits 0. + +- [ ] **Step 4: Build and inspect release artifacts** + +Run: `.venv/bin/python -m build` + +Run: `.venv/bin/python -m twine check dist/*` + +Run: `.venv/bin/python scripts/check_release.py --dist` + +Expected: wheel and sdist build successfully, Twine reports both valid, and bundled parser/dashboard/skill assets pass release inspection. + +- [ ] **Step 5: Perform the final privacy and Git review** + +Run: `git status --short --branch` + +Run: `git diff --stat main..HEAD` + +Run: `git diff main..HEAD -- . ':(exclude)src/codex_usage_tracker/plugin_data/dashboard/react/assets/*.js'` + +Run: `rg -n "conversation.id|secret.attribute|private body|/Users/|source_path" tests docs src/codex_usage_tracker/plugin_data --glob '!docs/superpowers/**'` + +Expected: only synthetic identifiers and documented schema field names appear; no real log content, local OTel paths, secrets, or private records are staged. + +- [ ] **Step 6: Commit documentation and verification evidence** + +```bash +git add -- docs/architecture.md docs/database-schema.md docs/pricing-and-credits.md docs/privacy.md docs/dashboard-guide.md docs/cli-json-schemas.md CHANGELOG.md +git commit -m "docs: explain exact Fast usage tracking" +``` diff --git a/docs/superpowers/plans/2026-07-16-service-tier-billing-readiness.md b/docs/superpowers/plans/2026-07-16-service-tier-billing-readiness.md new file mode 100644 index 00000000..1e0a66fa --- /dev/null +++ b/docs/superpowers/plans/2026-07-16-service-tier-billing-readiness.md @@ -0,0 +1,577 @@ +# Service Tier Billing Readiness Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make OTel service-tier enrichment release-ready by preserving the exact tier, selecting API prices per call, source-stamping ChatGPT Fast multipliers, correcting presentation, and closing reset and rotation safety gaps. + +**Architecture:** Keep `service_tier` as exact response evidence and `fast` as a derived speed classification. Pricing-v2 caches every published API tier while retaining the v1 `models` projection for compatibility; cost annotations expose exact-tier and comparison scenarios, while credit annotations use a source-stamped rate-card multiplier. OTel rebuild retention remains distinct from destructive database reset, and cursor identity comes from the open descriptor. + +**Tech Stack:** Python 3.10+, SQLite, pytest, TypeScript/React, Vitest, local JSON pricing/rate-card files. + +## Global Constraints + +- Never infer ChatGPT-versus-API authentication from OTel `service_tier`. +- Never apply ChatGPT Fast multipliers to API USD estimates. +- Preserve pricing-v1 files and stable existing output keys. +- Keep all new persisted and fixture data aggregate-only and synthetic. +- Write a failing test and observe the expected failure before each production change. +- Do not stage `.idea/`, `uv.lock`, local databases, OTel logs, or private data. + +--- + +### Task 1: Preserve Exact Response Tiers + +**Files:** +- Modify: `tests/parser/test_otel_parser.py` +- Modify: `src/codex_usage_tracker/parser/otel.py` +- Modify: `tests/store/test_otel_reconciliation.py` + +**Interfaces:** +- Produces: `OtelCompletion.service_tier` containing normalized upstream values such as `priority`, `fast`, `default`, `standard`, or `flex`. +- Produces: `OtelCompletion.fast` as the independent `1 | 0 | None` speed classification. + +- [ ] **Step 1: Write failing parser expectations** + +```python +@pytest.mark.parametrize( + ("raw_tier", "normalized_tier", "fast"), + [ + ("priority", "priority", 1), + ("fast", "fast", 1), + ("default", "default", 0), + ("standard", "standard", 0), + ("flex", "flex", 0), + ], +) +def test_explicit_tier_names_are_preserved(raw_tier, normalized_tier, fast): + completion = parse_otlp_json_line( + synthetic_otlp_line(attributes=completion_attributes(service_tier=raw_tier)) + ).completions[0] + assert (completion.service_tier, completion.fast) == (normalized_tier, fast) +``` + +- [ ] **Step 2: Run the parser test and verify RED** + +Run: `.venv/bin/python -m pytest tests/parser/test_otel_parser.py -q` +Expected: FAIL because `priority` currently becomes `fast` and `default` becomes `standard`. + +- [ ] **Step 3: Implement exact-tier normalization** + +```python +normalized = raw_tier.lower() +if normalized in {"priority", "fast"}: + return normalized, 1, "otel_response_completed", "exact" +return normalized, 0, "otel_response_completed", "exact" +``` + +Keep versioned omission as `service_tier="standard"`, `fast=0`, confidence `protocol`. + +- [ ] **Step 4: Update reconciliation fixtures and verify GREEN** + +Run: `.venv/bin/python -m pytest tests/parser/test_otel_parser.py tests/store/test_otel_reconciliation.py -q` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add -- tests/parser/test_otel_parser.py tests/store/test_otel_reconciliation.py src/codex_usage_tracker/parser/otel.py +git commit -m "fix: preserve exact OTel service tiers" +``` + +### Task 2: Cache All API Pricing Tiers Backward-Compatibly + +**Files:** +- Modify: `tests/pricing/test_pricing.py` +- Modify: `src/codex_usage_tracker/pricing/config.py` +- Modify: `src/codex_usage_tracker/pricing/openai.py` +- Modify: `src/codex_usage_tracker/pricing/api.py` + +**Interfaces:** +- Produces: `PRICING_SCHEMA = "codex-usage-tracker-pricing-v2"`. +- Produces: `PricingConfig.api_service_tiers: dict[str, dict[str, dict[str, float]]]`. +- Produces: `PricingConfig.billing_basis: str` with `unknown`, `chatgpt_credits`, or `api_tokens`. +- Produces: `PricingConfig.rates_for(model, service_tier=None)` and `pricing_tier_for(service_tier)`. +- Preserves: `PricingConfig.models`, `--tier`, `_source.tier`, aliases, estimates, pinning, and v1 loading. + +- [ ] **Step 1: Write failing v1/v2 config tests** + +```python +def test_pricing_v2_selects_rates_by_service_tier(tmp_path: Path) -> None: + path = tmp_path / "pricing.json" + path.write_text(json.dumps({ + "_schema": "codex-usage-tracker-pricing-v2", + "billing_basis": "api_tokens", + "models": {"gpt-5.6": { + "input_per_million": 5.0, + "cached_input_per_million": 0.5, + "output_per_million": 30.0, + }}, + "api_service_tiers": { + "standard": {"gpt-5.6": { + "input_per_million": 5.0, + "cached_input_per_million": 0.5, + "output_per_million": 30.0, + }}, + "priority": {"gpt-5.6": { + "input_per_million": 10.0, + "cached_input_per_million": 1.0, + "output_per_million": 60.0, + }}, + }, + }), encoding="utf-8") + config = load_pricing_config(path) + assert config.billing_basis == "api_tokens" + assert config.rates_for("gpt-5.6", service_tier="priority")["input_per_million"] == 10 + +def test_pricing_v1_ignores_row_tier_for_compatibility(tmp_path: Path) -> None: + config = load_v1_pricing(tmp_path, input_rate=5) + assert config.rates_for("gpt-5.6", service_tier="priority")["input_per_million"] == 5 +``` + +- [ ] **Step 2: Run config tests and verify RED** + +Run: `.venv/bin/python -m pytest tests/pricing/test_pricing.py -q` +Expected: FAIL because v2 tier maps and billing basis are unsupported. + +- [ ] **Step 3: Implement v2 loading and canonical tier selection** + +```python +def normalize_api_service_tier(value: object) -> str | None: + normalized = str(value or "").strip().lower() + return { + "fast": "priority", + "priority": "priority", + "default": "standard", + "standard": "standard", + "batch": "batch", + "flex": "flex", + }.get(normalized) + +def rates_for(self, model: object, service_tier: object | None = None): + tier = normalize_api_service_tier(service_tier) + if tier and tier in self.api_service_tiers: + return _rates_for_models(self.api_service_tiers[tier], self.aliases, model) + return _rates_for_models(self.models, self.aliases, model) +``` + +Invalid `billing_basis` makes the config invalid rather than guessing. + +- [ ] **Step 4: Write failing all-tier updater test** + +```python +def test_update_pricing_caches_every_api_tier(tmp_path: Path) -> None: + result = update_pricing_from_openai_docs( + tmp_path / "pricing.json", tier="batch", fetch_text=lambda _: ALL_TIER_FIXTURE + ) + raw = json.loads(result.path.read_text()) + assert set(raw["api_service_tiers"]) == {"standard", "batch", "flex", "priority"} + official_batch_models = { + model: rates + for model, rates in raw["models"].items() + if rates.get("estimated") is not True + } + assert official_batch_models == raw["api_service_tiers"]["batch"] + assert raw["_source"]["tier"] == "batch" +``` + +- [ ] **Step 5: Implement atomic all-tier parsing and cache writing** + +```python +def parse_all_openai_pricing_tiers(markdown: str): + return { + tier: parse_openai_pricing_markdown(markdown, tier=tier) + for tier in VALID_PRICING_TIERS + } +``` + +Fetch once, parse all four tiers before writing, keep `models` as the selected projection, add estimates only to that projection, and preserve the existing local `billing_basis` when refreshing. + +- [ ] **Step 6: Verify GREEN** + +Run: `.venv/bin/python -m pytest tests/pricing/test_pricing.py tests/cli/test_cli_module_entrypoints.py -q` +Expected: PASS. + +- [ ] **Step 7: Commit** + +```bash +git add -- tests/pricing/test_pricing.py src/codex_usage_tracker/pricing/config.py src/codex_usage_tracker/pricing/openai.py src/codex_usage_tracker/pricing/api.py +git commit -m "feat: cache all API pricing tiers" +``` + +### Task 3: Select Per-Call API Rates and Expose Scenarios + +**Files:** +- Modify: `tests/pricing/test_pricing.py` +- Modify: `src/codex_usage_tracker/pricing/costing.py` +- Modify: `src/codex_usage_tracker/core/schema.py` +- Modify: `frontend/dashboard/src/api/serviceTier.ts` +- Modify: `frontend/dashboard/src/api/types.ts` + +**Interfaces:** +- Adds row keys: `standard_cost_usd`, `priority_cost_usd`, `pricing_service_tier`, `billing_basis`, and `cost_semantics`. +- Preserves `estimated_cost_usd` as the API-equivalent estimate selected from the exact tier when available. + +- [ ] **Step 1: Write failing cost-selection tests** + +```python +def pricing_v2_config() -> PricingConfig: + standard = { + "input_per_million": 5.0, + "cached_input_per_million": 0.5, + "output_per_million": 30.0, + } + priority = { + "input_per_million": 10.0, + "cached_input_per_million": 1.0, + "output_per_million": 60.0, + } + return PricingConfig( + path=Path("/synthetic/pricing.json"), + models={"gpt-5.6": standard}, + loaded=True, + api_service_tiers={ + "standard": {"gpt-5.6": standard}, + "priority": {"gpt-5.6": priority}, + }, + billing_basis="unknown", + ) + +def cost_row(service_tier: str | None) -> dict[str, object]: + return { + "model": "gpt-5.6", + "input_tokens": 100, + "cached_input_tokens": 20, + "uncached_input_tokens": 80, + "output_tokens": 10, + "service_tier": service_tier, + } + +def test_v2_priority_row_uses_priority_table() -> None: + config = pricing_v2_config() + standard = estimate_cost_usd(cost_row(service_tier="standard"), config) + priority = estimate_cost_usd(cost_row(service_tier="priority"), config) + assert priority == pytest.approx(standard * 2) + +def test_cost_annotations_expose_standard_and_priority_scenarios() -> None: + annotated = annotate_rows_with_efficiency([cost_row(service_tier=None)], pricing_v2_config())[0] + assert annotated["standard_cost_usd"] is not None + assert annotated["priority_cost_usd"] is not None + assert annotated["billing_basis"] == "unknown" +``` + +- [ ] **Step 2: Verify RED** + +Run: `.venv/bin/python -m pytest tests/pricing/test_pricing.py -q` +Expected: FAIL because costing does not pass the row tier or emit scenarios. + +- [ ] **Step 3: Implement exact-tier and scenario costing** + +```python +rates = pricing.rates_for( + model if model is not None else row.get("model"), + service_tier=row.get("service_tier"), +) +copy["pricing_service_tier"] = pricing.pricing_tier_for(copy.get("service_tier")) +copy["standard_cost_usd"] = estimate_cost_usd_for_tier(copy, config, "standard") +copy["priority_cost_usd"] = estimate_cost_usd_for_tier(copy, config, "priority") +copy["billing_basis"] = config.billing_basis +copy["cost_semantics"] = "api_token_estimate" +``` + +- [ ] **Step 4: Verify GREEN and schema propagation** + +Run: `.venv/bin/python -m pytest tests/pricing/test_pricing.py tests/core/test_api_payloads.py tests/dashboard/test_dashboard_payload.py -q` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add -- tests/pricing/test_pricing.py tests/core/test_api_payloads.py tests/dashboard/test_dashboard_payload.py src/codex_usage_tracker/pricing/costing.py src/codex_usage_tracker/core/schema.py frontend/dashboard/src/api/serviceTier.ts frontend/dashboard/src/api/types.ts +git commit -m "feat: price calls by observed API tier" +``` + +### Task 4: Source-Stamp ChatGPT Fast Multipliers + +**Files:** +- Modify: `src/codex_usage_tracker/plugin_data/rate_cards/codex-credit-rates.json` +- Modify: `src/codex_usage_tracker/pricing/allowance_rate_card.py` +- Modify: `src/codex_usage_tracker/pricing/allowance_config.py` +- Modify: `src/codex_usage_tracker/pricing/fast_tier.py` +- Modify: `src/codex_usage_tracker/pricing/allowance_usage.py` +- Modify: `tests/pricing/test_allowance.py` +- Modify: `tests/pricing/test_rate_card.py` + +**Interfaces:** +- Produces: `FastMultiplierMatch(multiplier, model_family, source_url, fetched_at, confidence)`. +- Adds row keys: `fast_usage_credits`, `usage_credit_multiplier_source_url`, `usage_credit_multiplier_fetched_at`, and `usage_credit_multiplier_confidence`. +- Allows local `allowance.json` `fast_multipliers` entries to override bundled entries with `user_override` confidence. + +- [ ] **Step 1: Write failing rate-card and annotation tests** + +```python +def test_fast_multiplier_provenance_comes_from_rate_card() -> None: + config = replace( + _synthetic_allowance_config(), + fast_multipliers={ + "gpt-5.6": FastMultiplierRate( + multiplier=2.5, + source_name="OpenAI Codex Fast mode docs", + source_url="https://learn.chatgpt.com/docs/agent-configuration/speed", + fetched_at="2026-07-16", + confidence="exact", + ) + }, + ) + row = annotate_rows_with_allowance( + [_credit_row(model="gpt-5.6", fast=1, service_tier="priority")], config + )[0] + assert row["usage_credit_multiplier"] == 2.5 + assert row["usage_credit_multiplier_source"] == "OpenAI Codex Fast mode docs" + assert row["usage_credit_multiplier_source_url"].endswith("/agent-configuration/speed") + assert row["usage_credit_multiplier_confidence"] == "exact" + assert row["service_tier_source"] == "otel_response_completed" + +def test_local_fast_multiplier_override_is_source_stamped() -> None: + config = load_allowance_config(write_allowance_override( + fast_multipliers={ + "gpt-5.6": { + "multiplier": 3.0, + "source_name": "Synthetic override", + "source_url": "https://example.invalid/synthetic-fast-rate", + "fetched_at": "2026-07-16", + } + } + )) + assert config.fast_multipliers["gpt-5.6"].confidence == "user_override" +``` + +Add `write_allowance_override()` as a local test helper that writes the shown dictionary beneath +the existing allowance template's `fast_multipliers` key to `tmp_path / "allowance.json"` and +returns that path. + +- [ ] **Step 2: Verify RED** + +Run: `.venv/bin/python -m pytest tests/pricing/test_allowance.py tests/pricing/test_rate_card.py -q` +Expected: FAIL because multipliers are hard-coded and provenance is conflated with OTel. + +- [ ] **Step 3: Move multipliers into the source-stamped rate card** + +```json +"fast_multipliers": { + "gpt-5.6": { + "multiplier": 2.5, + "source_name": "OpenAI Codex Fast mode docs", + "source_url": "https://learn.chatgpt.com/docs/agent-configuration/speed", + "fetched_at": "2026-07-16", + "confidence": "exact" + } +} +``` + +Add equivalent GPT-5.5 and GPT-5.4 entries. Parse only finite multipliers `>= 1.0`; reject malformed local overrides without discarding the valid bundled card. + +- [ ] **Step 4: Resolve multiplier by model family and annotate provenance** + +`fast_tier.py` performs family matching against parsed configuration rather than module constants. `allowance_usage.py` computes Standard and hypothetical Fast scenarios, applies the Fast scenario only when `fast == 1`, and keeps OTel tier evidence in the existing service-tier fields. + +- [ ] **Step 5: Verify GREEN** + +Run: `.venv/bin/python -m pytest tests/pricing/test_allowance.py tests/pricing/test_rate_card.py tests/dashboard/test_dashboard_payload.py -q` +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add -- src/codex_usage_tracker/plugin_data/rate_cards/codex-credit-rates.json src/codex_usage_tracker/pricing/allowance_rate_card.py src/codex_usage_tracker/pricing/allowance_config.py src/codex_usage_tracker/pricing/fast_tier.py src/codex_usage_tracker/pricing/allowance_usage.py tests/pricing/test_allowance.py tests/pricing/test_rate_card.py tests/dashboard/test_dashboard_payload.py +git commit -m "feat: source stamp Fast credit multipliers" +``` + +### Task 5: Correct Dashboard Tier Labels + +**Files:** +- Modify: `frontend/dashboard/src/features/calls/serviceTier.test.ts` +- Modify: `frontend/dashboard/src/features/shared/callPresentation.ts` +- Modify: `frontend/dashboard/src/features/shared/tables.test.ts` +- Modify: `frontend/dashboard/src/features/shared/tables.tsx` + +**Interfaces:** +- Produces: `serviceTierLabel()` labels derived from `serviceTier` first and `fast` only as fallback. +- Produces: tier detail that reports exact tier and confidence without claiming billing path. + +- [ ] **Step 1: Write failing presentation tests** + +```typescript +it.each([ + ['priority', true, 'Priority / Fast'], + ['fast', true, 'Fast'], + ['default', false, 'Default / Standard'], + ['standard', false, 'Standard'], + ['flex', false, 'Flex'], + ['batch', false, 'Batch'], +])('labels exact service tier %s', (serviceTier, fast, expected) => { + expect(serviceTierLabel({ ...baseCall, serviceTier, fast })).toBe(expected); +}); +``` + +- [ ] **Step 2: Verify RED** + +Run: `npm test -- --run frontend/dashboard/src/features/calls/serviceTier.test.ts` +Expected: FAIL because every `fast=false` row is currently Standard. + +- [ ] **Step 3: Implement exact-tier labels and details** + +Normalize the tier string, map known values explicitly, title-case bounded unknown values, and use `fast` only when no exact tier exists. Include `serviceTier` in `ServiceTierInput`. + +- [ ] **Step 4: Verify GREEN** + +Run: `npm test -- --run frontend/dashboard/src/features/calls/serviceTier.test.ts frontend/dashboard/src/features/shared/tables.test.ts` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add -- frontend/dashboard/src/features/calls/serviceTier.test.ts frontend/dashboard/src/features/shared/callPresentation.ts frontend/dashboard/src/features/shared/tables.test.ts frontend/dashboard/src/features/shared/tables.tsx +git commit -m "fix: label exact service tiers" +``` + +### Task 6: Make Reset Destructive and Rotation Cursor-Safe + +**Files:** +- Modify: `tests/store/test_source_records.py` +- Modify: `src/codex_usage_tracker/store/api.py` +- Modify: `tests/store/test_otel_ingest.py` +- Modify: `src/codex_usage_tracker/store/otel_ingest.py` + +**Interfaces:** +- `reset_usage_database()` deletes `otel_completion_events` and `otel_completion_sources`. +- OTel cursors persist descriptor identity and never mix offsets across inodes. + +- [ ] **Step 1: Write failing reset test** + +```python +def test_reset_usage_database_clears_otel_staging_and_cursors(tmp_path: Path) -> None: + db_path = tmp_path / "usage.sqlite3" + otel_dir = write_otel_directory( + tmp_path, + "synthetic-reset", + (120, 40, 30, 10), + ) + with connect(db_path) as conn: + init_db(conn) + ingest_otel_completion_files(conn, otel_dir) + reset_usage_database(db_path) + with connect(db_path) as conn: + assert conn.execute("SELECT COUNT(*) FROM otel_completion_events").fetchone()[0] == 0 + assert conn.execute("SELECT COUNT(*) FROM otel_completion_sources").fetchone()[0] == 0 +``` + +- [ ] **Step 2: Verify RED, implement deletion, and verify GREEN** + +Run: `.venv/bin/python -m pytest tests/store/test_source_records.py -q` +Expected before implementation: FAIL with retained rows. Add both fixed-table `DELETE` statements and rerun for PASS. + +- [ ] **Step 3: Write failing concurrent-rotation test** + +Use a small injected `open_file`/`after_open` seam in `ingest_otel_completion_files` so the test can replace the path after the initial discovery stat but before reading. Assert the replacement file is read from byte zero and its cursor matches its own `fstat()` identity. + +- [ ] **Step 4: Verify RED** + +Run: `.venv/bin/python -m pytest tests/store/test_otel_ingest.py -q` +Expected: FAIL because path stats and descriptor reads can refer to different inodes. + +- [ ] **Step 5: Implement descriptor-based cursor safety** + +Open first, call `os.fstat(handle.fileno())` for resume identity, read complete lines, call `os.fstat()` again, and persist only descriptor-derived state. If descriptor identity changes unexpectedly, discard the candidate cursor and retry from zero once; never combine `Path.stat()` identity with an offset read from another descriptor. + +- [ ] **Step 6: Verify GREEN and commit** + +Run: `.venv/bin/python -m pytest tests/store/test_source_records.py tests/store/test_otel_ingest.py tests/store/test_otel_refresh.py -q` +Expected: PASS. + +```bash +git add -- tests/store/test_source_records.py tests/store/test_otel_ingest.py src/codex_usage_tracker/store/api.py src/codex_usage_tracker/store/otel_ingest.py +git commit -m "fix: clear OTel state and harden rotation" +``` + +### Task 7: Update Contracts, Documentation, and Generated Dashboard Assets + +**Files:** +- Modify: `docs/pricing-and-credits.md` +- Modify: `docs/database-schema.md` +- Modify: `docs/privacy.md` +- Modify: `docs/dashboard-guide.md` +- Modify: `docs/cli-json-schemas.md` +- Modify: `CHANGELOG.md` +- Modify: generated files under `src/codex_usage_tracker/plugin_data/dashboard/react/assets/` +- Modify: affected API/dashboard/report tests. + +**Interfaces:** +- Documents pricing-v2, exact-tier semantics, billing basis, scenario fields, reset/rebuild distinction, and source-stamped multipliers. +- Generated dashboard assets match the reviewed TypeScript source. + +- [ ] **Step 1: Update contract tests before docs/assets** + +Add assertions for exact `priority`, pricing scenario fields, multiplier provenance, and billing-basis labels to the existing API payload, CSV, report, and dashboard tests. Run them and observe failures where propagation is missing. + +- [ ] **Step 2: Complete additive propagation and documentation** + +Keep existing stable keys. Explain that `usage_credits` is ChatGPT-equivalent, `estimated_cost_usd` is API-token-equivalent, `billing_basis` identifies applicability, and unknown basis exposes scenarios without claiming actual spend. + +- [ ] **Step 3: Build dashboard assets and run focused gates** + +Run the repository's existing frontend build/governance commands from `package.json`, followed by: + +```bash +.venv/bin/python -m pytest tests/core/test_api_payloads.py tests/dashboard/test_dashboard_payload.py tests/reports/test_support.py -q +python scripts/check_release.py +git diff --check +``` + +Expected: all PASS and generated assets match source. + +- [ ] **Step 4: Commit** + +```bash +git add -- CHANGELOG.md docs/pricing-and-credits.md docs/database-schema.md docs/privacy.md docs/dashboard-guide.md docs/cli-json-schemas.md frontend/dashboard/src src/codex_usage_tracker/plugin_data/dashboard/react/assets tests +git commit -m "docs: explain tier-aware billing estimates" +``` + +### Task 8: Final Verification and Review + +**Files:** +- No intended production edits; fix only verified failures within this feature scope. + +**Interfaces:** +- Produces release-readiness evidence for the complete branch. + +- [ ] **Step 1: Run focused Python and frontend suites** + +```bash +python -m pytest tests/parser/test_otel_parser.py tests/store/test_otel_ingest.py tests/store/test_otel_reconciliation.py tests/store/test_otel_refresh.py tests/pricing/test_allowance.py tests/pricing/test_pricing.py tests/store/test_source_records.py -q +``` + +Run the frontend service-tier and table tests plus every dashboard test affected by generated assets. + +- [ ] **Step 2: Run the full repository gates** + +```bash +python -m ruff check . +python -m mypy +python -m pytest +python -m pytest --cov=codex_usage_tracker --cov-report=term-missing +python -m compileall src +for file in src/codex_usage_tracker/plugin_data/dashboard/dashboard*.js; do node --check "$file"; done +python scripts/check_release.py +git diff --check +python -m build +python -m twine check dist/* +python scripts/check_release.py --dist +``` + +- [ ] **Step 3: Review status, diff, and privacy** + +Confirm only intended tracked files changed, no local logs/database/config/private values entered the diff, `.idea/` and `uv.lock` remain untracked, and the exact-tier/billing tests prove the reviewed blockers are closed. + +- [ ] **Step 4: Report readiness** + +Provide commits, validation counts, skipped checks with reasons, remaining risks, and whether the branch is ready to push/open as a PR. Do not push or open a PR unless requested. diff --git a/docs/superpowers/specs/2026-07-16-otel-fast-usage-ingestion-design.md b/docs/superpowers/specs/2026-07-16-otel-fast-usage-ingestion-design.md new file mode 100644 index 00000000..9501fb38 --- /dev/null +++ b/docs/superpowers/specs/2026-07-16-otel-fast-usage-ingestion-design.md @@ -0,0 +1,310 @@ +# OTel Fast Usage Ingestion Design + +## Goal + +Enrich existing aggregate Codex usage calls with reliable response service-tier metadata from +the local OpenTelemetry `response.completed` stream, then use the exact observed tier together +with an explicit billing basis to estimate either ChatGPT credit consumption or API token cost +without conflating those two billing systems. + +The feature must remain local, aggregate-only, incremental, idempotent, and conservative. It +must never create a second usage call for an OTel completion, guess when more than one logical +call could match, or persist prompts, message bodies, arbitrary OTel attributes, account data, +or tool content. + +## Non-goals + +- Reconstruct Fast usage before completion-tier telemetry was emitted. +- Treat response timing, reasoning effort, or latency as proof of Fast mode. +- Infer ChatGPT-versus-API billing from a service-tier label alone. +- Replace session JSONL as the canonical source of usage calls and token totals. +- Persist or expose raw OTel payloads through the database, dashboard, exports, support + bundles, or tests. +- Add remote telemetry export or change the user's collector configuration. + +## Alternatives considered + +1. **Multiply every Fast USD estimate by the ChatGPT credit multiplier.** Rejected because API + Priority premiums are model-specific and can differ from ChatGPT Fast credit multipliers. +2. **Keep one globally selected API tier.** Rejected because an exact response tier can vary by + call, including Priority downgrades to Standard, so a global tier can misprice mixed history. +3. **Preserve the exact tier, cache all API tiers, and keep billing basis explicit.** Selected. + This keeps ChatGPT credits and API-token USD as separately labeled estimates, uses the actual + response tier for API-equivalent cost, and never infers authentication from OTel. + +## Source contract + +The optional local source is the file-exporter output under the tracker application directory: + +```text +~/.codex-usage-tracker/otel/codex-completions.jsonl +~/.codex-usage-tracker/otel/codex-completions-*.jsonl +``` + +Each line is one OTLP JSON object that may contain multiple resource, scope, and log-record +groups. The parser considers only log records whose allowlisted attributes identify +`event.name = codex.sse_event` and `event.kind = response.completed`. + +The parser may extract only these aggregate fields: + +- `conversation.id` +- `input_token_count` +- `cached_token_count` +- `output_token_count` +- `reasoning_token_count` +- `model` +- `model_reasoning_effort` +- `service_tier` +- `app.version` +- log-record timestamp + +All other attributes, resources, scopes, bodies, trace identifiers, and span identifiers are +ignored. Malformed lines or invalid field types increment bounded diagnostics without copying +the offending content into SQLite or report payloads. + +## Tier normalization + +The normalized call fields are: + +- `service_tier`: the exact normalized response tier (`priority`, `default`, `flex`, another + explicit value), `standard` when established by versioned omission, or `NULL` when unknown. +- `fast`: `1` for the Codex accelerated path established by `priority`/`fast`, `0` for a + confirmed non-Fast tier, or `NULL` when unknown. This is a derived product classification, + not a billing-system discriminator. +- `service_tier_source`: `otel_response_completed` for this ingestion path. +- `service_tier_confidence`: `exact` for explicit tier values and `protocol` when Standard is + established by versioned omission semantics. + +Explicit tier names are preserved in normalized lower case. `priority` and `fast` set `fast = 1`; +`default`, `standard`, `flex`, and other explicit non-Fast values set `fast = 0`. The exact +response tier and the derived Fast classification must remain separately auditable. + +Codex 0.143.0 and later emits `service_tier = priority` for accelerated completions and omits +the field for Standard completions. Therefore, a missing service tier with a parseable +`app.version >= 0.143.0` normalizes to `service_tier = standard`, `fast = 0`, and +`service_tier_confidence = protocol`. A missing tier from an older or unparseable version +remains unknown. + +## Persistent ingestion + +Two aggregate-only sidecar tables make ingestion incremental and independent of file arrival +order. + +### OTel source cursors + +`otel_completion_sources` records the source path, device, inode, size, parsed byte offset, +parsed line number, and last refresh timestamp. Append-only files resume at the previous byte +offset. Truncation, inode replacement, or size regression resets the cursor safely. Rotated +files are discovered in deterministic path order. + +### Completion staging + +`otel_completion_events` stores one normalized aggregate completion per semantic fingerprint. +The fingerprint is a versioned SHA-256 digest over the normalized conversation ID, timestamp, +four token counters, model, effort, tier state, and app version. Re-reading a current or +rotated file is therefore idempotent. + +The staging row stores only the extracted aggregate fields, source path, source line, +fingerprint, match status, and an optional matched record ID. It never stores the OTLP line, +body, arbitrary attribute map, prompt, account metadata, or tool content. + +Supported match statuses are `pending`, `matched`, `ambiguous`, `conflict`, and `invalid`. +Pending and ambiguous rows remain available for a later refresh because session JSONL and OTel +files can advance in either order. + +## Conservative correlation + +Session JSONL remains authoritative for call identity and token totals. A normalized OTel +completion may enrich a usage call only when all of the following hold: + +1. `conversation.id` equals `usage_events.session_id`. +2. Input, cached-input, output, and reasoning-output token counts match exactly. +3. Model values match when both sources provide a model. +4. Reasoning-effort values match when both sources provide an effort. +5. The candidates resolve to exactly one canonical usage-call group. + +Timestamp proximity is not a matching key. Export batching and source-event timing can differ, +so timestamp heuristics would create false confidence. The timestamp is used only in the +completion fingerprint and diagnostics. + +If candidates span multiple canonical groups, the completion remains `ambiguous`. If exactly +one canonical group matches, every physical clone in that canonical group receives the same +tier enrichment. This preserves clone consistency without changing usage fingerprints, +canonical IDs, or deduplication decisions. + +Existing non-null tier enrichment is never silently replaced by a contradictory completion. +The completion becomes `conflict`, the existing call value is preserved, and an aggregate +conflict counter is emitted. Repeated agreement is idempotent. + +The session-log upsert must use enrichment-owned merge semantics so a later source replacement +or incremental JSONL refresh cannot erase an existing non-null service tier. + +## Refresh and rebuild behavior + +Normal refresh performs these ordered phases: + +1. Parse and upsert session JSONL usage calls as today. +2. Discover and incrementally parse OTel completion files when the optional directory exists. +3. Reconcile pending and ambiguous completion rows against current usage calls. +4. Recompute affected derived facts, thread summaries, and revision counters. +5. Record bounded OTel coverage diagnostics in refresh metadata. + +An absent OTel directory is a supported no-op. OTel parse failures do not roll back valid +session-log ingestion; they produce aggregate diagnostics and preserve the last valid cursor. + +Rebuild clears canonical usage-derived state but retains normalized OTel staging. It resets +stale match pointers, rebuilds session usage calls, then reconciles the retained completion +events. This makes tier enrichment reproducible without requiring rotated source files to +remain forever. + +## Database and public rows + +`usage_events` gains four repairable nullable columns: + +```text +service_tier TEXT +fast INTEGER +service_tier_source TEXT +service_tier_confidence TEXT +``` + +These fields are additive to existing row and export contracts. Historical calls remain +`NULL`/unknown unless a conservative OTel match exists. They are not identity fields and must +not participate in usage fingerprints, canonical IDs, or duplicate classification. + +Calls and call-detail payloads expose the four aggregate fields. Dashboard labels preserve the +exact observed tier and distinguish Priority/Fast, Default/Standard, Flex, Batch, another +explicit tier, and Unknown. Existing proxy analysis remains available for historical unknown +calls, but exact OTel enrichment takes precedence and proxy wording must not claim that direct +tier data is unavailable for enriched rows. + +## Credit and USD pricing semantics + +Confirmed Fast calls multiply the existing Standard ChatGPT credit estimate by the documented +model multiplier: + +- GPT-5.6: `2.5` +- GPT-5.5: `2.5` +- GPT-5.4: `2.0` + +Confirmed Standard and unknown calls use multiplier `1.0`; unknown calls must remain visibly +unknown rather than being labeled Standard. Credit annotations add the effective multiplier +and tier provenance so totals remain explainable. + +`priority` can represent ChatGPT Fast mode or API Priority processing, and the retained OTel +attributes do not prove the authentication or billing path. Reports therefore require an +explicit billing basis with three states: + +- `chatgpt_credits`: apply the documented, source-stamped Fast credit multiplier to confirmed + Fast calls; API USD remains an explicitly labeled equivalent estimate rather than actual spend. +- `api_tokens`: do not apply ChatGPT credit multipliers; select the exact API pricing table from + the observed response tier (`priority`, `default`/`standard`, `flex`, or `batch`). +- `unknown`: do not present one falsely precise billing result; expose the Standard and Priority + API-equivalent scenarios plus the unadjusted and Fast-adjusted ChatGPT credit scenarios when + the required rates exist. + +The local API pricing-v2 snapshot stores all published service-tier tables together under +`api_service_tiers`, while retaining `models` as the selected legacy projection. Existing +single-tier pricing-v1 files remain readable and keep their source tier as their only available +tier. `billing_basis` is an explicit local value (`chatgpt_credits`, `api_tokens`, or `unknown`) +preserved by pricing refreshes; it labels which estimate is applicable but never changes the +observed service tier. New costing APIs accept the row's observed service tier and never apply a +generic Fast multiplier to API USD because Priority premiums vary by model. + +Cost annotations remain additive and backward compatible. `estimated_cost_usd` uses the exact +observed API tier when a pricing-v2 rate exists, while `standard_cost_usd` and +`priority_cost_usd` expose bounded comparison scenarios. `pricing_service_tier` records the +table actually selected. Existing pricing-v1 files keep their current single-tier behavior. +Credit annotations retain `usage_credits` as the ChatGPT-equivalent estimate and +`standard_usage_credits` as its baseline; `fast_usage_credits` exposes the documented Fast +scenario when the model has a source-stamped multiplier. The dashboard uses `billing_basis` to +label these as applicable or equivalent scenarios rather than claiming both are actual spend. + +Fast credit multiplier metadata is part of the source-stamped Codex rate-card contract. Its +provenance is separate from `service_tier_source`: OTel proves which tier served the call, while +the rate card proves which numeric multiplier and effective source were used. Bundled and local +rate cards may define `fast_multipliers` by model family with `multiplier`, `source_url`, +`fetched_at`, and `confidence`. Row annotations expose that rate-card provenance through +`usage_credit_multiplier_source_url`, `usage_credit_multiplier_fetched_at`, and +`usage_credit_multiplier_confidence`; `usage_credit_multiplier_source` remains a bounded label. + +## Presentation semantics + +Dashboard and CSV rows preserve the exact service tier. Human labels distinguish Fast, +Priority, Default/Standard, Flex, Batch, another explicit tier, and Unknown. When billing basis +is unknown, the dashboard labels the tier as observed without claiming whether the call was +billed through ChatGPT credits or API tokens. + +## Reset and rotation semantics + +`rebuild-index` retains normalized OTel staging so tier enrichment can be reconstructed after +canonical usage rows are rebuilt. In contrast, confirmed `reset-db` clears both OTel staging +tables and their source cursors along with every other tracker-owned aggregate row. + +Incremental ingestion derives resume identity and final size from the open file descriptor. +If a path changes inode between discovery and open, or the descriptor identity changes during +the guarded read, ingestion resets/retries without saving an offset from one inode against +another. A concurrent rotation must never permanently skip the beginning of the replacement +file. + +## Diagnostics and privacy + +Refresh diagnostics report aggregate counts only, including files scanned, completions +imported, matched, pending, ambiguous, conflicting, malformed, and unsupported-version rows. +Support bundles may include those counts and configuration presence, never source paths, +conversation IDs, fingerprints, record IDs, timestamps, token tuples, or raw payloads. + +Default dashboards, CSV, JSON, MCP responses, screenshots, and fixtures remain aggregate-first. +All committed fixtures use synthetic conversation IDs, timestamps, models, and token counts. + +## Test strategy + +Implementation follows red-green-refactor cycles with synthetic files and databases. + +Parser tests cover: + +- OTLP batches containing multiple resource and scope groups. +- Allowlisted completion extraction and arbitrary-field rejection. +- Explicit Fast, explicit Standard, protocol-omitted Standard, and unknown older versions. +- Invalid JSON, invalid integers, missing identifiers, and non-completion log records. +- Current-file append, truncation, replacement, rotation, and semantic deduplication. + +Store tests cover: + +- OTel-before-JSONL and JSONL-before-OTel arrival. +- Unique exact match, no match, multiple canonical matches, and conflicting tiers. +- Repeated refresh idempotency. +- Canonical clone propagation. +- Session source replacement preserving tier enrichment. +- Rebuild from retained staging. +- Schema migration and repairable-column behavior. +- No raw OTel body or arbitrary attribute persisted in any table. + +Pricing and report tests cover: + +- Fast credit multipliers for GPT-5.6, GPT-5.5, and GPT-5.4. +- Standard and unknown credit multiplier behavior. +- Source-stamped Fast multiplier provenance separate from OTel tier provenance. +- Pricing-v1 compatibility and pricing-v2 all-tier parsing. +- Per-call Standard, Priority, Flex, and Batch API rate selection without a generic multiplier. +- Explicit `billing_basis` preservation and unknown-basis scenario labels. +- Exact tier taking precedence over historical proxy labels. +- Additive Calls, detail, export, and support-bundle contract behavior. +- Confirmed reset clearing OTel staging/cursors while rebuild retains staging. +- Rotation during an active read without cross-inode cursor persistence. + +## Acceptance criteria + +- A uniquely correlated synthetic Fast completion enriches exactly one canonical call group. +- A uniquely correlated synthetic Standard completion records confirmed Standard status. +- Ambiguous or unmatched completions never alter usage calls. +- Refresh, rotation, source replacement, and rebuild remain idempotent. +- Credit estimates use documented Fast multipliers only for confirmed Fast calls. +- API USD estimates use the exact published tier table when that tier/model pair is available. +- ChatGPT credits and API USD remain separately labeled estimates, with explicit billing basis. +- Historical unknown calls remain unknown. +- `reset-db --yes` clears OTel completion rows and source cursors. +- No raw OTel content appears in SQLite, default reports, exports, support bundles, docs, or + committed fixtures. +- Focused parser, store, migration, pricing, report, and privacy tests pass before the full + repository verification gate. diff --git a/frontend/dashboard/src/api/billing.ts b/frontend/dashboard/src/api/billing.ts new file mode 100644 index 00000000..5dca320a --- /dev/null +++ b/frontend/dashboard/src/api/billing.ts @@ -0,0 +1,34 @@ +export type UsageBillingFields = { + estimated_cost_usd?: number | null; + standard_cost_usd?: number | null; + priority_cost_usd?: number | null; + pricing_service_tier?: string | null; + billing_basis?: string | null; + cost_semantics?: string | null; +}; + +export type CallBillingFields = { + cost: number; + standardCost: number | null; + priorityCost: number | null; + pricingServiceTier: string; + billingBasis: string; + costSemantics: string; +}; + +export function usageBillingFields(row: UsageBillingFields): CallBillingFields { + return { + cost: Number(row.estimated_cost_usd ?? 0), + standardCost: optionalNumber(row.standard_cost_usd), + priorityCost: optionalNumber(row.priority_cost_usd), + pricingServiceTier: String(row.pricing_service_tier ?? ''), + billingBasis: String(row.billing_basis ?? 'unknown'), + costSemantics: String(row.cost_semantics ?? 'api_token_estimate'), + }; +} + +function optionalNumber(value: unknown): number | null { + if (value === null || value === undefined || value === '') return null; + const numeric = Number(value); + return Number.isFinite(numeric) ? numeric : null; +} diff --git a/frontend/dashboard/src/api/client.test.ts b/frontend/dashboard/src/api/client.test.ts index 66653ee1..e32dc1ce 100644 --- a/frontend/dashboard/src/api/client.test.ts +++ b/frontend/dashboard/src/api/client.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { loadUsagePayload, modelFromBootPayload } from './client'; +import { loadUsagePayload, modelFromBootPayload, usageRowToCall } from './client'; import type { DashboardBootPayload } from './types'; afterEach(() => { @@ -8,6 +8,53 @@ afterEach(() => { }); describe('dashboard API model builder', () => { + it('keeps exact tier separate from throughput proxy', () => { + const call = usageRowToCall({ + service_tier: 'standard', + fast: 0, + service_tier_confidence: 'protocol', + duration_seconds: 1, + total_tokens: 9000, + }, 0); + + expect(call.fast).toBe(false); + expect(call.fastProxyCandidate).toBe(true); + expect(call.serviceTier).toBe('standard'); + }); + + it('preserves billing scenarios and Fast multiplier provenance', () => { + const call = usageRowToCall({ + service_tier: 'priority', + fast: 1, + estimated_cost_usd: 0.2, + standard_cost_usd: 0.1, + priority_cost_usd: 0.2, + pricing_service_tier: 'priority', + billing_basis: 'unknown', + cost_semantics: 'api_token_estimate', + standard_usage_credits: 4, + fast_usage_credits: 10, + usage_credit_multiplier: 2.5, + usage_credit_multiplier_source: 'OpenAI Codex Fast mode docs', + usage_credit_multiplier_source_url: 'https://example.invalid/fast', + usage_credit_multiplier_fetched_at: '2026-07-16', + usage_credit_multiplier_confidence: 'exact', + }); + + expect(call).toEqual(expect.objectContaining({ + standardCost: 0.1, + priorityCost: 0.2, + pricingServiceTier: 'priority', + billingBasis: 'unknown', + costSemantics: 'api_token_estimate', + standardUsageCredits: 4, + fastUsageCredits: 10, + usageCreditMultiplierSourceUrl: 'https://example.invalid/fast', + usageCreditMultiplierFetchedAt: '2026-07-16', + usageCreditMultiplierConfidence: 'exact', + })); + }); + it('derives model cost bars from live aggregate rows', () => { const payload: DashboardBootPayload = { loaded_row_count: 3, diff --git a/frontend/dashboard/src/api/client.ts b/frontend/dashboard/src/api/client.ts index 3109e355..12f3fb2f 100644 --- a/frontend/dashboard/src/api/client.ts +++ b/frontend/dashboard/src/api/client.ts @@ -8,6 +8,7 @@ import { import { buildFindings, buildModelCosts, buildReports } from './modelInsights'; import { buildOverviewSeriesFromDailyValues } from './overviewSeries'; import { scopeSummaryFromBootPayload, summaryNumber } from './dashboardScopeSummary'; +import { usageBillingFields, usageServiceTierFields } from './rowAnnotations'; import type { CallRow, ContextRuntime, DashboardBootPayload, DashboardModel, MetricCard, Series, ThreadRow, UsageRow, WeeklyWindow } from './types'; import { loadAllUsagePayloadPaged, @@ -537,7 +538,6 @@ export function usageRowToCall(row: UsageRow, index = 0): CallRow { const contextWindowPct = percentNumber(row.context_window_percent); const modelContextWindow = Number(row.model_context_window); const cumulativeTotalTokens = Number(row.cumulative_total_tokens); - return { id, threadKey: String(row.thread_key ?? ''), @@ -555,8 +555,7 @@ export function usageRowToCall(row: UsageRow, index = 0): CallRow { cachedInput: cached, uncachedInput, cachedPct, - cost: Number(row.estimated_cost_usd ?? 0), - credits: Number(row.usage_credits ?? 0), + ...usageBillingFields(row), duration: formatDuration(durationSeconds), durationSeconds, previousCallGap: formatDuration(previousCallGapSeconds), @@ -565,7 +564,7 @@ export function usageRowToCall(row: UsageRow, index = 0): CallRow { initiator: String(row.call_initiator ?? 'unknown'), initiatorReason: String(row.call_initiator_reason ?? ''), initiatorConfidence: String(row.call_initiator_confidence ?? ''), - fast: durationSeconds > 0 && totalTokens / Math.max(durationSeconds, 1) > 4_000, + ...usageServiceTierFields(row, durationSeconds, totalTokens), usageCreditConfidence: String(row.usage_credit_confidence ?? 'unknown'), usageCreditModel: String(row.usage_credit_model ?? ''), usageCreditSource: String(row.usage_credit_source ?? ''), diff --git a/frontend/dashboard/src/api/modelInsights.ts b/frontend/dashboard/src/api/modelInsights.ts index 14678abb..b86a68f0 100644 --- a/frontend/dashboard/src/api/modelInsights.ts +++ b/frontend/dashboard/src/api/modelInsights.ts @@ -52,7 +52,7 @@ export function buildReports(calls: CallRow[]): ReportSummary[] { description: 'Highest estimated credit-impact calls from loaded aggregate rows.', }, ]; - if (calls.some(call => call.fast || call.effort.toLowerCase() === 'low')) { + if (calls.some(call => call.fastProxyCandidate || call.effort.toLowerCase() === 'low')) { reports.push({ title: 'Fast Mode Proxy', status: 'Ready', diff --git a/frontend/dashboard/src/api/rowAnnotations.ts b/frontend/dashboard/src/api/rowAnnotations.ts new file mode 100644 index 00000000..b490ee9d --- /dev/null +++ b/frontend/dashboard/src/api/rowAnnotations.ts @@ -0,0 +1,2 @@ +export { usageBillingFields } from './billing'; +export { usageServiceTierFields } from './serviceTier'; diff --git a/frontend/dashboard/src/api/serviceTier.ts b/frontend/dashboard/src/api/serviceTier.ts new file mode 100644 index 00000000..495f4486 --- /dev/null +++ b/frontend/dashboard/src/api/serviceTier.ts @@ -0,0 +1,63 @@ +export type UsageServiceTierFields = { + standard_usage_credits?: number | null; + fast_usage_credits?: number | null; + usage_credit_multiplier?: number | null; + usage_credit_multiplier_source?: string | null; + usage_credit_multiplier_source_url?: string | null; + usage_credit_multiplier_fetched_at?: string | null; + usage_credit_multiplier_confidence?: string | null; + service_tier?: string | null; + fast?: number | boolean | null; + service_tier_source?: string | null; + service_tier_confidence?: string | null; +}; + +export type CallServiceTierFields = { + credits: number; + serviceTier: string; + fast: boolean | null; + serviceTierSource: string; + serviceTierConfidence: string; + fastProxyCandidate: boolean; + standardUsageCredits: number; + fastUsageCredits: number | null; + usageCreditMultiplier: number; + usageCreditMultiplierSource: string; + usageCreditMultiplierSourceUrl: string; + usageCreditMultiplierFetchedAt: string; + usageCreditMultiplierConfidence: string; +}; + +export function usageServiceTierFields( + row: UsageServiceTierFields & { usage_credits?: number }, + durationSeconds: number, + totalTokens: number, +): CallServiceTierFields { + const rawFast = row.fast; + const fast = rawFast === true || rawFast === 1 + ? true + : rawFast === false || rawFast === 0 + ? false + : null; + return { + credits: Number(row.usage_credits ?? 0), + serviceTier: String(row.service_tier ?? ''), + fast, + serviceTierSource: String(row.service_tier_source ?? ''), + serviceTierConfidence: String(row.service_tier_confidence ?? ''), + fastProxyCandidate: durationSeconds > 0 && totalTokens / Math.max(durationSeconds, 1) > 4_000, + standardUsageCredits: Number(row.standard_usage_credits ?? row.usage_credits ?? 0), + fastUsageCredits: optionalNumber(row.fast_usage_credits), + usageCreditMultiplier: Number(row.usage_credit_multiplier ?? 1), + usageCreditMultiplierSource: String(row.usage_credit_multiplier_source ?? ''), + usageCreditMultiplierSourceUrl: String(row.usage_credit_multiplier_source_url ?? ''), + usageCreditMultiplierFetchedAt: String(row.usage_credit_multiplier_fetched_at ?? ''), + usageCreditMultiplierConfidence: String(row.usage_credit_multiplier_confidence ?? ''), + }; +} + +function optionalNumber(value: unknown): number | null { + if (value === null || value === undefined || value === '') return null; + const numeric = Number(value); + return Number.isFinite(numeric) ? numeric : null; +} diff --git a/frontend/dashboard/src/api/types.ts b/frontend/dashboard/src/api/types.ts index d86def01..75435744 100644 --- a/frontend/dashboard/src/api/types.ts +++ b/frontend/dashboard/src/api/types.ts @@ -1,6 +1,8 @@ +import type { CallBillingFields, UsageBillingFields } from './billing'; import type { DashboardDataScope, DashboardModelScope } from './dashboardDataScope'; +import type { CallServiceTierFields, UsageServiceTierFields } from './serviceTier'; -export type UsageRow = { +export type UsageRow = UsageBillingFields & UsageServiceTierFields & { id?: string; record_id?: string; session_id?: string; @@ -42,7 +44,6 @@ export type UsageRow = { reasoning_output_tokens?: number; total_tokens?: number; uncached_input_tokens?: number; - estimated_cost_usd?: number; usage_credits?: number; rate_limit_plan_type?: string | null; rate_limit_limit_id?: string | null; @@ -290,7 +291,7 @@ export type Finding = { summary: string; }; -export type CallRow = { +export type CallRow = CallBillingFields & CallServiceTierFields & { id: string; threadKey?: string; rawTime: string; @@ -307,8 +308,6 @@ export type CallRow = { cachedInput: number; uncachedInput: number; cachedPct: number; - cost: number; - credits: number; duration: string; durationSeconds: number; previousCallGap: string; @@ -317,7 +316,6 @@ export type CallRow = { initiator: string; initiatorReason: string; initiatorConfidence: string; - fast: boolean; usageCreditConfidence: string; usageCreditModel: string; usageCreditSource: string; diff --git a/frontend/dashboard/src/features/call-investigator/CallInvestigatorPage.tsx b/frontend/dashboard/src/features/call-investigator/CallInvestigatorPage.tsx index 8fda4ace..ea61f0d5 100644 --- a/frontend/dashboard/src/features/call-investigator/CallInvestigatorPage.tsx +++ b/frontend/dashboard/src/features/call-investigator/CallInvestigatorPage.tsx @@ -14,7 +14,7 @@ import { ContextEntryMetadata } from '../shared/ContextEntryMetadata'; import { CallSourceMetadata } from '../shared/CallSourceMetadata'; import { TokenPricingBreakdown } from '../shared/TokenPricingBreakdown'; import { ThreadCallTimeline } from '../shared/ThreadCallTimeline'; -import { cacheState, contextWindowLabel, sourceLine, summarizeTopCounts } from '../shared/callPresentation'; +import { billingBasisDetail, cacheState, contextWindowLabel, serviceTierDetail, sourceLine, summarizeTopCounts } from '../shared/callPresentation'; import { copyText } from '../shared/copyText'; import { CallSignalPucks } from '../shared/tables'; import { @@ -209,8 +209,8 @@ throw new Error('Clipboard unavailable'); - - + + diff --git a/frontend/dashboard/src/features/calls/CallInspector.tsx b/frontend/dashboard/src/features/calls/CallInspector.tsx index d1cb279b..032978cf 100644 --- a/frontend/dashboard/src/features/calls/CallInspector.tsx +++ b/frontend/dashboard/src/features/calls/CallInspector.tsx @@ -20,13 +20,14 @@ import { CallDecisionCard } from '../shared/CallDecisionCard'; import { CallSourceMetadata } from '../shared/CallSourceMetadata'; import { TokenPricingBreakdown } from '../shared/TokenPricingBreakdown'; import { ThreadCallTimeline } from '../shared/ThreadCallTimeline'; -import { cacheState, summarizeTopCounts } from '../shared/callPresentation'; +import { billingBasisDetail, cacheState, summarizeTopCounts } from '../shared/callPresentation'; import { copyText } from '../shared/copyText'; import { formatCompact, formatNumber, money, pct } from '../shared/format'; import { CallSignalPucks } from '../shared/tables'; import { compareCallTimeDescending } from './callsFilterSort'; import { CallContextEvidence } from './CallContextEvidence'; import { DetailRow, DrillMetric } from './CallDetailPrimitives'; +import { serviceTierDetail } from './serviceTier'; type DrillDownTab = 'summary' | 'tokens' | 'cache' | 'thread' | 'evidence'; @@ -165,8 +166,8 @@ function SummaryTab({ call }: { call: CallRow }) { - - + + diff --git a/frontend/dashboard/src/features/calls/serviceTier.test.ts b/frontend/dashboard/src/features/calls/serviceTier.test.ts new file mode 100644 index 00000000..810700e0 --- /dev/null +++ b/frontend/dashboard/src/features/calls/serviceTier.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, it } from 'vitest'; + +import { usageRowToCall } from '../../api/client'; +import { serviceTierDetail, serviceTierLabel } from './serviceTier'; + +describe('service tier labels', () => { + it.each([ + ['priority', true, 'Priority / Fast'], + ['fast', true, 'Fast'], + ['default', false, 'Default / Standard'], + ['standard', false, 'Standard'], + ['flex', false, 'Flex'], + ['batch', false, 'Batch'], + ])('labels exact service tier %s', (serviceTier, fast, expected) => { + const call = usageRowToCall({ + service_tier: serviceTier, + fast: fast ? 1 : 0, + service_tier_source: 'otel_response_completed', + service_tier_confidence: 'exact', + }); + + expect(serviceTierLabel(call)).toBe(expected); + expect(serviceTierDetail(call)).toBe(`observed ${expected} · exact`); + }); + + it('shows protocol-confirmed Standard separately from the throughput proxy', () => { + const call = usageRowToCall({ + service_tier: 'standard', + fast: 0, + service_tier_source: 'otel_response_completed', + service_tier_confidence: 'protocol', + duration_seconds: 1, + total_tokens: 9000, + }); + + expect(serviceTierLabel(call)).toBe('Standard'); + expect(serviceTierDetail(call)).toBe('observed Standard · protocol'); + expect(call.fastProxyCandidate).toBe(true); + }); + + it('describes unknown tiers with the historical throughput proxy', () => { + const candidate = usageRowToCall({ duration_seconds: 1, total_tokens: 9000 }); + const normal = usageRowToCall({ duration_seconds: 10, total_tokens: 100 }); + + expect(serviceTierLabel(candidate)).toBe('Unknown'); + expect(serviceTierDetail(candidate)).toBe('tier unknown · Fast proxy candidate'); + expect(serviceTierDetail(normal)).toBe('tier unknown · normal throughput proxy'); + }); + + it('uses the derived Fast classification only when no exact tier is present', () => { + const legacy = usageRowToCall({ + fast: 1, + service_tier_confidence: 'exact', + }); + + expect(serviceTierLabel(legacy)).toBe('Fast'); + expect(serviceTierDetail(legacy)).toBe('confirmed Fast · exact'); + }); +}); diff --git a/frontend/dashboard/src/features/calls/serviceTier.ts b/frontend/dashboard/src/features/calls/serviceTier.ts new file mode 100644 index 00000000..1c693985 --- /dev/null +++ b/frontend/dashboard/src/features/calls/serviceTier.ts @@ -0,0 +1 @@ +export { serviceTierDetail, serviceTierLabel } from '../shared/callPresentation'; diff --git a/frontend/dashboard/src/features/reports/reportModel.ts b/frontend/dashboard/src/features/reports/reportModel.ts index 6bea68d4..3e5173fd 100644 --- a/frontend/dashboard/src/features/reports/reportModel.ts +++ b/frontend/dashboard/src/features/reports/reportModel.ts @@ -45,7 +45,7 @@ export function reportEvidenceCalls(report: ReportView | undefined, calls: CallR const rows = [...calls]; if (text.includes('fast')) { return rows - .filter(call => call.fast || call.effort.toLowerCase() === 'low') + .filter(call => call.fastProxyCandidate || call.effort.toLowerCase() === 'low') .sort((left, right) => left.durationSeconds - right.durationSeconds || callCredits(right) - callCredits(left)) .slice(0, 8); } diff --git a/frontend/dashboard/src/features/shared/callPresentation.test.ts b/frontend/dashboard/src/features/shared/callPresentation.test.ts index 8d033710..9683dffe 100644 --- a/frontend/dashboard/src/features/shared/callPresentation.test.ts +++ b/frontend/dashboard/src/features/shared/callPresentation.test.ts @@ -1,5 +1,11 @@ import { describe, expect, it } from 'vitest'; -import { cacheState, contextWindowLabel, sourceLine, summarizeTopCounts } from './callPresentation'; +import { + billingBasisDetail, + cacheState, + contextWindowLabel, + sourceLine, + summarizeTopCounts, +} from './callPresentation'; describe('call presentation helpers', () => { it('labels cache state thresholds consistently across call surfaces', () => { @@ -30,4 +36,16 @@ describe('call presentation helpers', () => { ); expect(summarizeTopCounts([], { emptyLabel: 'no model mix' })).toBe('no model mix'); }); + + it('labels API-equivalent scenarios without claiming actual billing', () => { + expect(billingBasisDetail({ billingBasis: 'unknown', pricingServiceTier: 'priority' })).toBe( + 'API-equivalent scenario · billing basis unknown', + ); + expect(billingBasisDetail({ billingBasis: 'api_tokens', pricingServiceTier: 'priority' })).toBe( + 'API token estimate · Priority rates', + ); + expect(billingBasisDetail({ billingBasis: 'chatgpt_credits', pricingServiceTier: 'priority' })).toBe( + 'API-equivalent scenario · ChatGPT credits selected', + ); + }); }); diff --git a/frontend/dashboard/src/features/shared/callPresentation.ts b/frontend/dashboard/src/features/shared/callPresentation.ts index 41b1e0e8..02813a11 100644 --- a/frontend/dashboard/src/features/shared/callPresentation.ts +++ b/frontend/dashboard/src/features/shared/callPresentation.ts @@ -16,6 +16,74 @@ type ContextWindowInput = { type TopCountStyle = 'parenthetical' | 'x'; +type ServiceTierInput = { + serviceTier: string; + fast: boolean | null; + serviceTierConfidence: string; + fastProxyCandidate: boolean; +}; + +type BillingBasisInput = { + billingBasis: string; + pricingServiceTier: string; +}; + +const knownServiceTierLabels: Record = { + priority: 'Priority / Fast', + fast: 'Fast', + default: 'Default / Standard', + standard: 'Standard', + flex: 'Flex', + batch: 'Batch', +}; + +function exactServiceTierLabel(value: string): string | null { + const normalized = value.trim().toLowerCase().replace(/[\s_]+/g, '-'); + if (!normalized) return null; + const known = knownServiceTierLabels[normalized]; + if (known) return known; + if (normalized.length > 48 || !/^[a-z0-9.-]+$/.test(normalized)) return 'Other'; + return normalized + .split(/[-.]+/) + .filter(Boolean) + .map(part => `${part.charAt(0).toUpperCase()}${part.slice(1)}`) + .join(' '); +} + +export function serviceTierLabel(call: ServiceTierInput): string { + const exactLabel = exactServiceTierLabel(call.serviceTier); + if (exactLabel) return exactLabel; + if (call.fast === true) return 'Fast'; + if (call.fast === false) return 'Standard'; + return 'Unknown'; +} + +export function serviceTierDetail(call: ServiceTierInput): string { + if (exactServiceTierLabel(call.serviceTier)) { + return `observed ${serviceTierLabel(call)} · ${call.serviceTierConfidence || 'exact'}`; + } + if (call.fast !== null) { + return `confirmed ${serviceTierLabel(call)} · ${call.serviceTierConfidence || 'exact'}`; + } + return call.fastProxyCandidate + ? 'tier unknown · Fast proxy candidate' + : 'tier unknown · normal throughput proxy'; +} + +export function billingBasisDetail(call: BillingBasisInput): string { + if (call.billingBasis === 'api_tokens') { + const tier = call.pricingServiceTier.trim(); + const label = tier + ? `${tier.charAt(0).toUpperCase()}${tier.slice(1).toLowerCase()} rates` + : 'configured rates'; + return `API token estimate · ${label}`; + } + if (call.billingBasis === 'chatgpt_credits') { + return 'API-equivalent scenario · ChatGPT credits selected'; + } + return 'API-equivalent scenario · billing basis unknown'; +} + export function cacheState(call: CacheStateInput): string { if (call.cachedPct < 25) return 'cold or weak cache'; if (call.cachedPct < 50) return 'partial cache reuse'; diff --git a/frontend/dashboard/src/features/shared/tables.test.ts b/frontend/dashboard/src/features/shared/tables.test.ts index 9c4d626f..e9b1dc6d 100644 --- a/frontend/dashboard/src/features/shared/tables.test.ts +++ b/frontend/dashboard/src/features/shared/tables.test.ts @@ -23,6 +23,11 @@ describe('call CSV columns', () => { uncachedInput: 600, cachedPct: 40, cost: 0.123456, + standardCost: 0.1, + priorityCost: 0.2, + pricingServiceTier: 'standard', + billingBasis: 'unknown', + costSemantics: 'api_token_estimate', credits: 4.5, duration: '2m 0s', durationSeconds: 120, @@ -33,6 +38,17 @@ describe('call CSV columns', () => { initiatorReason: 'tool-driven continuation', initiatorConfidence: 'high', fast: false, + serviceTier: 'standard', + serviceTierSource: 'otel_response_completed', + serviceTierConfidence: 'protocol', + fastProxyCandidate: true, + standardUsageCredits: 4.5, + fastUsageCredits: 9, + usageCreditMultiplier: 1, + usageCreditMultiplierSource: 'otel_response_completed', + usageCreditMultiplierSourceUrl: 'https://example.invalid/fast', + usageCreditMultiplierFetchedAt: '2026-07-16', + usageCreditMultiplierConfidence: 'exact', usageCreditConfidence: 'credit-estimated', usageCreditModel: 'codex-1', usageCreditSource: 'rate-card', @@ -88,6 +104,15 @@ describe('call CSV columns', () => { expect(header).toContain('previous_call_event_timestamp'); expect(header).toContain('pricing_model'); expect(header).toContain('usage_credit_confidence'); + expect(header).toContain('service_tier'); + expect(header).toContain('service_tier_confidence'); + expect(header).toContain('fast_proxy_candidate'); + expect(header).toContain('usage_credit_multiplier'); + expect(header).toContain('standard_cost_usd'); + expect(header).toContain('priority_cost_usd'); + expect(header).toContain('billing_basis'); + expect(header).toContain('fast_usage_credits'); + expect(header).toContain('usage_credit_multiplier_source_url'); expect(header).toContain('thread_attachment'); expect(header).toContain('model_context_window'); expect(values).toContain('record-csv-1'); @@ -101,9 +126,24 @@ expect(header).toContain('model_context_window'); expect(values).toContain('2026-07-01T11:54:30Z'); expect(values).toContain('codex-1-pricing'); expect(values).toContain('credit-estimated'); + expect(values).toContain('otel_response_completed'); expect(values).toContain('spawned child'); expect(values).toContain('cache-drop|context-heavy'); }); + + it('includes an exact Service Tier column', () => { + expect(callColumns.some(column => column.id === 'serviceTier')).toBe(true); + }); + + it('renders Flex and Priority from the exact tier instead of the boolean fallback', () => { + const serviceTierColumn = callColumns.find(column => column.id === 'serviceTier'); + const accessor = serviceTierColumn && 'accessorFn' in serviceTierColumn + ? serviceTierColumn.accessorFn + : undefined; + + expect(accessor?.({ serviceTier: 'flex', fast: false } as CallRow, 0)).toBe('Flex'); + expect(accessor?.({ serviceTier: 'priority', fast: true } as CallRow, 0)).toBe('Priority / Fast'); + }); }); describe('call table columns', () => { diff --git a/frontend/dashboard/src/features/shared/tables.tsx b/frontend/dashboard/src/features/shared/tables.tsx index ca9c3851..c0f4abec 100644 --- a/frontend/dashboard/src/features/shared/tables.tsx +++ b/frontend/dashboard/src/features/shared/tables.tsx @@ -3,6 +3,7 @@ import { useShellI18n } from '../../app/i18nContext'; import type { CallRow, ThreadRow } from '../../api/types'; import type { ColumnChoice } from '../../components/ColumnChooser'; import type { CsvColumn } from './exportCsv'; +import { serviceTierLabel } from './callPresentation'; import { formatCompact, formatNumber, money, pct } from './format'; export { callActionColumn, callInvestigatorRowLabel, threadActionColumn, threadInvestigatorRowLabel } from './tableActions'; @@ -71,6 +72,16 @@ export const callColumns: Array> = [ }, }, { accessorKey: 'duration', header: 'Duration' }, + { + id: 'serviceTier', + accessorFn: call => serviceTierLabel(call), + header: 'Service Tier', + cell: info => { + const label = String(info.getValue()); + const tone = label.includes('Fast') ? 'green' : label.includes('Standard') ? 'blue' : ''; + return {label}; + }, + }, { accessorKey: 'previousCallGap', header: 'Prev Gap', @@ -107,7 +118,24 @@ export const callCsvColumns: Array> = [ { header: 'output_tokens', value: row => row.output }, { header: 'reasoning_output_tokens', value: row => row.reasoningOutput }, { header: 'estimated_cost_usd', value: row => row.cost.toFixed(6) }, + { header: 'standard_cost_usd', value: row => row.standardCost?.toFixed(6) ?? '' }, + { header: 'priority_cost_usd', value: row => row.priorityCost?.toFixed(6) ?? '' }, + { header: 'pricing_service_tier', value: row => row.pricingServiceTier }, + { header: 'billing_basis', value: row => row.billingBasis }, + { header: 'cost_semantics', value: row => row.costSemantics }, { header: 'usage_credits', value: row => row.credits.toFixed(6) }, + { header: 'standard_usage_credits', value: row => row.standardUsageCredits.toFixed(6) }, + { header: 'fast_usage_credits', value: row => row.fastUsageCredits?.toFixed(6) ?? '' }, + { header: 'service_tier', value: row => row.serviceTier }, + { header: 'fast', value: row => row.fast === null ? '' : row.fast ? 1 : 0 }, + { header: 'service_tier_source', value: row => row.serviceTierSource }, + { header: 'service_tier_confidence', value: row => row.serviceTierConfidence }, + { header: 'fast_proxy_candidate', value: row => String(row.fastProxyCandidate) }, + { header: 'usage_credit_multiplier', value: row => row.usageCreditMultiplier }, + { header: 'usage_credit_multiplier_source', value: row => row.usageCreditMultiplierSource }, + { header: 'usage_credit_multiplier_source_url', value: row => row.usageCreditMultiplierSourceUrl }, + { header: 'usage_credit_multiplier_fetched_at', value: row => row.usageCreditMultiplierFetchedAt }, + { header: 'usage_credit_multiplier_confidence', value: row => row.usageCreditMultiplierConfidence }, { header: 'cache_ratio', value: row => row.cachedPct.toFixed(2) }, { header: 'context_window_percent', value: row => row.contextWindowPct?.toFixed(2) ?? '' }, { header: 'pricing_model', value: row => row.pricingModel }, diff --git a/frontend/dashboard/src/test-fixtures/dashboardFixture.ts b/frontend/dashboard/src/test-fixtures/dashboardFixture.ts index a27c8057..f005098e 100644 --- a/frontend/dashboard/src/test-fixtures/dashboardFixture.ts +++ b/frontend/dashboard/src/test-fixtures/dashboardFixture.ts @@ -141,6 +141,11 @@ export const fixtureModel: DashboardModel = { uncachedInput, cachedPct: cachedPercent, cost: index === 4 ? 0 : Number(cost), + standardCost: index === 4 ? 0 : Number(cost), + priorityCost: index === 4 ? 0 : Number(cost) * 2, + pricingServiceTier: 'standard', + billingBasis: 'unknown', + costSemantics: 'api_token_estimate', credits: (index === 4 ? 0 : Number(cost)) * 25, duration: String(duration), durationSeconds: Number.parseFloat(String(duration)) || 0, @@ -150,7 +155,18 @@ export const fixtureModel: DashboardModel = { initiator: index % 3 === 0 ? 'user' : index % 3 === 1 ? 'assistant' : 'tool', initiatorReason: index % 3 === 0 ? 'direct user request' : index % 3 === 1 ? 'assistant follow-up' : 'tool-driven continuation', initiatorConfidence: index % 2 === 0 ? 'exact' : 'estimated', - fast: Boolean(fast), + serviceTier: '', + fast: null, + serviceTierSource: '', + serviceTierConfidence: '', + fastProxyCandidate: Boolean(fast), + standardUsageCredits: (index === 4 ? 0 : Number(cost)) * 25, + fastUsageCredits: (index === 4 ? 0 : Number(cost)) * 50, + usageCreditMultiplier: 1, + usageCreditMultiplierSource: 'tier_unknown', + usageCreditMultiplierSourceUrl: 'https://example.invalid/fixture-fast', + usageCreditMultiplierFetchedAt: '2026-07-16', + usageCreditMultiplierConfidence: 'exact', usageCreditConfidence: index === 7 ? 'user_override' : index % 4 === 0 ? 'exact' : index % 4 === 1 ? 'estimated' : index % 4 === 2 ? 'missing' : 'fixture', usageCreditModel: index === 4 ? '' : `${model}-credits`, usageCreditSource: index === 4 ? '' : 'fixture-rate-card', diff --git a/src/codex_usage_tracker/core/models.py b/src/codex_usage_tracker/core/models.py index 1b2f5559..9116b952 100644 --- a/src/codex_usage_tracker/core/models.py +++ b/src/codex_usage_tracker/core/models.py @@ -71,6 +71,10 @@ class UsageEvent: canonical_record_id: str | None = None is_duplicate: int = 0 duplicate_reason: str | None = None + service_tier: str | None = None + fast: int | None = None + service_tier_source: str | None = None + service_tier_confidence: str | None = None @property def uncached_input_tokens(self) -> int: diff --git a/src/codex_usage_tracker/core/paths.py b/src/codex_usage_tracker/core/paths.py index 0ca1dcaf..0500502a 100644 --- a/src/codex_usage_tracker/core/paths.py +++ b/src/codex_usage_tracker/core/paths.py @@ -6,6 +6,7 @@ APP_DIR = Path.home() / ".codex-usage-tracker" DEFAULT_DB_PATH = APP_DIR / "usage.sqlite3" +DEFAULT_OTEL_COMPLETIONS_DIR = APP_DIR / "otel" DEFAULT_DASHBOARD_PATH = APP_DIR / "dashboard.html" DEFAULT_SUPPORT_BUNDLE_PATH = APP_DIR / "support-bundle.json" DEFAULT_PRICING_PATH = APP_DIR / "pricing.json" diff --git a/src/codex_usage_tracker/core/schema.py b/src/codex_usage_tracker/core/schema.py index 77269f91..ef0b9e27 100644 --- a/src/codex_usage_tracker/core/schema.py +++ b/src/codex_usage_tracker/core/schema.py @@ -77,6 +77,10 @@ class UsageColumn: "is_duplicate", "INTEGER NOT NULL DEFAULT 0", "INTEGER NOT NULL DEFAULT 0", repairable=True ), UsageColumn("duplicate_reason", "TEXT", "TEXT", repairable=True), + UsageColumn("service_tier", "TEXT", "TEXT", repairable=True), + UsageColumn("fast", "INTEGER", "INTEGER", repairable=True), + UsageColumn("service_tier_source", "TEXT", "TEXT", repairable=True), + UsageColumn("service_tier_confidence", "TEXT", "TEXT", repairable=True), UsageColumn("uncached_input_tokens", "INTEGER NOT NULL", "INTEGER"), UsageColumn("cache_ratio", "REAL NOT NULL", "REAL"), UsageColumn("reasoning_output_ratio", "REAL NOT NULL", "REAL"), diff --git a/src/codex_usage_tracker/parser/__init__.py b/src/codex_usage_tracker/parser/__init__.py index 901336ed..9cc560f5 100644 --- a/src/codex_usage_tracker/parser/__init__.py +++ b/src/codex_usage_tracker/parser/__init__.py @@ -5,6 +5,20 @@ from importlib import import_module from typing import Any +from codex_usage_tracker.parser.otel import ( + OTEL_DIAGNOSTIC_KEYS, + OtelCompletion, + OtelParseResult, + parse_otlp_json_line, +) + +__all__ = [ + "OTEL_DIAGNOSTIC_KEYS", + "OtelCompletion", + "OtelParseResult", + "parse_otlp_json_line", +] + _API_MODULE = "codex_usage_tracker.parser.api" diff --git a/src/codex_usage_tracker/parser/otel.py b/src/codex_usage_tracker/parser/otel.py new file mode 100644 index 00000000..9c099e13 --- /dev/null +++ b/src/codex_usage_tracker/parser/otel.py @@ -0,0 +1,342 @@ +"""Pure, aggregate-only parsing for Codex OTLP completion logs.""" + +from __future__ import annotations + +import hashlib +import json +import re +from collections import Counter +from collections.abc import Iterator, Sequence +from dataclasses import dataclass +from datetime import datetime, timezone +from typing import cast + +OTEL_DIAGNOSTIC_KEYS = ( + "otel_invalid_json", + "otel_invalid_record", + "otel_invalid_integer", + "otel_unsupported_version", + "otel_non_completion", +) + +_ALLOWED_ATTRIBUTES = frozenset( + { + "event.name", + "event.kind", + "conversation.id", + "input_token_count", + "cached_token_count", + "output_token_count", + "reasoning_token_count", + "model", + "model_reasoning_effort", + "service_tier", + "app.version", + } +) +_TOKEN_FIELDS = ( + "input_token_count", + "cached_token_count", + "output_token_count", + "reasoning_token_count", +) +_STRING_FIELDS = frozenset( + { + "event.name", + "event.kind", + "conversation.id", + "model", + "model_reasoning_effort", + "service_tier", + "app.version", + } +) +_VERSION_PATTERN = re.compile(r"^v?(\d+)\.(\d+)\.(\d+)(?:[-+][0-9A-Za-z.-]+)?$") +_STANDARD_BY_OMISSION_VERSION = (0, 143, 0) +_INVALID = object() + + +@dataclass(frozen=True) +class OtelCompletion: + fingerprint: str + conversation_id: str | None + event_timestamp: str | None + input_tokens: int | None + cached_input_tokens: int | None + output_tokens: int | None + reasoning_output_tokens: int | None + model: str | None + effort: str | None + service_tier: str | None + fast: int | None + service_tier_source: str | None + service_tier_confidence: str | None + app_version: str | None + match_status: str + + +@dataclass(frozen=True) +class OtelParseResult: + completions: Sequence[OtelCompletion] + diagnostics: dict[str, int] + + +def parse_otlp_json_line(raw: str) -> OtelParseResult: + """Parse one OTLP JSON line without retaining bodies or arbitrary attributes.""" + + try: + payload = json.loads(raw) + except (json.JSONDecodeError, UnicodeDecodeError): + return OtelParseResult((), {"otel_invalid_json": 1}) + if not isinstance(payload, dict): + return OtelParseResult((), {"otel_invalid_record": 1}) + + completions: list[OtelCompletion] = [] + diagnostics: Counter[str] = Counter() + for record in _log_records(cast(dict[str, object], payload), diagnostics): + attributes, invalid_fields = _allowlisted_attributes( + record.get("attributes"), diagnostics + ) + semantic_type_errors = { + key + for key in _STRING_FIELDS + if key in attributes and not isinstance(attributes[key], str) + } + if semantic_type_errors: + diagnostics["otel_invalid_record"] += len(semantic_type_errors) + invalid_fields.update(semantic_type_errors) + if ( + _text(attributes, "event.name") != "codex.sse_event" + or _text(attributes, "event.kind") != "response.completed" + ): + diagnostics["otel_non_completion"] += 1 + continue + completion, completion_diagnostics = _completion_from_attributes( + record, attributes, invalid_fields + ) + diagnostics.update(completion_diagnostics) + completions.append(completion) + return OtelParseResult(tuple(completions), dict(diagnostics)) + + +def _log_records( + payload: dict[str, object], diagnostics: Counter[str] +) -> Iterator[dict[str, object]]: + resource_logs = payload.get("resourceLogs") + if not isinstance(resource_logs, list): + diagnostics["otel_invalid_record"] += 1 + return + for resource_log in resource_logs: + if not isinstance(resource_log, dict): + diagnostics["otel_invalid_record"] += 1 + continue + scope_logs = resource_log.get("scopeLogs") + if not isinstance(scope_logs, list): + diagnostics["otel_invalid_record"] += 1 + continue + for scope_log in scope_logs: + if not isinstance(scope_log, dict): + diagnostics["otel_invalid_record"] += 1 + continue + log_records = scope_log.get("logRecords") + if not isinstance(log_records, list): + diagnostics["otel_invalid_record"] += 1 + continue + for record in log_records: + if not isinstance(record, dict): + diagnostics["otel_invalid_record"] += 1 + continue + yield cast(dict[str, object], record) + + +def _allowlisted_attributes( + raw_attributes: object, diagnostics: Counter[str] +) -> tuple[dict[str, object], set[str]]: + values: dict[str, object] = {} + invalid_fields: set[str] = set() + if not isinstance(raw_attributes, list): + diagnostics["otel_invalid_record"] += 1 + return values, invalid_fields + for raw_attribute in raw_attributes: + if not isinstance(raw_attribute, dict): + diagnostics["otel_invalid_record"] += 1 + continue + key = raw_attribute.get("key") + if not isinstance(key, str) or key not in _ALLOWED_ATTRIBUTES: + continue + value = _otlp_value(raw_attribute.get("value")) + if value is _INVALID: + diagnostics["otel_invalid_record"] += 1 + invalid_fields.add(key) + continue + values[key] = value + return values, invalid_fields + + +def _otlp_value(raw_value: object) -> object: + if not isinstance(raw_value, dict): + return _INVALID + for key in ("stringValue", "intValue", "doubleValue", "boolValue"): + if key not in raw_value: + continue + value = raw_value[key] + if key == "stringValue" and isinstance(value, str): + return value + if key == "intValue" and isinstance(value, (str, int)) and not isinstance(value, bool): + try: + return int(value) + except ValueError: + return value + if key == "doubleValue" and isinstance(value, (int, float)) and not isinstance(value, bool): + return value + if key == "boolValue" and isinstance(value, bool): + return value + return _INVALID + return _INVALID + + +def _completion_from_attributes( + record: dict[str, object], + attributes: dict[str, object], + invalid_fields: set[str], +) -> tuple[OtelCompletion, Counter[str]]: + diagnostics: Counter[str] = Counter() + conversation_id = _text(attributes, "conversation.id") + token_values = tuple(_integer(attributes, key, diagnostics) for key in _TOKEN_FIELDS) + input_tokens, cached_input_tokens, output_tokens, reasoning_output_tokens = token_values + model = _text(attributes, "model") + effort = _text(attributes, "model_reasoning_effort") + app_version = _text(attributes, "app.version") + service_tier, fast, service_tier_source, service_tier_confidence = _normalize_tier( + attributes, invalid_fields, app_version, diagnostics + ) + event_timestamp, timestamp_invalid = _event_timestamp(record) + + has_identity = conversation_id is not None and all(value is not None for value in token_values) + has_tier = service_tier is not None and fast in (0, 1) + match_status = "pending" + if not has_identity or not has_tier or invalid_fields or timestamp_invalid: + match_status = "invalid" + diagnostics["otel_invalid_record"] += 1 + + normalized: dict[str, object] = { + "conversation_id": conversation_id, + "event_timestamp": event_timestamp, + "input_tokens": input_tokens, + "cached_input_tokens": cached_input_tokens, + "output_tokens": output_tokens, + "reasoning_output_tokens": reasoning_output_tokens, + "model": model, + "effort": effort, + "service_tier": service_tier, + "fast": fast, + "service_tier_source": service_tier_source, + "service_tier_confidence": service_tier_confidence, + "app_version": app_version, + } + return ( + OtelCompletion( + fingerprint=_semantic_fingerprint(normalized), + conversation_id=conversation_id, + event_timestamp=event_timestamp, + input_tokens=input_tokens, + cached_input_tokens=cached_input_tokens, + output_tokens=output_tokens, + reasoning_output_tokens=reasoning_output_tokens, + model=model, + effort=effort, + service_tier=service_tier, + fast=fast, + service_tier_source=service_tier_source, + service_tier_confidence=service_tier_confidence, + app_version=app_version, + match_status=match_status, + ), + diagnostics, + ) + + +def _text(attributes: dict[str, object], key: str) -> str | None: + value = attributes.get(key) + if not isinstance(value, str): + return None + normalized = value.strip() + return normalized or None + + +def _integer( + attributes: dict[str, object], key: str, diagnostics: Counter[str] +) -> int | None: + value = attributes.get(key) + if isinstance(value, bool) or value is None: + diagnostics["otel_invalid_integer"] += 1 + return None + if not isinstance(value, str | int | float): + diagnostics["otel_invalid_integer"] += 1 + return None + try: + parsed = int(value) + except (TypeError, ValueError, OverflowError): + diagnostics["otel_invalid_integer"] += 1 + return None + if parsed < 0 or (isinstance(value, float) and not value.is_integer()): + diagnostics["otel_invalid_integer"] += 1 + return None + return parsed + + +def _normalize_tier( + attributes: dict[str, object], + invalid_fields: set[str], + app_version: str | None, + diagnostics: Counter[str], +) -> tuple[str | None, int | None, str | None, str | None]: + if "service_tier" in invalid_fields: + return None, None, None, None + raw_tier = _text(attributes, "service_tier") + if "service_tier" in attributes: + if raw_tier is None: + return None, None, None, None + normalized = raw_tier.lower() + if normalized in {"priority", "fast"}: + return normalized, 1, "otel_response_completed", "exact" + return normalized, 0, "otel_response_completed", "exact" + + parsed_version = _parse_version(app_version) + if parsed_version is None: + diagnostics["otel_unsupported_version"] += 1 + return None, None, None, None + if parsed_version >= _STANDARD_BY_OMISSION_VERSION: + return "standard", 0, "otel_response_completed", "protocol" + return None, None, None, None + + +def _parse_version(value: str | None) -> tuple[int, int, int] | None: + if value is None: + return None + match = _VERSION_PATTERN.fullmatch(value) + if match is None: + return None + return tuple(int(part) for part in match.groups()) # type: ignore[return-value] + + +def _event_timestamp(record: dict[str, object]) -> tuple[str | None, bool]: + raw = record.get("timeUnixNano") + if isinstance(raw, bool) or not isinstance(raw, (str, int)): + return None, raw is not None + try: + nanoseconds = int(raw) + seconds, remainder = divmod(nanoseconds, 1_000_000_000) + timestamp = datetime.fromtimestamp(seconds, tz=timezone.utc) + except (TypeError, ValueError, OverflowError, OSError): + return None, True + base = timestamp.strftime("%Y-%m-%dT%H:%M:%S") + if remainder: + return f"{base}.{remainder:09d}Z", False + return f"{base}Z", False + + +def _semantic_fingerprint(normalized: dict[str, object]) -> str: + payload = {"fingerprint_version": 1, **normalized} + serialized = json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + return hashlib.sha256(serialized.encode("utf-8")).hexdigest() diff --git a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/App.js b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/App.js index f2ade763..b70b4803 100644 --- a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/App.js +++ b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/App.js @@ -1,128 +1,128 @@ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/ThreadsPage.js","assets/dashboard-react.js","assets/index.css","assets/useInfiniteQuery.js","assets/useBaseQuery.js","assets/exploreQueries.js","assets/queryOptions.js","assets/infiniteQueryOptions.js","assets/ExploreWorkspaceSwitcher.js","assets/Primitives.js","assets/Primitives.css","assets/EvidenceGridControls.js","assets/router.js","assets/EvidenceGridControls.css","assets/filtering.js","assets/threadSummaryAdapter.js","assets/UsageConstellation.js","assets/triangle-alert.js","assets/UsageConstellation.css","assets/StatusBadge.js","assets/PageLoadProgress.js","assets/PageLoadProgress.css","assets/index2.js","assets/rowActionEvents.js","assets/search.js","assets/locale-zh-Hans.js","assets/dashboardRouter.js","assets/ThreadsPage.css","assets/CacheContextPage.js","assets/LineChart.js","assets/DataTable.js","assets/overviewQueries.js","assets/gauge.js","assets/Panel.js","assets/useQuery.js","assets/tableActions.js","assets/UsageDrainPage.js","assets/chevron-right.js","assets/shield-check.js","assets/UsageDrainPage.css","assets/DiagnosticsPage.js","assets/diagnosticsQueries.js","assets/ReportsPage.js","assets/ReportsPage.css","assets/CallsPage.js","assets/EvidenceGrid.js","assets/contextEvidenceState.js","assets/lock-keyhole.js","assets/CallsPage.css","assets/OverviewPage.js","assets/OverviewPage.css","assets/InvestigatorPage.js","assets/InvestigatorPage.css","assets/CompressionLabPage.js","assets/CompressionLabPage.css","assets/ExploreRoutePage.js","assets/ExploreRoutePage.css","assets/CallInvestigatorPage.js","assets/SettingsPage.js","assets/SettingsPage.css"])))=>i.map(i=>d[i]); -var qn=e=>{throw TypeError(e)};var Kt=(e,t,n)=>t.has(e)||qn("Cannot "+n);var l=(e,t,n)=>(Kt(e,t,"read from private field"),n?n.call(e):t.get(e)),P=(e,t,n)=>t.has(e)?qn("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n),x=(e,t,n,a)=>(Kt(e,t,"write to private field"),a?a.call(e,n):t.set(e,n),n),V=(e,t,n)=>(Kt(e,t,"access private method"),n);var St=(e,t,n,a)=>({set _(r){x(e,t,r,n)},get _(){return l(e,t,a)}});import{r as C,j as c,_ as O}from"./dashboard-react.js";import{t as bs}from"./locale-zh-Hans.js";import{i as vs}from"./dashboardRouter.js";import{a9 as se}from"./router.js";/** +var Hn=e=>{throw TypeError(e)};var Kt=(e,t,n)=>t.has(e)||Hn("Cannot "+n);var l=(e,t,n)=>(Kt(e,t,"read from private field"),n?n.call(e):t.get(e)),P=(e,t,n)=>t.has(e)?Hn("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n),x=(e,t,n,a)=>(Kt(e,t,"write to private field"),a?a.call(e,n):t.set(e,n),n),V=(e,t,n)=>(Kt(e,t,"access private method"),n);var Ct=(e,t,n,a)=>({set _(r){x(e,t,r,n)},get _(){return l(e,t,a)}});import{r as C,j as c,_ as O}from"./dashboard-react.js";import{t as ys}from"./locale-zh-Hans.js";import{i as _s}from"./dashboardRouter.js";import{a9 as se}from"./router.js";/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const ws=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),Na=(...e)=>e.filter((t,n,a)=>!!t&&t.trim()!==""&&a.indexOf(t)===n).join(" ").trim();/** + */const Ss=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),Fa=(...e)=>e.filter((t,n,a)=>!!t&&t.trim()!==""&&a.indexOf(t)===n).join(" ").trim();/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */var ys={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/** + */var Cs={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const _s=C.forwardRef(({color:e="currentColor",size:t=24,strokeWidth:n=2,absoluteStrokeWidth:a,className:r="",children:s,iconNode:i,...o},h)=>C.createElement("svg",{ref:h,...ys,width:t,height:t,stroke:e,strokeWidth:a?Number(n)*24/Number(t):n,className:Na("lucide",r),...o},[...i.map(([f,d])=>C.createElement(f,d)),...Array.isArray(s)?s:[s]]));/** + */const xs=C.forwardRef(({color:e="currentColor",size:t=24,strokeWidth:n=2,absoluteStrokeWidth:a,className:r="",children:s,iconNode:i,...o},d)=>C.createElement("svg",{ref:d,...Cs,width:t,height:t,stroke:e,strokeWidth:a?Number(n)*24/Number(t):n,className:Fa("lucide",r),...o},[...i.map(([f,h])=>C.createElement(f,h)),...Array.isArray(s)?s:[s]]));/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const I=(e,t)=>{const n=C.forwardRef(({className:a,...r},s)=>C.createElement(_s,{ref:s,iconNode:t,className:Na(`lucide-${ws(e)}`,a),...r}));return n.displayName=`${e}`,n};/** + */const $=(e,t)=>{const n=C.forwardRef(({className:a,...r},s)=>C.createElement(xs,{ref:s,iconNode:t,className:Fa(`lucide-${Ss(e)}`,a),...r}));return n.displayName=`${e}`,n};/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Ss=I("Activity",[["path",{d:"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2",key:"169zse"}]]);/** + */const ks=$("Activity",[["path",{d:"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2",key:"169zse"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Cs=I("ArrowUp",[["path",{d:"m5 12 7-7 7 7",key:"hav0vg"}],["path",{d:"M12 19V5",key:"x0mq9r"}]]);/** + */const Ts=$("ArrowUp",[["path",{d:"m5 12 7-7 7 7",key:"hav0vg"}],["path",{d:"M12 19V5",key:"x0mq9r"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const xs=I("BookOpen",[["path",{d:"M12 7v14",key:"1akyts"}],["path",{d:"M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z",key:"ruj8y"}]]);/** + */const Ls=$("BookOpen",[["path",{d:"M12 7v14",key:"1akyts"}],["path",{d:"M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z",key:"ruj8y"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const ks=I("Boxes",[["path",{d:"M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z",key:"lc1i9w"}],["path",{d:"m7 16.5-4.74-2.85",key:"1o9zyk"}],["path",{d:"m7 16.5 5-3",key:"va8pkn"}],["path",{d:"M7 16.5v5.17",key:"jnp8gn"}],["path",{d:"M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z",key:"8zsnat"}],["path",{d:"m17 16.5-5-3",key:"8arw3v"}],["path",{d:"m17 16.5 4.74-2.85",key:"8rfmw"}],["path",{d:"M17 16.5v5.17",key:"k6z78m"}],["path",{d:"M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z",key:"1xygjf"}],["path",{d:"M12 8 7.26 5.15",key:"1vbdud"}],["path",{d:"m12 8 4.74-2.85",key:"3rx089"}],["path",{d:"M12 13.5V8",key:"1io7kd"}]]);/** + */const Rs=$("Boxes",[["path",{d:"M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z",key:"lc1i9w"}],["path",{d:"m7 16.5-4.74-2.85",key:"1o9zyk"}],["path",{d:"m7 16.5 5-3",key:"va8pkn"}],["path",{d:"M7 16.5v5.17",key:"jnp8gn"}],["path",{d:"M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z",key:"8zsnat"}],["path",{d:"m17 16.5-5-3",key:"8arw3v"}],["path",{d:"m17 16.5 4.74-2.85",key:"8rfmw"}],["path",{d:"M17 16.5v5.17",key:"k6z78m"}],["path",{d:"M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z",key:"1xygjf"}],["path",{d:"M12 8 7.26 5.15",key:"1vbdud"}],["path",{d:"m12 8 4.74-2.85",key:"3rx089"}],["path",{d:"M12 13.5V8",key:"1io7kd"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Ls=I("BrainCircuit",[["path",{d:"M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z",key:"l5xja"}],["path",{d:"M9 13a4.5 4.5 0 0 0 3-4",key:"10igwf"}],["path",{d:"M6.003 5.125A3 3 0 0 0 6.401 6.5",key:"105sqy"}],["path",{d:"M3.477 10.896a4 4 0 0 1 .585-.396",key:"ql3yin"}],["path",{d:"M6 18a4 4 0 0 1-1.967-.516",key:"2e4loj"}],["path",{d:"M12 13h4",key:"1ku699"}],["path",{d:"M12 18h6a2 2 0 0 1 2 2v1",key:"105ag5"}],["path",{d:"M12 8h8",key:"1lhi5i"}],["path",{d:"M16 8V5a2 2 0 0 1 2-2",key:"u6izg6"}],["circle",{cx:"16",cy:"13",r:".5",key:"ry7gng"}],["circle",{cx:"18",cy:"3",r:".5",key:"1aiba7"}],["circle",{cx:"20",cy:"21",r:".5",key:"yhc1fs"}],["circle",{cx:"20",cy:"8",r:".5",key:"1e43v0"}]]);/** + */const Ms=$("BrainCircuit",[["path",{d:"M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z",key:"l5xja"}],["path",{d:"M9 13a4.5 4.5 0 0 0 3-4",key:"10igwf"}],["path",{d:"M6.003 5.125A3 3 0 0 0 6.401 6.5",key:"105sqy"}],["path",{d:"M3.477 10.896a4 4 0 0 1 .585-.396",key:"ql3yin"}],["path",{d:"M6 18a4 4 0 0 1-1.967-.516",key:"2e4loj"}],["path",{d:"M12 13h4",key:"1ku699"}],["path",{d:"M12 18h6a2 2 0 0 1 2 2v1",key:"105ag5"}],["path",{d:"M12 8h8",key:"1lhi5i"}],["path",{d:"M16 8V5a2 2 0 0 1 2-2",key:"u6izg6"}],["circle",{cx:"16",cy:"13",r:".5",key:"ry7gng"}],["circle",{cx:"18",cy:"3",r:".5",key:"1aiba7"}],["circle",{cx:"20",cy:"21",r:".5",key:"yhc1fs"}],["circle",{cx:"20",cy:"8",r:".5",key:"1e43v0"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Rs=I("ChartColumn",[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16",key:"c24i48"}],["path",{d:"M18 17V9",key:"2bz60n"}],["path",{d:"M13 17V5",key:"1frdt8"}],["path",{d:"M8 17v-3",key:"17ska0"}]]);/** + */const Ps=$("ChartColumn",[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16",key:"c24i48"}],["path",{d:"M18 17V9",key:"2bz60n"}],["path",{d:"M13 17V5",key:"1frdt8"}],["path",{d:"M8 17v-3",key:"17ska0"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Ts=I("Copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]]);/** + */const js=$("Copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Ms=I("Database",[["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3",key:"msslwz"}],["path",{d:"M3 5V19A9 3 0 0 0 21 19V5",key:"1wlel7"}],["path",{d:"M3 12A9 3 0 0 0 21 12",key:"mv7ke4"}]]);/** + */const As=$("Database",[["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3",key:"msslwz"}],["path",{d:"M3 5V19A9 3 0 0 0 21 19V5",key:"1wlel7"}],["path",{d:"M3 12A9 3 0 0 0 21 12",key:"mv7ke4"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Ps=I("Download",[["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["polyline",{points:"7 10 12 15 17 10",key:"2ggqvy"}],["line",{x1:"12",x2:"12",y1:"15",y2:"3",key:"1vk2je"}]]);/** + */const Ns=$("Download",[["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["polyline",{points:"7 10 12 15 17 10",key:"2ggqvy"}],["line",{x1:"12",x2:"12",y1:"15",y2:"3",key:"1vk2je"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const js=I("FileText",[["path",{d:"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z",key:"1rqfz7"}],["path",{d:"M14 2v4a2 2 0 0 0 2 2h4",key:"tnqrlb"}],["path",{d:"M10 9H8",key:"b1mrlr"}],["path",{d:"M16 13H8",key:"t4e002"}],["path",{d:"M16 17H8",key:"z1uh3a"}]]);/** + */const Es=$("FileText",[["path",{d:"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z",key:"1rqfz7"}],["path",{d:"M14 2v4a2 2 0 0 0 2 2h4",key:"tnqrlb"}],["path",{d:"M10 9H8",key:"b1mrlr"}],["path",{d:"M16 13H8",key:"t4e002"}],["path",{d:"M16 17H8",key:"z1uh3a"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const As=I("Filter",[["polygon",{points:"22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3",key:"1yg77f"}]]);/** + */const Fs=$("Filter",[["polygon",{points:"22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3",key:"1yg77f"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Ns=I("FlaskConical",[["path",{d:"M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2",key:"18mbvz"}],["path",{d:"M6.453 15h11.094",key:"3shlmq"}],["path",{d:"M8.5 2h7",key:"csnxdl"}]]);/** + */const Ds=$("FlaskConical",[["path",{d:"M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2",key:"18mbvz"}],["path",{d:"M6.453 15h11.094",key:"3shlmq"}],["path",{d:"M8.5 2h7",key:"csnxdl"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Es=I("House",[["path",{d:"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8",key:"5wwlr5"}],["path",{d:"M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",key:"1d0kgt"}]]);/** + */const $s=$("House",[["path",{d:"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8",key:"5wwlr5"}],["path",{d:"M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",key:"1d0kgt"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Ds=I("RefreshCw",[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]]);/** + */const Is=$("RefreshCw",[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Fs=I("Settings",[["path",{d:"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z",key:"1qme2f"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]]);/** + */const Os=$("Settings",[["path",{d:"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z",key:"1qme2f"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Is=I("ShieldAlert",[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"M12 8v4",key:"1got3b"}],["path",{d:"M12 16h.01",key:"1drbdi"}]]);/** + */const Us=$("ShieldAlert",[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"M12 8v4",key:"1got3b"}],["path",{d:"M12 16h.01",key:"1drbdi"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const $s=I("Table2",[["path",{d:"M9 3H5a2 2 0 0 0-2 2v4m6-6h10a2 2 0 0 1 2 2v4M9 3v18m0 0h10a2 2 0 0 0 2-2V9M9 21H5a2 2 0 0 1-2-2V9m0 0h18",key:"gugj83"}]]);/** + */const Vs=$("Table2",[["path",{d:"M9 3H5a2 2 0 0 0-2 2v4m6-6h10a2 2 0 0 1 2 2v4M9 3v18m0 0h10a2 2 0 0 0 2-2V9M9 21H5a2 2 0 0 1-2-2V9m0 0h18",key:"gugj83"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Os=I("Terminal",[["polyline",{points:"4 17 10 11 4 5",key:"akl6gq"}],["line",{x1:"12",x2:"20",y1:"19",y2:"19",key:"q2wloq"}]]);/** + */const Ks=$("Terminal",[["polyline",{points:"4 17 10 11 4 5",key:"akl6gq"}],["line",{x1:"12",x2:"20",y1:"19",y2:"19",key:"q2wloq"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Us=I("TimerReset",[["path",{d:"M10 2h4",key:"n1abiw"}],["path",{d:"M12 14v-4",key:"1evpnu"}],["path",{d:"M4 13a8 8 0 0 1 8-7 8 8 0 1 1-5.3 14L4 17.6",key:"1ts96g"}],["path",{d:"M9 17H4v5",key:"8t5av"}]]);/** + */const qs=$("TimerReset",[["path",{d:"M10 2h4",key:"n1abiw"}],["path",{d:"M12 14v-4",key:"1evpnu"}],["path",{d:"M4 13a8 8 0 0 1 8-7 8 8 0 1 1-5.3 14L4 17.6",key:"1ts96g"}],["path",{d:"M9 17H4v5",key:"8t5av"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Vs=I("Workflow",[["rect",{width:"8",height:"8",x:"3",y:"3",rx:"2",key:"by2w9f"}],["path",{d:"M7 11v4a2 2 0 0 0 2 2h4",key:"xkn7yn"}],["rect",{width:"8",height:"8",x:"13",y:"13",rx:"2",key:"1cgmvn"}]]);/** + */const Hs=$("Workflow",[["rect",{width:"8",height:"8",x:"3",y:"3",rx:"2",key:"by2w9f"}],["path",{d:"M7 11v4a2 2 0 0 0 2 2h4",key:"xkn7yn"}],["rect",{width:"8",height:"8",x:"13",y:"13",rx:"2",key:"1cgmvn"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const un=I("X",[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]]),Ea="codex-usage-dashboard-language",Ks={"button.back_to_dashboard":"Back to dashboard","button.clear":"Clear","button.copy_link":"Copy link","button.enable_context_loading":"Enable context loading","button.export_csv":"Export CSV","button.full_serialized_analysis":"Run full serialized analysis","button.hide_details":"Hide details","button.include_tool_output":"Include tool output","button.load_more":"Load more","button.load_older_context":"Load older entries","button.next_call":"Next call","button.no_char_limit":"No char limit","button.open_investigator":"Open investigator","button.previous_call":"Previous call","button.refresh":"Refresh","button.show_compaction_history":"Show compacted replacement","button.show_tool_output":"Show tool output","button.top":"Top","button.show_turn_evidence":"Show turn log evidence","badge.live":"Live","dashboard.eyebrow":"Local Codex analytics","dashboard.call_details":"Call Details","dashboard.title":"Usage Dashboard","dashboard.view.call":"Call Investigator","dashboard.view.calls":"Calls","dashboard.view.insights":"Overview","dashboard.view.overview":"Overview","dashboard.view.threads":"Threads","detail.next_action":"Next action","filter.search":"Search dashboard","filter.search_placeholder":"Search calls, threads, models, diagnostics...","language.label":"Language","nav.history":"History","nav.load":"Load","nav.live":"Live","option.active_sessions_only":"Active","option.all_history":"All history","status.paused":"Paused","status.static":"Static"},qs={overview:"dashboard.view.overview",calls:"dashboard.view.calls",call:"dashboard.view.call",threads:"dashboard.view.threads"};function jl(e,t){return typeof t=="string"?e.translateText(t):e.formatText(t.template,t.values)}function Da(e,t){const n=Fa(e),a=Ia(t,n),r=(e==null?void 0:e.translation_catalog)??{},s=r[a]??((e==null?void 0:e.language)===a?e==null?void 0:e.translations:void 0)??{},i=r.en??((e==null?void 0:e.language)==="en"?e.translations:void 0)??{},o={...Ks,...i,...s},h=Qs(i,s),f=Hs(i,s),d=n.find(p=>p.code===a),v=(d==null?void 0:d.dir)==="rtl"||!d&&(e==null?void 0:e.language_direction)==="rtl"?"rtl":"ltr",u=p=>a==="zh-Hans"?bs(p,h,f):h.get(p)??p;return{language:a,direction:v,languages:n,t:(p,g)=>o[p]??g??p,translateText:u,formatText:(p,g)=>u(p).replace(/\{([A-Za-z][A-Za-z0-9_]*)\}/gu,(b,w)=>String(g[String(w)]??b)),navLabel:(p,g)=>{const b=qs[p];return b?o[b]??g:g}}}function Hs(e,t){const n=[];for(const[a,r]of Object.entries(e)){const s=t[a];if(!s||s===r||!r.includes("{"))continue;const i=[],o=[];let h=0;for(const f of r.matchAll(/\{([A-Za-z][A-Za-z0-9_]*)\}/gu)){const d=f.index??h;o.push(Hn(r.slice(h,d))),o.push("(.+?)"),i.push(f[1]),h=d+f[0].length}o.push(Hn(r.slice(h))),n.push({pattern:new RegExp(`^${o.join("")}$`,"u"),placeholders:i,translatedTemplate:s})}return n.sort((a,r)=>r.translatedTemplate.length-a.translatedTemplate.length)}function Hn(e){return e.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")}function Qs(e,t){const n=new Map;for(const[a,r]of Object.entries(e)){const s=t[a];s&&s!==r&&n.set(r,s)}return n}function Fa(e){var n;const t=((n=e==null?void 0:e.available_languages)==null?void 0:n.filter(a=>a.code))??[];return t.length?t:[{code:"en",english_name:"English",native_name:"English",dir:"ltr"}]}function Ws(e){return Ia(Bs()||(e==null?void 0:e.language)||"en",Fa(e))}function zs(e){var t;try{(t=window.localStorage)==null||t.setItem(Ea,e)}catch{}}function Bs(){var e;try{return((e=window.localStorage)==null?void 0:e.getItem(Ea))??""}catch{return""}}function Ia(e,t){if(new Set(t.map(s=>s.code)).has(e))return e;const a=e.toLowerCase(),r=t.find(s=>s.code.toLowerCase()===a);return(r==null?void 0:r.code)??"en"}const Gs=Da(null,"en"),$a=C.createContext(Gs);function Js({value:e,children:t}){return c.jsx($a.Provider,{value:e,children:t})}function Oa(){return C.useContext($a)}var Et=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(e){return this.listeners.add(e),this.onSubscribe(),()=>{this.listeners.delete(e),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}},Ne,ve,Be,xa,Zs=(xa=class extends Et{constructor(){super();P(this,Ne);P(this,ve);P(this,Be);x(this,Be,t=>{if(typeof window<"u"&&window.addEventListener){const n=()=>t();return window.addEventListener("visibilitychange",n,!1),()=>{window.removeEventListener("visibilitychange",n)}}})}onSubscribe(){l(this,ve)||this.setEventListener(l(this,Be))}onUnsubscribe(){var t;this.hasListeners()||((t=l(this,ve))==null||t.call(this),x(this,ve,void 0))}setEventListener(t){var n;x(this,Be,t),(n=l(this,ve))==null||n.call(this),x(this,ve,t(a=>{typeof a=="boolean"?this.setFocused(a):this.onFocus()}))}setFocused(t){l(this,Ne)!==t&&(x(this,Ne,t),this.onFocus())}onFocus(){const t=this.isFocused();this.listeners.forEach(n=>{n(t)})}isFocused(){var t;return typeof l(this,Ne)=="boolean"?l(this,Ne):((t=globalThis.document)==null?void 0:t.visibilityState)!=="hidden"}},Ne=new WeakMap,ve=new WeakMap,Be=new WeakMap,xa),Ua=new Zs,Xs={setTimeout:(e,t)=>setTimeout(e,t),clearTimeout:e=>clearTimeout(e),setInterval:(e,t)=>setInterval(e,t),clearInterval:e=>clearInterval(e)},we,ln,ka,Ys=(ka=class{constructor(){P(this,we,Xs);P(this,ln,!1)}setTimeoutProvider(e){x(this,we,e)}setTimeout(e,t){return l(this,we).setTimeout(e,t)}clearTimeout(e){l(this,we).clearTimeout(e)}setInterval(e,t){return l(this,we).setInterval(e,t)}clearInterval(e){l(this,we).clearInterval(e)}},we=new WeakMap,ln=new WeakMap,ka),Bt=new Ys;function ei(e){setTimeout(e,0)}var ti=typeof window>"u"||"Deno"in globalThis;function ne(){}function ni(e,t){return typeof e=="function"?e(t):e}function ai(e){return typeof e=="number"&&e>=0&&e!==1/0}function ri(e,t){return Math.max(e+(t||0)-Date.now(),0)}function Gt(e,t){return typeof e=="function"?e(t):e}function si(e,t){return typeof e=="function"?e(t):e}function Qn(e,t){const{type:n="all",exact:a,fetchStatus:r,predicate:s,queryKey:i,stale:o}=e;if(i){if(a){if(t.queryHash!==dn(i,t.options))return!1}else if(!ut(t.queryKey,i))return!1}if(n!=="all"){const h=t.isActive();if(n==="active"&&!h||n==="inactive"&&h)return!1}return!(typeof o=="boolean"&&t.isStale()!==o||r&&r!==t.state.fetchStatus||s&&!s(t))}function Wn(e,t){const{exact:n,status:a,predicate:r,mutationKey:s}=e;if(s){if(!t.options.mutationKey)return!1;if(n){if(lt(t.options.mutationKey)!==lt(s))return!1}else if(!ut(t.options.mutationKey,s))return!1}return!(a&&t.state.status!==a||r&&!r(t))}function dn(e,t){return((t==null?void 0:t.queryKeyHashFn)||lt)(e)}function lt(e){return JSON.stringify(e,(t,n)=>Jt(n)?Object.keys(n).sort().reduce((a,r)=>(a[r]=n[r],a),{}):n)}function ut(e,t){return e===t?!0:typeof e!=typeof t?!1:e&&t&&typeof e=="object"&&typeof t=="object"?Object.keys(t).every(n=>ut(e[n],t[n])):!1}var ii=Object.prototype.hasOwnProperty;function Va(e,t,n=0){if(e===t)return e;if(n>500)return t;const a=zn(e)&&zn(t);if(!a&&!(Jt(e)&&Jt(t)))return t;const s=(a?e:Object.keys(e)).length,i=a?t:Object.keys(t),o=i.length,h=a?new Array(o):{};let f=0;for(let d=0;d{Bt.setTimeout(t,e)})}function ci(e,t,n){return typeof n.structuralSharing=="function"?n.structuralSharing(e,t):n.structuralSharing!==!1?Va(e,t):t}function li(e,t,n=0){const a=[...e,t];return n&&a.length>n?a.slice(1):a}function ui(e,t,n=0){const a=[t,...e];return n&&a.length>n?a.slice(0,-1):a}var hn=Symbol();function Ka(e,t){return!e.queryFn&&(t!=null&&t.initialPromise)?()=>t.initialPromise:!e.queryFn||e.queryFn===hn?()=>Promise.reject(new Error(`Missing queryFn: '${e.queryHash}'`)):e.queryFn}function Nl(e,t){return typeof e=="function"?e(...t):!!e}function di(e,t,n){let a=!1,r;return Object.defineProperty(e,"signal",{enumerable:!0,get:()=>(r??(r=t()),a||(a=!0,r.aborted?n():r.addEventListener("abort",n,{once:!0})),r)}),e}var qa=(()=>{let e=()=>ti;return{isServer(){return e()},setIsServer(t){e=t}}})();function hi(){let e,t;const n=new Promise((r,s)=>{e=r,t=s});n.status="pending",n.catch(()=>{});function a(r){Object.assign(n,r),delete n.resolve,delete n.reject}return n.resolve=r=>{a({status:"fulfilled",value:r}),e(r)},n.reject=r=>{a({status:"rejected",reason:r}),t(r)},n}var fi=ei;function mi(){let e=[],t=0,n=o=>{o()},a=o=>{o()},r=fi;const s=o=>{t?e.push(o):r(()=>{n(o)})},i=()=>{const o=e;e=[],o.length&&r(()=>{a(()=>{o.forEach(h=>{n(h)})})})};return{batch:o=>{let h;t++;try{h=o()}finally{t--,t||i()}return h},batchCalls:o=>(...h)=>{s(()=>{o(...h)})},schedule:s,setNotifyFunction:o=>{n=o},setBatchNotifyFunction:o=>{a=o},setScheduler:o=>{r=o}}}var z=mi(),Ge,ye,Je,La,gi=(La=class extends Et{constructor(){super();P(this,Ge,!0);P(this,ye);P(this,Je);x(this,Je,t=>{if(typeof window<"u"&&window.addEventListener){const n=()=>t(!0),a=()=>t(!1);return window.addEventListener("online",n,!1),window.addEventListener("offline",a,!1),()=>{window.removeEventListener("online",n),window.removeEventListener("offline",a)}}})}onSubscribe(){l(this,ye)||this.setEventListener(l(this,Je))}onUnsubscribe(){var t;this.hasListeners()||((t=l(this,ye))==null||t.call(this),x(this,ye,void 0))}setEventListener(t){var n;x(this,Je,t),(n=l(this,ye))==null||n.call(this),x(this,ye,t(this.setOnline.bind(this)))}setOnline(t){l(this,Ge)!==t&&(x(this,Ge,t),this.listeners.forEach(a=>{a(t)}))}isOnline(){return l(this,Ge)}},Ge=new WeakMap,ye=new WeakMap,Je=new WeakMap,La),kt=new gi;function pi(e){return Math.min(1e3*2**e,3e4)}function Ha(e){return(e??"online")==="online"?kt.isOnline():!0}var Lt=class extends Error{constructor(e){super("CancelledError"),this.revert=e==null?void 0:e.revert,this.silent=e==null?void 0:e.silent}};function bi(e){return e instanceof Lt}function Qa(e){let t=!1,n=0,a;const r=hi(),s=()=>r.status!=="pending",i=b=>{var w;if(!s()){const L=new Lt(b);u(L),(w=e.onCancel)==null||w.call(e,L)}},o=()=>{t=!0},h=()=>{t=!1},f=()=>Ua.isFocused()&&(e.networkMode==="always"||kt.isOnline())&&e.canRun(),d=()=>Ha(e.networkMode)&&e.canRun(),v=b=>{s()||(a==null||a(),r.resolve(b))},u=b=>{s()||(a==null||a(),r.reject(b))},p=()=>new Promise(b=>{var w;a=L=>{(s()||f())&&b(L)},(w=e.onPause)==null||w.call(e)}).then(()=>{var b;a=void 0,s()||(b=e.onContinue)==null||b.call(e)}),g=()=>{if(s())return;let b;const w=n===0?e.initialPromise:void 0;try{b=w??e.fn()}catch(L){b=Promise.reject(L)}Promise.resolve(b).then(v).catch(L=>{var y;if(s())return;const N=e.retry??(qa.isServer()?0:3),T=e.retryDelay??pi,_=typeof T=="function"?T(n,L):T,R=N===!0||typeof N=="number"&&nf()?void 0:p()).then(()=>{t?u(L):g()})})};return{promise:r,status:()=>r.status,cancel:i,continue:()=>(a==null||a(),r),cancelRetry:o,continueRetry:h,canStart:d,start:()=>(d()?g():p().then(g),r)}}var Ee,Ra,Wa=(Ra=class{constructor(){P(this,Ee)}destroy(){this.clearGcTimeout()}scheduleGc(){this.clearGcTimeout(),ai(this.gcTime)&&x(this,Ee,Bt.setTimeout(()=>{this.optionalRemove()},this.gcTime))}updateGcTime(e){this.gcTime=Math.max(this.gcTime||0,e??(qa.isServer()?1/0:300*1e3))}clearGcTimeout(){l(this,Ee)!==void 0&&(Bt.clearTimeout(l(this,Ee)),x(this,Ee,void 0))}},Ee=new WeakMap,Ra);function vi(e){return{onFetch:(t,n)=>{var d,v,u,p,g;const a=t.options,r=(u=(v=(d=t.fetchOptions)==null?void 0:d.meta)==null?void 0:v.fetchMore)==null?void 0:u.direction,s=((p=t.state.data)==null?void 0:p.pages)||[],i=((g=t.state.data)==null?void 0:g.pageParams)||[];let o={pages:[],pageParams:[]},h=0;const f=async()=>{let b=!1;const w=T=>{di(T,()=>t.signal,()=>b=!0)},L=Ka(t.options,t.fetchOptions),N=async(T,_,R)=>{if(b)return Promise.reject(t.signal.reason);if(_==null&&T.pages.length)return Promise.resolve(T);const k=(()=>{const me={client:t.client,queryKey:t.queryKey,pageParam:_,direction:R?"backward":"forward",meta:t.options.meta};return w(me),me})(),j=await L(k),{maxPages:E}=t.options,D=R?ui:li;return{pages:D(T.pages,j,E),pageParams:D(T.pageParams,_,E)}};if(r&&s.length){const T=r==="backward",_=T?za:Zt,R={pages:s,pageParams:i},y=_(a,R);o=await N(R,y,T)}else{const T=e??s.length;do{const _=h===0?i[0]??a.initialPageParam:Zt(a,o);if(h>0&&_==null)break;o=await N(o,_),h++}while(h{var b,w;return(w=(b=t.options).persister)==null?void 0:w.call(b,f,{client:t.client,queryKey:t.queryKey,meta:t.options.meta,signal:t.signal},n)}:t.fetchFn=f}}}function Zt(e,{pages:t,pageParams:n}){const a=t.length-1;return t.length>0?e.getNextPageParam(t[a],t,n[a],n):void 0}function za(e,{pages:t,pageParams:n}){var a;return t.length>0?(a=e.getPreviousPageParam)==null?void 0:a.call(e,t[0],t,n[0],n):void 0}function El(e,t){return t?Zt(e,t)!=null:!1}function Dl(e,t){return!t||!e.getPreviousPageParam?!1:za(e,t)!=null}var Ze,De,Xe,X,Fe,U,ht,Ie,Z,Ba,he,Ta,wi=(Ta=class extends Wa{constructor(t){super();P(this,Z);P(this,Ze);P(this,De);P(this,Xe);P(this,X);P(this,Fe);P(this,U);P(this,ht);P(this,Ie);x(this,Ie,!1),x(this,ht,t.defaultOptions),this.setOptions(t.options),this.observers=[],x(this,Fe,t.client),x(this,X,l(this,Fe).getQueryCache()),this.queryKey=t.queryKey,this.queryHash=t.queryHash,x(this,De,Jn(this.options)),this.state=t.state??l(this,De),this.scheduleGc()}get meta(){return this.options.meta}get queryType(){return l(this,Ze)}get promise(){var t;return(t=l(this,U))==null?void 0:t.promise}setOptions(t){if(this.options={...l(this,ht),...t},t!=null&&t._type&&x(this,Ze,t._type),this.updateGcTime(this.options.gcTime),this.state&&this.state.data===void 0){const n=Jn(this.options);n.data!==void 0&&(this.setState(Gn(n.data,n.dataUpdatedAt)),x(this,De,n))}}optionalRemove(){!this.observers.length&&this.state.fetchStatus==="idle"&&l(this,X).remove(this)}setData(t,n){const a=ci(this.state.data,t,this.options);return V(this,Z,he).call(this,{data:a,type:"success",dataUpdatedAt:n==null?void 0:n.updatedAt,manual:n==null?void 0:n.manual}),a}setState(t){V(this,Z,he).call(this,{type:"setState",state:t})}cancel(t){var a,r;const n=(a=l(this,U))==null?void 0:a.promise;return(r=l(this,U))==null||r.cancel(t),n?n.then(ne).catch(ne):Promise.resolve()}destroy(){super.destroy(),this.cancel({silent:!0})}get resetState(){return l(this,De)}reset(){this.destroy(),this.setState(this.resetState)}isActive(){return this.observers.some(t=>si(t.options.enabled,this)!==!1)}isDisabled(){return this.getObserversCount()>0?!this.isActive():this.options.queryFn===hn||!this.isFetched()}isFetched(){return this.state.dataUpdateCount+this.state.errorUpdateCount>0}isStatic(){return this.getObserversCount()>0?this.observers.some(t=>Gt(t.options.staleTime,this)==="static"):!1}isStale(){return this.getObserversCount()>0?this.observers.some(t=>t.getCurrentResult().isStale):this.state.data===void 0||this.state.isInvalidated}isStaleByTime(t=0){return this.state.data===void 0?!0:t==="static"?!1:this.state.isInvalidated?!0:!ri(this.state.dataUpdatedAt,t)}onFocus(){var n;const t=this.observers.find(a=>a.shouldFetchOnWindowFocus());t==null||t.refetch({cancelRefetch:!1}),(n=l(this,U))==null||n.continue()}onOnline(){var n;const t=this.observers.find(a=>a.shouldFetchOnReconnect());t==null||t.refetch({cancelRefetch:!1}),(n=l(this,U))==null||n.continue()}addObserver(t){this.observers.includes(t)||(this.observers.push(t),this.clearGcTimeout(),l(this,X).notify({type:"observerAdded",query:this,observer:t}))}removeObserver(t){this.observers.includes(t)&&(this.observers=this.observers.filter(n=>n!==t),this.observers.length||(l(this,U)&&(l(this,Ie)||V(this,Z,Ba).call(this)?l(this,U).cancel({revert:!0}):l(this,U).cancelRetry()),this.scheduleGc()),l(this,X).notify({type:"observerRemoved",query:this,observer:t}))}getObserversCount(){return this.observers.length}invalidate(){this.state.isInvalidated||V(this,Z,he).call(this,{type:"invalidate"})}async fetch(t,n){var f,d,v,u,p,g,b,w,L,N,T;if(this.state.fetchStatus!=="idle"&&((f=l(this,U))==null?void 0:f.status())!=="rejected"){if(this.state.data!==void 0&&(n!=null&&n.cancelRefetch))this.cancel({silent:!0});else if(l(this,U))return l(this,U).continueRetry(),l(this,U).promise}if(t&&this.setOptions(t),!this.options.queryFn){const _=this.observers.find(R=>R.options.queryFn);_&&this.setOptions(_.options)}const a=new AbortController,r=_=>{Object.defineProperty(_,"signal",{enumerable:!0,get:()=>(x(this,Ie,!0),a.signal)})},s=()=>{const _=Ka(this.options,n),y=(()=>{const k={client:l(this,Fe),queryKey:this.queryKey,meta:this.meta};return r(k),k})();return x(this,Ie,!1),this.options.persister?this.options.persister(_,y,this):_(y)},o=(()=>{const _={fetchOptions:n,options:this.options,queryKey:this.queryKey,client:l(this,Fe),state:this.state,fetchFn:s};return r(_),_})(),h=l(this,Ze)==="infinite"?vi(this.options.pages):this.options.behavior;h==null||h.onFetch(o,this),x(this,Xe,this.state),(this.state.fetchStatus==="idle"||this.state.fetchMeta!==((d=o.fetchOptions)==null?void 0:d.meta))&&V(this,Z,he).call(this,{type:"fetch",meta:(v=o.fetchOptions)==null?void 0:v.meta}),x(this,U,Qa({initialPromise:n==null?void 0:n.initialPromise,fn:o.fetchFn,onCancel:_=>{_ instanceof Lt&&_.revert&&this.setState({...l(this,Xe),fetchStatus:"idle"}),a.abort()},onFail:(_,R)=>{V(this,Z,he).call(this,{type:"failed",failureCount:_,error:R})},onPause:()=>{V(this,Z,he).call(this,{type:"pause"})},onContinue:()=>{V(this,Z,he).call(this,{type:"continue"})},retry:o.options.retry,retryDelay:o.options.retryDelay,networkMode:o.options.networkMode,canRun:()=>!0}));try{const _=await l(this,U).start();if(_===void 0)throw new Error(`${this.queryHash} data is undefined`);return this.setData(_),(p=(u=l(this,X).config).onSuccess)==null||p.call(u,_,this),(b=(g=l(this,X).config).onSettled)==null||b.call(g,_,this.state.error,this),_}catch(_){if(_ instanceof Lt){if(_.silent)return l(this,U).promise;if(_.revert){if(this.state.data===void 0)throw _;return this.state.data}}throw V(this,Z,he).call(this,{type:"error",error:_}),(L=(w=l(this,X).config).onError)==null||L.call(w,_,this),(T=(N=l(this,X).config).onSettled)==null||T.call(N,this.state.data,_,this),_}finally{this.scheduleGc()}}},Ze=new WeakMap,De=new WeakMap,Xe=new WeakMap,X=new WeakMap,Fe=new WeakMap,U=new WeakMap,ht=new WeakMap,Ie=new WeakMap,Z=new WeakSet,Ba=function(){return this.state.fetchStatus==="paused"&&this.state.status==="pending"},he=function(t){const n=a=>{switch(t.type){case"failed":return{...a,fetchFailureCount:t.failureCount,fetchFailureReason:t.error};case"pause":return{...a,fetchStatus:"paused"};case"continue":return{...a,fetchStatus:"fetching"};case"fetch":return{...a,...yi(a.data,this.options),fetchMeta:t.meta??null};case"success":const r={...a,...Gn(t.data,t.dataUpdatedAt),dataUpdateCount:a.dataUpdateCount+1,...!t.manual&&{fetchStatus:"idle",fetchFailureCount:0,fetchFailureReason:null}};return x(this,Xe,t.manual?r:void 0),r;case"error":const s=t.error;return{...a,error:s,errorUpdateCount:a.errorUpdateCount+1,errorUpdatedAt:Date.now(),fetchFailureCount:a.fetchFailureCount+1,fetchFailureReason:s,fetchStatus:"idle",status:"error",isInvalidated:!0};case"invalidate":return{...a,isInvalidated:!0};case"setState":return{...a,...t.state}}};this.state=n(this.state),z.batch(()=>{this.observers.forEach(a=>{a.onQueryUpdate()}),l(this,X).notify({query:this,type:"updated",action:t})})},Ta);function yi(e,t){return{fetchFailureCount:0,fetchFailureReason:null,fetchStatus:Ha(t.networkMode)?"fetching":"paused",...e===void 0&&{error:null,status:"pending"}}}function Gn(e,t){return{data:e,dataUpdatedAt:t??Date.now(),error:null,isInvalidated:!1,status:"success"}}function Jn(e){const t=typeof e.initialData=="function"?e.initialData():e.initialData,n=t!==void 0,a=n?typeof e.initialDataUpdatedAt=="function"?e.initialDataUpdatedAt():e.initialDataUpdatedAt:0;return{data:t,dataUpdateCount:0,dataUpdatedAt:n?a??Date.now():0,error:null,errorUpdateCount:0,errorUpdatedAt:0,fetchFailureCount:0,fetchFailureReason:null,fetchMeta:null,isInvalidated:!1,status:n?"success":"pending",fetchStatus:"idle"}}var ft,le,K,$e,ue,be,Ma,_i=(Ma=class extends Wa{constructor(t){super();P(this,ue);P(this,ft);P(this,le);P(this,K);P(this,$e);x(this,ft,t.client),this.mutationId=t.mutationId,x(this,K,t.mutationCache),x(this,le,[]),this.state=t.state||Si(),this.setOptions(t.options),this.scheduleGc()}setOptions(t){this.options=t,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(t){l(this,le).includes(t)||(l(this,le).push(t),this.clearGcTimeout(),l(this,K).notify({type:"observerAdded",mutation:this,observer:t}))}removeObserver(t){x(this,le,l(this,le).filter(n=>n!==t)),this.scheduleGc(),l(this,K).notify({type:"observerRemoved",mutation:this,observer:t})}optionalRemove(){l(this,le).length||(this.state.status==="pending"?this.scheduleGc():l(this,K).remove(this))}continue(){var t;return((t=l(this,$e))==null?void 0:t.continue())??this.execute(this.state.variables)}async execute(t){var i,o,h,f,d,v,u,p,g,b,w,L,N,T,_,R,y,k;const n=()=>{V(this,ue,be).call(this,{type:"continue"})},a={client:l(this,ft),meta:this.options.meta,mutationKey:this.options.mutationKey};x(this,$e,Qa({fn:()=>this.options.mutationFn?this.options.mutationFn(t,a):Promise.reject(new Error("No mutationFn found")),onFail:(j,E)=>{V(this,ue,be).call(this,{type:"failed",failureCount:j,error:E})},onPause:()=>{V(this,ue,be).call(this,{type:"pause"})},onContinue:n,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>l(this,K).canRun(this)}));const r=this.state.status==="pending",s=!l(this,$e).canStart();try{if(r)n();else{V(this,ue,be).call(this,{type:"pending",variables:t,isPaused:s}),l(this,K).config.onMutate&&await l(this,K).config.onMutate(t,this,a);const E=await((o=(i=this.options).onMutate)==null?void 0:o.call(i,t,a));E!==this.state.context&&V(this,ue,be).call(this,{type:"pending",context:E,variables:t,isPaused:s})}const j=await l(this,$e).start();return await((f=(h=l(this,K).config).onSuccess)==null?void 0:f.call(h,j,t,this.state.context,this,a)),await((v=(d=this.options).onSuccess)==null?void 0:v.call(d,j,t,this.state.context,a)),await((p=(u=l(this,K).config).onSettled)==null?void 0:p.call(u,j,null,this.state.variables,this.state.context,this,a)),await((b=(g=this.options).onSettled)==null?void 0:b.call(g,j,null,t,this.state.context,a)),V(this,ue,be).call(this,{type:"success",data:j}),j}catch(j){try{await((L=(w=l(this,K).config).onError)==null?void 0:L.call(w,j,t,this.state.context,this,a))}catch(E){Promise.reject(E)}try{await((T=(N=this.options).onError)==null?void 0:T.call(N,j,t,this.state.context,a))}catch(E){Promise.reject(E)}try{await((R=(_=l(this,K).config).onSettled)==null?void 0:R.call(_,void 0,j,this.state.variables,this.state.context,this,a))}catch(E){Promise.reject(E)}try{await((k=(y=this.options).onSettled)==null?void 0:k.call(y,void 0,j,t,this.state.context,a))}catch(E){Promise.reject(E)}throw V(this,ue,be).call(this,{type:"error",error:j}),j}finally{l(this,K).runNext(this)}}},ft=new WeakMap,le=new WeakMap,K=new WeakMap,$e=new WeakMap,ue=new WeakSet,be=function(t){const n=a=>{switch(t.type){case"failed":return{...a,failureCount:t.failureCount,failureReason:t.error};case"pause":return{...a,isPaused:!0};case"continue":return{...a,isPaused:!1};case"pending":return{...a,context:t.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:t.isPaused,status:"pending",variables:t.variables,submittedAt:Date.now()};case"success":return{...a,data:t.data,failureCount:0,failureReason:null,error:null,status:"success",isPaused:!1};case"error":return{...a,data:void 0,error:t.error,failureCount:a.failureCount+1,failureReason:t.error,isPaused:!1,status:"error"}}};this.state=n(this.state),z.batch(()=>{l(this,le).forEach(a=>{a.onMutationUpdate(t)}),l(this,K).notify({mutation:this,type:"updated",action:t})})},Ma);function Si(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:"idle",variables:void 0,submittedAt:0}}var fe,ae,mt,Pa,Ci=(Pa=class extends Et{constructor(t={}){super();P(this,fe);P(this,ae);P(this,mt);this.config=t,x(this,fe,new Set),x(this,ae,new Map),x(this,mt,0)}build(t,n,a){const r=new _i({client:t,mutationCache:this,mutationId:++St(this,mt)._,options:t.defaultMutationOptions(n),state:a});return this.add(r),r}add(t){l(this,fe).add(t);const n=Ct(t);if(typeof n=="string"){const a=l(this,ae).get(n);a?a.push(t):l(this,ae).set(n,[t])}this.notify({type:"added",mutation:t})}remove(t){if(l(this,fe).delete(t)){const n=Ct(t);if(typeof n=="string"){const a=l(this,ae).get(n);if(a)if(a.length>1){const r=a.indexOf(t);r!==-1&&a.splice(r,1)}else a[0]===t&&l(this,ae).delete(n)}}this.notify({type:"removed",mutation:t})}canRun(t){const n=Ct(t);if(typeof n=="string"){const a=l(this,ae).get(n),r=a==null?void 0:a.find(s=>s.state.status==="pending");return!r||r===t}else return!0}runNext(t){var a;const n=Ct(t);if(typeof n=="string"){const r=(a=l(this,ae).get(n))==null?void 0:a.find(s=>s!==t&&s.state.isPaused);return(r==null?void 0:r.continue())??Promise.resolve()}else return Promise.resolve()}clear(){z.batch(()=>{l(this,fe).forEach(t=>{this.notify({type:"removed",mutation:t})}),l(this,fe).clear(),l(this,ae).clear()})}getAll(){return Array.from(l(this,fe))}find(t){const n={exact:!0,...t};return this.getAll().find(a=>Wn(n,a))}findAll(t={}){return this.getAll().filter(n=>Wn(t,n))}notify(t){z.batch(()=>{this.listeners.forEach(n=>{n(t)})})}resumePausedMutations(){const t=this.getAll().filter(n=>n.state.isPaused);return z.batch(()=>Promise.all(t.map(n=>n.continue().catch(ne))))}},fe=new WeakMap,ae=new WeakMap,mt=new WeakMap,Pa);function Ct(e){var t;return(t=e.options.scope)==null?void 0:t.id}var de,ja,xi=(ja=class extends Et{constructor(t={}){super();P(this,de);this.config=t,x(this,de,new Map)}build(t,n,a){const r=n.queryKey,s=n.queryHash??dn(r,n);let i=this.get(s);return i||(i=new wi({client:t,queryKey:r,queryHash:s,options:t.defaultQueryOptions(n),state:a,defaultOptions:t.getQueryDefaults(r)}),this.add(i)),i}add(t){l(this,de).has(t.queryHash)||(l(this,de).set(t.queryHash,t),this.notify({type:"added",query:t}))}remove(t){const n=l(this,de).get(t.queryHash);n&&(t.destroy(),n===t&&l(this,de).delete(t.queryHash),this.notify({type:"removed",query:t}))}clear(){z.batch(()=>{this.getAll().forEach(t=>{this.remove(t)})})}get(t){return l(this,de).get(t)}getAll(){return[...l(this,de).values()]}find(t){const n={exact:!0,...t};return this.getAll().find(a=>Qn(n,a))}findAll(t={}){const n=this.getAll();return Object.keys(t).length>0?n.filter(a=>Qn(t,a)):n}notify(t){z.batch(()=>{this.listeners.forEach(n=>{n(t)})})}onFocus(){z.batch(()=>{this.getAll().forEach(t=>{t.onFocus()})})}onOnline(){z.batch(()=>{this.getAll().forEach(t=>{t.onOnline()})})}},de=new WeakMap,ja),F,_e,Se,Ye,et,Ce,tt,nt,Aa,ki=(Aa=class{constructor(e={}){P(this,F);P(this,_e);P(this,Se);P(this,Ye);P(this,et);P(this,Ce);P(this,tt);P(this,nt);x(this,F,e.queryCache||new xi),x(this,_e,e.mutationCache||new Ci),x(this,Se,e.defaultOptions||{}),x(this,Ye,new Map),x(this,et,new Map),x(this,Ce,0)}mount(){St(this,Ce)._++,l(this,Ce)===1&&(x(this,tt,Ua.subscribe(async e=>{e&&(await this.resumePausedMutations(),l(this,F).onFocus())})),x(this,nt,kt.subscribe(async e=>{e&&(await this.resumePausedMutations(),l(this,F).onOnline())})))}unmount(){var e,t;St(this,Ce)._--,l(this,Ce)===0&&((e=l(this,tt))==null||e.call(this),x(this,tt,void 0),(t=l(this,nt))==null||t.call(this),x(this,nt,void 0))}isFetching(e){return l(this,F).findAll({...e,fetchStatus:"fetching"}).length}isMutating(e){return l(this,_e).findAll({...e,status:"pending"}).length}getQueryData(e){var n;const t=this.defaultQueryOptions({queryKey:e});return(n=l(this,F).get(t.queryHash))==null?void 0:n.state.data}ensureQueryData(e){const t=this.defaultQueryOptions(e),n=l(this,F).build(this,t),a=n.state.data;return a===void 0?this.fetchQuery(e):(e.revalidateIfStale&&n.isStaleByTime(Gt(t.staleTime,n))&&this.prefetchQuery(t),Promise.resolve(a))}getQueriesData(e){return l(this,F).findAll(e).map(({queryKey:t,state:n})=>{const a=n.data;return[t,a]})}setQueryData(e,t,n){const a=this.defaultQueryOptions({queryKey:e}),r=l(this,F).get(a.queryHash),s=r==null?void 0:r.state.data,i=ni(t,s);if(i!==void 0)return l(this,F).build(this,a).setData(i,{...n,manual:!0})}setQueriesData(e,t,n){return z.batch(()=>l(this,F).findAll(e).map(({queryKey:a})=>[a,this.setQueryData(a,t,n)]))}getQueryState(e){var n;const t=this.defaultQueryOptions({queryKey:e});return(n=l(this,F).get(t.queryHash))==null?void 0:n.state}removeQueries(e){const t=l(this,F);z.batch(()=>{t.findAll(e).forEach(n=>{t.remove(n)})})}resetQueries(e,t){const n=l(this,F);return z.batch(()=>(n.findAll(e).forEach(a=>{a.reset()}),this.refetchQueries({type:"active",...e},t)))}cancelQueries(e,t={}){const n={revert:!0,...t},a=z.batch(()=>l(this,F).findAll(e).map(r=>r.cancel(n)));return Promise.all(a).then(ne).catch(ne)}invalidateQueries(e,t={}){return z.batch(()=>(l(this,F).findAll(e).forEach(n=>{n.invalidate()}),(e==null?void 0:e.refetchType)==="none"?Promise.resolve():this.refetchQueries({...e,type:(e==null?void 0:e.refetchType)??(e==null?void 0:e.type)??"active"},t)))}refetchQueries(e,t={}){const n={...t,cancelRefetch:t.cancelRefetch??!0},a=z.batch(()=>l(this,F).findAll(e).filter(r=>!r.isDisabled()&&!r.isStatic()).map(r=>{let s=r.fetch(void 0,n);return n.throwOnError||(s=s.catch(ne)),r.state.fetchStatus==="paused"?Promise.resolve():s}));return Promise.all(a).then(ne)}fetchQuery(e){const t=this.defaultQueryOptions(e);t.retry===void 0&&(t.retry=!1);const n=l(this,F).build(this,t);return n.isStaleByTime(Gt(t.staleTime,n))?n.fetch(t):Promise.resolve(n.state.data)}prefetchQuery(e){return this.fetchQuery(e).then(ne).catch(ne)}fetchInfiniteQuery(e){return e._type="infinite",this.fetchQuery(e)}prefetchInfiniteQuery(e){return this.fetchInfiniteQuery(e).then(ne).catch(ne)}ensureInfiniteQueryData(e){return e._type="infinite",this.ensureQueryData(e)}resumePausedMutations(){return kt.isOnline()?l(this,_e).resumePausedMutations():Promise.resolve()}getQueryCache(){return l(this,F)}getMutationCache(){return l(this,_e)}getDefaultOptions(){return l(this,Se)}setDefaultOptions(e){x(this,Se,e)}setQueryDefaults(e,t){l(this,Ye).set(lt(e),{queryKey:e,defaultOptions:t})}getQueryDefaults(e){const t=[...l(this,Ye).values()],n={};return t.forEach(a=>{ut(e,a.queryKey)&&Object.assign(n,a.defaultOptions)}),n}setMutationDefaults(e,t){l(this,et).set(lt(e),{mutationKey:e,defaultOptions:t})}getMutationDefaults(e){const t=[...l(this,et).values()],n={};return t.forEach(a=>{ut(e,a.mutationKey)&&Object.assign(n,a.defaultOptions)}),n}defaultQueryOptions(e){if(e._defaulted)return e;const t={...l(this,Se).queries,...this.getQueryDefaults(e.queryKey),...e,_defaulted:!0};return t.queryHash||(t.queryHash=dn(t.queryKey,t)),t.refetchOnReconnect===void 0&&(t.refetchOnReconnect=t.networkMode!=="always"),t.throwOnError===void 0&&(t.throwOnError=!!t.suspense),!t.networkMode&&t.persister&&(t.networkMode="offlineFirst"),t.queryFn===hn&&(t.enabled=!1),t}defaultMutationOptions(e){return e!=null&&e._defaulted?e:{...l(this,Se).mutations,...(e==null?void 0:e.mutationKey)&&this.getMutationDefaults(e.mutationKey),...e,_defaulted:!0}}clear(){l(this,F).clear(),l(this,_e).clear()}},F=new WeakMap,_e=new WeakMap,Se=new WeakMap,Ye=new WeakMap,et=new WeakMap,Ce=new WeakMap,tt=new WeakMap,nt=new WeakMap,Aa),Ga=C.createContext(void 0),Fl=e=>{const t=C.useContext(Ga);if(!t)throw new Error("No QueryClient set, use QueryClientProvider to set one");return t},Li=({client:e,children:t})=>(C.useEffect(()=>(e.mount(),()=>{e.unmount()}),[e]),c.jsx(Ga.Provider,{value:e,children:t}));function Ri(e,t=window.location.href){var a;const n=(a=new URL(t).searchParams.get("record"))==null?void 0:a.trim();if(n){const r=e.calls.find(s=>s.id===n);return r?[r]:[]}return e.calls[0]?[e.calls[0]]:[]}function Il({calls:e,recordId:t,detail:n}){const a=e.findIndex(v=>v.id===t),r=a>=0?a:!t&&e.length?0:-1,s=(n==null?void 0:n.record.id)===t?n:null,i=(s==null?void 0:s.record)??(r>=0?e[r]:null),o=(s==null?void 0:s.previousRecord)??(r>0?e[r-1]:null),h=(s==null?void 0:s.nextRecord)??(r>=0&&r=0?`${r+1} of ${e.length} loaded calls`:"Record outside loaded snapshot";return{modelIndex:a,activeIndex:r,hydratedDetail:s,call:i,previous:o,next:h,threadCalls:f,positionLabel:d}}function Ti(e,t,n){const a=e.filter(i=>i.thread===t.thread),r=[n==null?void 0:n.previousRecord,n==null?void 0:n.record,n==null?void 0:n.nextRecord].filter(Mi),s=new Map;for(const i of[...a,...r,t])s.set(i.id,i);return[...s.values()].sort(Pi)}function Mi(e){return!!(e!=null&&e.id)}function Pi(e,t){return Date.parse(t.rawTime||t.time)-Date.parse(e.rawTime||e.time)}function ji(e,t){const n=t.map(r=>Zn(r.header)).join(","),a=e.map(r=>t.map(s=>Zn(s.value(r))).join(","));return[n,...a].join(` -`)}function Ai(e,t){const n=new Blob([t],{type:"text/csv;charset=utf-8"}),a=typeof URL.createObjectURL=="function"?URL.createObjectURL(n):`data:text/csv;charset=utf-8,${encodeURIComponent(t)}`,r=document.createElement("a");r.href=a,r.download=e,r.rel="noopener",document.body.append(r),r.click(),r.remove(),a.startsWith("blob:")&&typeof URL.revokeObjectURL=="function"&&URL.revokeObjectURL(a)}function Ni(e=new Date){return e.toISOString().slice(0,10)}function Zn(e){const t=String(e??"");return/[",\n\r]/.test(t)?`"${t.replaceAll('"','""')}"`:t}function Le(e){return new Intl.NumberFormat("en-US").format(Math.round(e))}function Re(e){return new Intl.NumberFormat("en-US",{notation:"compact",maximumFractionDigits:2}).format(e)}function Xt(e){return new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",minimumFractionDigits:2,maximumFractionDigits:2}).format(e)}function Rt(e){return`${e.toFixed(1)}%`}const $l=[{id:"time",accessorFn:e=>Number(Date.parse(e.eventTimestamp||e.callStartedAt||e.rawTime||e.time))||0,header:"Time",cell:e=>e.row.original.time},{accessorKey:"thread",header:"Thread"},{accessorKey:"model",header:"Model"},{accessorKey:"effort",header:"Effort",cell:e=>c.jsx("span",{className:`pill effort-${String(e.getValue())}`,children:String(e.getValue())})},{accessorKey:"input",header:"Input Tokens",cell:e=>c.jsx("span",{className:"num",children:Le(Number(e.getValue()))})},{accessorKey:"totalTokens",header:"Total Tokens",cell:e=>c.jsx("span",{className:"num",children:Le(Number(e.getValue()))})},{accessorKey:"cachedInput",header:"Cached Input",cell:e=>c.jsx("span",{className:"num",children:Le(Number(e.getValue()))})},{accessorKey:"uncachedInput",header:"Uncached Input",cell:e=>c.jsx("span",{className:"num",children:Le(Number(e.getValue()))})},{accessorKey:"output",header:"Output Tokens",cell:e=>c.jsx("span",{className:"num",children:Le(Number(e.getValue()))})},{accessorKey:"reasoningOutput",header:"Reasoning Output",cell:e=>c.jsx("span",{className:"num",children:Le(Number(e.getValue()))})},{accessorKey:"cachedPct",header:"Cached %",cell:e=>c.jsx("span",{className:"cache-pill",children:Rt(Number(e.getValue()))})},{accessorKey:"cost",header:"Est. Cost",cell:e=>c.jsx("span",{className:"num",children:Xt(Number(e.getValue()))})},{accessorKey:"credits",header:"Codex Credits",cell:e=>c.jsx("span",{className:"num",children:Re(Number(e.getValue()))})},{accessorKey:"contextWindowPct",header:"Context %",cell:e=>{const t=e.getValue(),n=typeof t=="number"?t:Number.NaN;return c.jsx("span",{className:"num",children:Number.isFinite(n)?Rt(n):"-"})}},{accessorKey:"duration",header:"Duration"},{accessorKey:"previousCallGap",header:"Prev Gap",cell:e=>c.jsx("span",{className:"num",children:String(e.getValue())})},{accessorKey:"initiator",header:"Initiated",cell:e=>c.jsx("span",{className:"status-badge blue",children:String(e.getValue())})},{accessorKey:"signal",header:"Signals",cell:e=>c.jsx(Di,{call:e.row.original})}],ce=[{header:"timestamp",value:e=>e.eventTimestamp||e.rawTime||e.time},{header:"thread",value:e=>e.thread},{header:"call_started_at",value:e=>e.callStartedAt||e.rawTime||e.time},{header:"call_duration_seconds",value:e=>e.durationSeconds},{header:"previous_call_event_timestamp",value:e=>e.previousCallEventTimestamp},{header:"previous_call_delta_seconds",value:e=>e.previousCallGapSeconds},{header:"initiated",value:e=>e.initiator},{header:"initiated_reason",value:e=>e.initiatorReason},{header:"project",value:e=>e.project},{header:"model",value:e=>e.model},{header:"effort",value:e=>e.effort},{header:"total_tokens",value:e=>e.totalTokens},{header:"input_tokens",value:e=>e.input},{header:"cached_input_tokens",value:e=>e.cachedInput},{header:"uncached_input_tokens",value:e=>e.uncachedInput},{header:"output_tokens",value:e=>e.output},{header:"reasoning_output_tokens",value:e=>e.reasoningOutput},{header:"estimated_cost_usd",value:e=>e.cost.toFixed(6)},{header:"usage_credits",value:e=>e.credits.toFixed(6)},{header:"cache_ratio",value:e=>e.cachedPct.toFixed(2)},{header:"context_window_percent",value:e=>{var t;return((t=e.contextWindowPct)==null?void 0:t.toFixed(2))??""}},{header:"pricing_model",value:e=>e.pricingModel},{header:"usage_credit_confidence",value:e=>e.usageCreditConfidence},{header:"recommendation",value:e=>e.recommendation},{header:"record_id",value:e=>e.id},{header:"thread_attachment",value:e=>e.threadAttachmentLabel},{header:"thread_source",value:e=>e.threadSource},{header:"parent_thread",value:e=>e.parentThread},{header:"session_id",value:e=>e.sessionId},{header:"turn_id",value:e=>e.turnId},{header:"parent_session_id",value:e=>e.parentSessionId},{header:"parent_session_updated_at",value:e=>e.parentSessionUpdatedAt},{header:"project_relative_cwd",value:e=>e.projectRelativeCwd},{header:"cwd",value:e=>e.cwd},{header:"source_file",value:e=>e.sourceFile},{header:"source_line",value:e=>e.lineNumber??""},{header:"git_branch",value:e=>e.gitBranch},{header:"git_remote_label",value:e=>e.gitRemoteLabel},{header:"git_remote_hash",value:e=>e.gitRemoteHash},{header:"pricing_estimated",value:e=>String(e.pricingEstimated)},{header:"usage_credit_model",value:e=>e.usageCreditModel},{header:"usage_credit_source",value:e=>e.usageCreditSource},{header:"usage_credit_tier",value:e=>e.usageCreditTier},{header:"usage_credit_fetched_at",value:e=>e.usageCreditFetchedAt},{header:"usage_credit_note",value:e=>e.usageCreditNote},{header:"model_context_window",value:e=>e.modelContextWindow??""},{header:"cumulative_total_tokens",value:e=>e.cumulativeTotalTokens??""},{header:"estimated_cache_savings_usd",value:e=>e.estimatedCacheSavings.toFixed(6)},{header:"initiated_confidence",value:e=>e.initiatorConfidence},{header:"signal",value:e=>e.signal},{header:"tags",value:e=>e.tags.join("|")},{header:"efficiency_flags",value:e=>e.efficiencyFlags.join("|")}];function Ei(e,t=3){const a=Fi([e.signal,...e.efficiencyFlags]).map((r,s)=>({key:`${r}-${s}`,label:Ja(r),shortLabel:Ii(r)}));return{visible:a.slice(0,t),hidden:a.slice(t)}}function Di({call:e}){const t=Oa(),{visible:n,hidden:a}=Ei(e);if(!n.length)return c.jsx("span",{className:"muted",children:"None"});const r=n.map(o=>({...o,label:t.translateText(o.label),shortLabel:t.translateText(o.shortLabel)})),s=a.map(o=>({...o,label:t.translateText(o.label),shortLabel:t.translateText(o.shortLabel)})),i=s.map(o=>o.label).join("、");return c.jsxs("span",{className:"flags compact-flags","aria-label":t.language==="zh-Hans"?`信号:${[...r,...s].map(o=>o.label).join("、")}`:`Signals: ${[...n,...a].map(o=>o.label).join(", ")}`,children:[r.map(o=>c.jsx("span",{className:"flag signal-puck",title:o.label,children:o.shortLabel},o.key)),s.length?c.jsxs("span",{className:"flag signal-puck more",title:i,children:["+",s.length]}):null]})}function Fi(e){const t=new Set;return e.map(n=>n.trim()).filter(n=>n&&n!=="aggregate").filter(n=>{const a=n.toLowerCase();return t.has(a)?!1:(t.add(a),!0)})}function Ja(e){return e.replace(/[-_]+/g," ").replace(/\b\w/g,t=>t.toUpperCase())}function Ii(e){const t=e.toLowerCase().replace(/[_\s]+/g,"-"),n={"cache-drop":"CACHE","cache-risk":"CACHE","context-bloat":"CTX","context-heavy":"CTX","elevated-context":"CTX","elevated-context-use":"CTX","estimated-pricing":"EST","expensive-low-output-call":"LO","high-context-use":"CTX","high-cost":"$","high-estimated-cost":"$","high-reasoning-share":"RSN","large-thread":"BIG","low-cache":"CACHE","low-cache-reuse":"CACHE","low-output":"LO","pricing-gap":"PRICE","reasoning-spike":"RSN","subagent-attribution":"SUB"};if(n[t])return n[t];const a=Ja(e).split(/\s+/).filter(Boolean);return a.length?a.length===1?a[0].slice(0,4).toUpperCase():a.slice(0,3).map(r=>r[0]).join("").toUpperCase():"?"}const Ol=[{accessorKey:"name",header:"Thread"},{accessorKey:"latestActivity",header:"Latest"},{accessorKey:"turns",header:"Turns",cell:e=>c.jsx("span",{className:"num",children:Le(Number(e.getValue()))})},{accessorKey:"totalDuration",header:"Duration"},{accessorKey:"averageGap",header:"Avg Gap",cell:e=>c.jsx("span",{className:"num",children:String(e.getValue())})},{accessorKey:"initiatorSummary",header:"Initiated",cell:e=>c.jsx("span",{className:"status-badge blue",children:String(e.getValue())})},{accessorKey:"modelSummary",header:"Models",cell:e=>c.jsx("span",{className:"pill model-pill",children:String(e.getValue())})},{accessorKey:"effortSummary",header:"Effort Mix"},{accessorKey:"totalTokens",header:"Total Tokens",cell:e=>c.jsx("span",{className:"num",children:Re(Number(e.getValue()))})},{accessorKey:"cachedInput",header:"Cached Input",cell:e=>c.jsx("span",{className:"num",children:Re(Number(e.getValue()))})},{accessorKey:"uncachedInput",header:"Uncached Input",cell:e=>c.jsx("span",{className:"num",children:Re(Number(e.getValue()))})},{accessorKey:"outputTokens",header:"Output Tokens",cell:e=>c.jsx("span",{className:"num",children:Re(Number(e.getValue()))})},{accessorKey:"reasoningOutput",header:"Reasoning Output",cell:e=>c.jsx("span",{className:"num",children:Re(Number(e.getValue()))})},{accessorKey:"cost",header:"Est. Cost",cell:e=>c.jsx("span",{className:"num",children:Xt(Number(e.getValue()))})},{accessorKey:"credits",header:"Codex Credits",cell:e=>c.jsx("span",{className:"num",children:Re(Number(e.getValue()))})},{accessorKey:"cachePct",header:"Cache %",cell:e=>c.jsx("span",{className:"cache-pill",children:Rt(Number(e.getValue()))})},{accessorKey:"contextPct",header:"Context %",cell:e=>{const t=e.getValue();return c.jsx("span",{className:"num",children:typeof t=="number"?Rt(t):"-"})}},{accessorKey:"costPerCall",header:"Cost / Call",cell:e=>c.jsx("span",{className:"num",children:Xt(Number(e.getValue()))})},{accessorKey:"coldResumeRisk",header:"Cold Resume Risk",cell:e=>c.jsx("span",{className:`status-badge ${$i(String(e.getValue()))}`,children:String(e.getValue())})},{accessorKey:"productivity",header:"Productivity",cell:e=>c.jsx("span",{className:"score",children:Number(e.getValue())})}],Ul=[{id:"name",label:"Thread",locked:!0},{id:"latestActivity",label:"Latest"},{id:"turns",label:"Turns"},{id:"totalDuration",label:"Duration"},{id:"averageGap",label:"Avg Gap"},{id:"initiatorSummary",label:"Initiated"},{id:"modelSummary",label:"Models"},{id:"effortSummary",label:"Effort Mix"},{id:"totalTokens",label:"Total Tokens"},{id:"cachedInput",label:"Cached Input"},{id:"uncachedInput",label:"Uncached Input"},{id:"outputTokens",label:"Output Tokens"},{id:"reasoningOutput",label:"Reasoning Output"},{id:"cost",label:"Est. Cost"},{id:"credits",label:"Codex Credits"},{id:"cachePct",label:"Cache %"},{id:"contextPct",label:"Context %"},{id:"costPerCall",label:"Cost / Call"},{id:"coldResumeRisk",label:"Cold Resume Risk"},{id:"productivity",label:"Productivity"},{id:"investigate",label:"Investigate",locked:!0}];function $i(e){return e==="High"?"red":e==="Medium"?"orange":e==="Low"?"green":"neutral"}const dt=1,re=0,qt=100,Za=1e3,Xa=500,Ya="codexUsageDashboardLoadSettings",Oi={day:1440*60*1e3,week:10080*60*1e3};function ct(e,t=Xa){if((e==null?void 0:e.limit_label)==="All"||t===re&&(e==null?void 0:e.limit)==null)return re;const n=Number((e==null?void 0:e.limit)??(e==null?void 0:e.loaded_row_count)??t);return Number.isFinite(n)&&n>=0?n:t}function gt(e){return Number.isFinite(e)?e<=re?re:Math.max(dt,Math.round(e)):dt}function je(...e){const t=e.find(n=>typeof n=="number"&&Number.isFinite(n)&&n>0);return t?Math.max(dt,Math.round(t)):dt}function Ui(e,t,n=null,a="rows"){const r=gt(e);return{historyScope:t,loadWindow:a,limit:r===re?null:r,since:n}}function Vi(e){return e.limit??re}function fn(e){return mn(e==null?void 0:e.load_window)?e.load_window:e!=null&&e.since?"week":(e==null?void 0:e.limit_label)==="All"||(e==null?void 0:e.limit)==null?"all":"rows"}function Ki(e){return mn(e==null?void 0:e.default_load_window)?e.default_load_window:fn(e)}function Ht(e,t=new Date){const n=Oi[e];if(!n)return null;const a=Math.floor(t.getTime()/6e4)*6e4;return new Date(a-n).toISOString()}function Te(e,t=Xa){return e==="day"?"Last 24 hours":e==="week"?"Last 7 days":e==="all"?"All time":`Most recent ${gt(t).toLocaleString()}`}function qi(e=er()){if(!e)return null;try{const t=e.getItem(Ya);if(!t)return null;const n=JSON.parse(t),a=typeof n.loadLimit=="number"&&Number.isFinite(n.loadLimit)?gt(n.loadLimit):void 0,r=n.historyScope==="active"||n.historyScope==="all"?n.historyScope:void 0,s=mn(n.loadWindow)?n.loadWindow:void 0;return a===void 0&&r===void 0&&s===void 0?null:{loadLimit:a,historyScope:r,loadWindow:s}}catch{return null}}function Hi(e,t,n="rows",a=er()){if(a)try{a.setItem(Ya,JSON.stringify({loadLimit:gt(e),historyScope:t,loadWindow:n}))}catch{}}function mn(e){return e==="day"||e==="week"||e==="rows"||e==="all"}function Qi({currentLimit:e,loadedRows:t,pendingLimit:n}){const a=n===re?0:n+qt,s=Math.max(dt,e===re?0:e,a,t);return Math.ceil((s+Za)/qt)*qt}function Wi({currentLimit:e,loadedRows:t,pendingLimit:n}){return Math.max(je(e),je(n),je(t))+Za}function zi({loadedRows:e,limit:t,totalRows:n}){return t===re&&e>0?`Loaded all ${e.toLocaleString()}`:n>e?`Loaded ${e.toLocaleString()} of ${n.toLocaleString()}`:`Loaded ${e.toLocaleString()} rows`}function er(){try{return typeof window>"u"?null:window.sessionStorage}catch{return null}}async function Bi(e,t,n,a="",r=""){const s=Ni();switch(e){case"threads":{const{threadCallsForCurrentUrl:i}=await O(async()=>{const{threadCallsForCurrentUrl:o}=await import("./ThreadsPage.js");return{threadCallsForCurrentUrl:o}},__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]));return te(`codex-thread-filtered-calls-${s}.csv`,i(t,a),ce,"call rows")}case"cache-context":{const{cacheContextCallsForCurrentUrl:i}=await O(async()=>{const{cacheContextCallsForCurrentUrl:o}=await import("./CacheContextPage.js");return{cacheContextCallsForCurrentUrl:o}},__vite__mapDeps([28,1,2,29,30,22,31,32,6,33,19,20,21,9,10,23,34,4,3,5,7,15,35,24,25,26,12]));return te(`codex-${e}-calls-${s}.csv`,i(t),ce,"call rows")}case"usage-drain":{const{usageDrainCallsForCurrentUrl:i}=await O(async()=>{const{usageDrainCallsForCurrentUrl:o}=await import("./UsageDrainPage.js");return{usageDrainCallsForCurrentUrl:o}},__vite__mapDeps([36,1,2,34,4,20,21,9,10,16,17,18,24,37,38,25,26,12,39]));return te(`codex-usage-drain-calls-${s}.csv`,i(t),ce,"call rows")}case"diagnostics":{const{diagnosticsCallsForCurrentUrl:i}=await O(async()=>{const{diagnosticsCallsForCurrentUrl:o}=await import("./DiagnosticsPage.js");return{diagnosticsCallsForCurrentUrl:o}},__vite__mapDeps([40,1,2,29,33,19,20,21,9,10,23,24,41,4,6,7,34,3,25,26,12]));return te(`codex-diagnostics-calls-${s}.csv`,i(t),ce,"call rows")}case"reports":{const{reportCallsForCurrentUrl:i}=await O(async()=>{const{reportCallsForCurrentUrl:o}=await import("./ReportsPage.js");return{reportCallsForCurrentUrl:o}},__vite__mapDeps([42,1,2,34,4,6,33,19,20,21,9,10,16,17,18,30,22,35,23,24,25,26,12,43]));return te(`codex-reports-evidence-${s}.csv`,i(t),ce,"call rows")}case"settings":return te(`codex-dashboard-settings-${s}.csv`,Ji(t,n),Gi,"settings rows");case"calls":{const{callsForCurrentUrl:i}=await O(async()=>{const{callsForCurrentUrl:o}=await import("./CallsPage.js");return{callsForCurrentUrl:o}},__vite__mapDeps([44,1,2,3,4,5,6,7,14,29,33,19,45,22,11,12,13,35,23,24,34,46,47,38,25,26,48]));return te(`codex-calls-${s}.csv`,i(t.calls,a,r),ce,"call rows")}case"overview":{const{overviewCallsForQuery:i}=await O(async()=>{const{overviewCallsForQuery:o}=await import("./OverviewPage.js");return{overviewCallsForQuery:o}},__vite__mapDeps([49,1,2,34,4,20,21,9,10,31,32,6,16,17,18,45,22,11,12,13,14,35,23,24,25,26,50]));return te(`codex-overview-calls-${s}.csv`,i(t.calls,a),ce,"call rows")}case"investigator":{const{investigatorCallsForCurrentUrl:i}=await O(async()=>{const{investigatorCallsForCurrentUrl:o}=await import("./InvestigatorPage.js");return{investigatorCallsForCurrentUrl:o}},__vite__mapDeps([51,1,2,34,4,6,41,7,20,21,9,10,16,17,18,45,22,11,12,13,23,24,38,25,26,52]));return te(`codex-investigator-calls-${s}.csv`,i(t),ce,"call rows")}case"compression-lab":return te(`codex-compression-lab-scope-${s}.csv`,t.calls,ce,"call rows");case"call":return te(`codex-call-calls-${s}.csv`,Ri(t),ce,"call rows")}}function te(e,t,n,a){return{filename:e,csv:ji(t,n),rowCount:t.length,label:a}}const Gi=[{header:"Field",value:e=>e.field},{header:"Value",value:e=>e.value}];function Ji(e,t){return[{field:"live_api",value:t.canUseLiveApi?"available":"static snapshot"},{field:"context_api",value:t.contextRuntime.contextApiEnabled?"enabled":"gated"},{field:"history_scope",value:t.historyScope},{field:"data_window",value:Te(t.loadWindow,t.loadLimit)},{field:"scope_since",value:t.scopeSince??"none"},{field:"row_request",value:t.loadLimit===re?"no cap":String(t.loadLimit)},{field:"loaded_rows",value:String(t.loadedRowCount)},{field:"total_available_rows",value:String(t.totalAvailableRows)},{field:"auto_refresh",value:t.autoRefreshEnabled?"enabled":"paused"},{field:"refresh_state",value:t.refreshState},{field:"visible_calls",value:String(e.calls.length)},{field:"visible_threads",value:String(e.threads.length)}]}function Zi(e){return e instanceof Error?e.message:String(e)}function Xn(e,t){const n=t==="all"?"all history":"active history",a=e.message||"Refreshing usage index",r=typeof e.percent=="number"?` ${Math.round(e.percent)}%`:"";return`${a}${r} (${n})`}function Qt(e,t="active"){return e?typeof e.include_archived=="boolean"?e.include_archived?"all":"active":e.history_scope==="all-history"||e.history_scope==="all"?"all":e.history_scope==="active"?"active":t:t}function Xi({historyScope:e,activeRows:t,allRows:n,archivedRows:a}){const r=Yi({activeRows:t,allRows:n,archivedRows:a});return e==="all"?r===null?"All history selected":r>0?`All history includes ${r.toLocaleString()} archived calls`:"All history selected; no archived calls are indexed yet":r===null||r<=0?"Active sessions only":`Active sessions only; ${r.toLocaleString()} archived calls hidden`}function Yi({activeRows:e,allRows:t,archivedRows:n}){const a=Wt(n);if(a!==null)return a;const r=Wt(e),s=Wt(t);return r!==null&&s!==null?Math.max(s-r,0):null}function Wt(e){return typeof e=="number"&&Number.isFinite(e)&&e>=0?Math.round(e):null}const tr=["aria-description","aria-label","aria-valuetext","title","placeholder","alt"],eo="data-localization-attributes",to=new Set(["CODE","KBD","PRE","SAMP","SCRIPT","STYLE","TEXTAREA"]),Yt=new WeakMap,en=new WeakMap;function no({value:e,children:t}){return c.jsxs(Js,{value:e,children:[c.jsx(ao,{}),t]})}function ao(){const e=Oa();return C.useLayoutEffect(()=>{const t=document.querySelector("[data-dashboard-localization-root]");if(!t)return;if(e.language!=="zh-Hans"){so(t),document.title="Codex Usage Tracker React Dashboard";return}document.title="Codex Usage Tracker · 用量仪表盘",Yn(t,e.translateText);const n=new MutationObserver(a=>{for(const r of a){if(r.type==="characterData"&&r.target instanceof Text){tn(r.target,e.translateText);continue}if(r.type==="attributes"&&r.target instanceof Element){nn(r.target,e.translateText);continue}for(const s of r.addedNodes)s instanceof Text?tn(s,e.translateText):s instanceof Element&&Yn(s,e.translateText)}});return n.observe(t,{attributes:!0,attributeFilter:[...tr],characterData:!0,childList:!0,subtree:!0}),()=>n.disconnect()},[e]),null}function Yn(e,t){if(Tt(e))return;nn(e,t);const n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT);let a=n.nextNode();for(;a;){if(a instanceof Element){if(Tt(a)){a=ro(n,a,e);continue}nn(a,t)}else a instanceof Text&&tn(a,t);a=n.nextNode()}}function ro(e,t,n){let a=t;for(;a&&a!==n;){const r=e.nextSibling();if(r)return r;a=a.parentNode,a&&(e.currentNode=a)}return null}function tn(e,t){const n=e.parentElement;if(!n||Tt(n))return;const a=e.nodeValue??"",r=Yt.get(e),s=r&&(a===r.source||a===r.translated)?r.source:a,i=s.match(/^(\s*)([\s\S]*?)(\s*)$/u);if(!i||!i[2])return;const o=t(i[2]),h=`${i[1]}${o}${i[3]}`;Yt.set(e,{source:s,translated:h}),h!==a&&(e.nodeValue=h)}function nn(e,t){if(Tt(e))return;const n=new Set((e.getAttribute(eo)??"").split(/[\s,]+/u).filter(Boolean));if(!n.size)return;const a=en.get(e)??new Map;for(const r of tr){if(!n.has(r))continue;const s=e.getAttribute(r);if(!s)continue;const i=a.get(r),o=i&&(s===i.source||s===i.translated)?i.source:s,h=t(o);a.set(r,{source:o,translated:h}),h!==s&&e.setAttribute(r,h)}a.size&&en.set(e,a)}function so(e){const t=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT);ea(e);let n=t.nextNode();for(;n;){if(n instanceof Text){const a=Yt.get(n);a&&n.nodeValue===a.translated&&(n.nodeValue=a.source)}else n instanceof Element&&ea(n);n=t.nextNode()}}function ea(e){const t=en.get(e);if(t)for(const[n,a]of t)e.getAttribute(n)===a.translated&&e.setAttribute(n,a.source)}function Tt(e){return to.has(e.tagName)||!!e.closest('[data-localization-skip="true"]')}const Mt=["May 12","May 19","May 26","Jun 02","Jun 09","Jun 16","Jun 23","Jun 30"],it=Mt.slice(2),nr={contextRuntime:{apiToken:"",contextApiEnabled:!1,fileMode:!1},cards:[{label:"Total Tokens",value:"24.83M",detail:"7-day total: 12.56M",trend:"up 18.7% vs prior 7 days",tone:"blue"},{label:"Estimated Cost",value:"$42.67",detail:"7-day total: $24.81",trend:"up 14.2% vs prior 7 days",tone:"green"},{label:"Cache Hit Rate",value:"38.7%",detail:"7-day average: 42.3%",trend:"down 3.6pp vs prior 7 days",tone:"purple"},{label:"Total Calls",value:"1,342",detail:"678 calls in last 7 days",trend:"up 11.3% vs prior 7 days",tone:"blue"},{label:"Usage Remaining",value:"32.4%",detail:"~15.9K credits estimated",trend:"resets in 4d 12h",tone:"green"}],tokenSeries:[Q("input","Input","#2563eb",[5.2,6.4,7.5,9.2,6.7,7.5,6.6,9],1e6),Q("output","Output","#059669",[2.5,3,3.6,5.1,3.7,5,4,5.4],1e6),Q("cached","Cached","#7c3aed",[1.1,1.4,1.7,2.4,1.8,2.3,1.8,2.6],1e6,!0)],costSeries:[Q("cost","Estimated Cost","#2563eb",[8.4,10.1,14.3,9.9,11.4,7.8,12.7,16.5])],cacheSeries:[Q("cache","Cache Hit Rate","#059669",[58,61,39,45,50,44,48,41])],weeklyCreditSeries:[{id:"pro",label:"Pro observed",color:"#2563eb",points:Mt.map((e,t)=>({label:e,value:[59800,44900,45e3,40500,42800,38900,31400,35900][t]??0,low:[52100,38800,38900,34700,36200,32200,26100,29900][t]??0,high:[67400,51200,51100,46800,49200,45100,37300,41900][t]??0}))},Q("pro-trend","Pro trend","#1d4ed8",[54.2,51,47.8,44.6,41.4,38.2,35,31.8],1e3,!0),Q("prolite","Prolite observed","#0f766e",[15.9,15.8,15.9,16.1,15.7,15.6,15.9,15.8],1e3)],usageRemainingSeries:[Q("remaining","Usage remaining","#059669",[87,71,63,56,56,48,50,54]),Q("allowance","Allowance guide","#0f766e",[86,85,84,83,82,81,80,79],1,!0)],actualVsPredictedSeries:[Q("observed","Observed drain","#2563eb",[18.7,22.1,45.3,31,34.8],1e3),Q("predicted","Predicted baseline","#1d4ed8",[17.9,21.4,41.2,29.6,33.9],1e3,!0)],findings:[{rank:1,title:"Long Thread: data-engine-refactor",severity:"High",credits:12847,share:25.6,summary:"Very long duration with high model effort."},{rank:2,title:"Cache Misses (Large Inputs)",severity:"High",credits:9612,share:19.1,summary:"Large uncached inputs across 214 calls."},{rank:3,title:"High Model Effort",severity:"Medium",credits:7404,share:14.7,summary:"Reasoning and output token volume are concentrated."},{rank:4,title:"Tool Output Volume",severity:"Medium",credits:5231,share:10.4,summary:"Large tool outputs returned to the model."}],calls:[["2026-06-01T10:24:00Z","Jun 1, 10:24 AM","thread-9f3a1c","codex-1","high",128542,45231,62,.42,"18.4s",!1,["review"]],["2026-06-01T10:18:00Z","Jun 1, 10:18 AM","thread-7b2e91","o4-mini","medium",98731,32104,41,.31,"12.7s",!0,["analysis"]],["2026-06-01T10:12:00Z","Jun 1, 10:12 AM","thread-3c8d4e","o3","high",64221,18903,28,.12,"9.3s",!0,["uncached"]],["2026-06-01T10:06:00Z","Jun 1, 10:06 AM","thread-1a2b3c","codex-1","high",245123,98112,71,.87,"27.6s",!1,["large"]],["2026-06-01T10:01:00Z","Jun 1, 10:01 AM","thread-8d7c6b","gpt-4.1","low",12543,4231,12,.03,"3.6s",!0,["quick"]],["2026-06-01T09:55:00Z","Jun 1, 09:55 AM","thread-2f9e7d","o4-mini","medium",76881,28442,39,.24,"11.2s",!1,["subagent"]],["2026-06-01T09:50:00Z","Jun 1, 09:50 AM","thread-6a5b4c","codex-1","high",312654,112991,67,1.12,"31.8s",!1,["file-heavy"]],["2026-06-01T09:47:00Z","Jun 1, 09:47 AM","thread-0f1e2d","o3","low",8221,2903,15,.02,"2.8s",!0,["fast"]]].map(([e,t,n,a,r,s,i,o,h,f,d,v],u)=>{const p=Number(s),g=Number(i),b=Number(o),w=Math.round(p*Math.max(100-b,0)/100);return{id:`fixture-call-${u}`,rawTime:String(e),eventTimestamp:String(e),callStartedAt:String(e),time:String(t),thread:String(n),model:String(a),effort:String(r),input:p,output:g,reasoningOutput:Math.round(g*.2),totalTokens:p+g,cachedInput:Math.round(p*b/100),uncachedInput:w,cachedPct:b,cost:u===4?0:Number(h),credits:(u===4?0:Number(h))*25,duration:String(f),durationSeconds:Number.parseFloat(String(f))||0,previousCallGap:u===0?"-":`${u*7}m 0s`,previousCallEventTimestamp:u===0?"":`2026-06-01T09:${String(40+u).padStart(2,"0")}:00Z`,previousCallGapSeconds:u*420,initiator:u%3===0?"user":u%3===1?"assistant":"tool",initiatorReason:u%3===0?"direct user request":u%3===1?"assistant follow-up":"tool-driven continuation",initiatorConfidence:u%2===0?"exact":"estimated",fast:!!d,usageCreditConfidence:u===7?"user_override":u%4===0?"exact":u%4===1?"estimated":u%4===2?"missing":"fixture",usageCreditModel:u===4?"":`${a}-credits`,usageCreditSource:u===4?"":"fixture-rate-card",usageCreditFetchedAt:u===4?"":"2026-06-01T10:00:00Z",usageCreditTier:u%2===0?"standard":"estimated",usageCreditNote:u===2?"fixture inherited rate card":"",pricingModel:u===4?"":`${a}-pricing`,pricingEstimated:u===1||u===5,signal:w>5e4?"cache-risk":"aggregate",recommendation:w>5e4?"Review uncached aggregate input before continuing this thread.":"",tags:v,sessionId:`fixture-session-${u}`,turnId:`fixture-turn-${u}`,parentSessionId:u%3===0?"fixture-parent-session":"",parentSessionUpdatedAt:u%3===0?"2026-06-01T09:30:00Z":"",parentThread:u%3===0?"parent-thread-analysis":"",threadAttachmentLabel:u%3===0?"spawned child thread":"direct active thread",threadSource:u%3===0?"subagent":"user",subagentType:u%3===0?"analysis":"",agentRole:u%3===0?"reviewer":"",agentNickname:u%3===0?"usage-reviewer":"",project:u%2===0?"codex-usage-tracker":"local-ops",projectRelativeCwd:u%2===0?"frontend/dashboard":".",projectTags:u%2===0?["dashboard","rewrite"]:["local"],cwd:`/fixtures/${u%2===0?"codex-usage-tracker":"local-ops"}`,sourceFile:`fixture-thread-${u}.jsonl`,lineNumber:120+u,gitBranch:"experiment/frontend-rewrite",gitRemoteLabel:"origin",gitRemoteHash:`fixture-${u}`,contextWindowPct:Math.min(18+b,96),modelContextWindow:128e3,cumulativeTotalTokens:p+g+u*1e4,estimatedCacheSavings:Math.round((p-w)*1e-5*100)/100,efficiencyFlags:w>5e4?["cache-risk"]:[]}}),threads:[["thread-9f3a",142,58400,8.76,12,1.38,"High",42],["thread-7c2b",87,31200,4.21,22,1.12,"Medium",55],["thread-1a8c",64,22700,3.02,18,.98,"High",48],["thread-d3e1",53,18100,2.11,41,.81,"Low",72],["thread-b7f0",41,13600,1.65,47,.73,"Low",75],["thread-3c5d",36,9900,1.18,35,.66,"Medium",63],["thread-0e16",28,6400,.72,56,.58,"Low",82]].map(([e,t,n,a,r,s,i,o],h)=>{const f=Number(t),d=Number(n),v=Number(r),u=f*48,p=(h+1)*420;return{name:String(e),latestCallId:`fixture-call-${h}`,latestActivity:`Jun ${h+1}, 10:${String(24-h).padStart(2,"0")} AM`,latestActivityRaw:`2026-06-${String(h+1).padStart(2,"0")}T10:${String(24-h).padStart(2,"0")}:00Z`,turns:f,totalDurationSeconds:u,totalDuration:`${Math.floor(u/60)}m ${u%60}s`,averageGapSeconds:p,averageGap:`${Math.floor(p/60)}m ${p%60}s`,initiatorSummary:h%2===0?"user x4, assistant x2":"assistant x3, tool x1",modelSummary:h%2===0?"codex-1 x5, o4-mini x2":"o4-mini x3, o3 x1",effortSummary:h%2===0?"high x5, medium x2":"medium x3, low x1",totalTokens:d,cachedInput:Math.round(d*v/100),uncachedInput:Math.round(d*Math.max(100-v,0)/100),outputTokens:Math.round(d*.28),reasoningOutput:Math.round(d*.08),cost:Number(a),credits:Number(a)*25,cachePct:v,contextPct:Math.min(96,28+h*7),costPerCall:Number(s),coldResumeRisk:i,productivity:Number(o)}}),weeklyWindows:Mt.map((e,t)=>({week:e,plan:t===0?"Prolite":"Pro",observedPct:[61.4,49.2,47.7,48.3,44.5,37.4,40.1,35.8][t]??0,credits:[49812,41275,39887,40563,37284,31420,33842,35900][t]??0,projected:[49812,41275,39887,40563,37284,31420,33842,35900][t]??0,ciLow:[42156,34892,33424,34021,31241,26164,28234,29450][t]??0,ciHigh:[57467,47657,46349,47105,43326,36676,39450,41900][t]??0,confidence:t===0?"Medium":"High",note:["Prolite baseline","Drop in credits","Stable","Slight uptick","Down again","Lowest window","Recovery","Latest"][t]??""})),modelCosts:[{label:"codex-1",value:16.21,color:"#2563eb"},{label:"o3",value:12.43,color:"#1d4ed8"},{label:"o4-mini",value:7.62,color:"#059669"},{label:"gpt-4.1",value:4.91,color:"#f59e0b"},{label:"other",value:1.5,color:"#94a3b8"}],commandActions:[{title:"Show highest uncached calls",status:"Ready",owner:"Calls",description:"214 calls above the uncached threshold."},{title:"Compare Pro weeks",status:"Ready",owner:"Usage Drain",description:"Allowance windows with confidence intervals."},{title:"Find cold resumes",status:"Ready",owner:"Cache",description:"14 threads with long idle gaps."},{title:"Export support bundle",status:"Planned",owner:"Reports",description:"Local aggregate artifacts only."}],cacheSegments:[{label:"Cache read",value:38.7,color:"#2563eb"},{label:"Cache write",value:29.6,color:"#059669"},{label:"Uncached",value:31.7,color:"#7c3aed"}],cacheHeatmap:[{thread:"thread-8c1e",labels:it,values:[62,71,89,82,74,31]},{thread:"thread-2b9d",labels:it,values:[42,58,77,61,51,24]},{thread:"thread-713a",labels:it,values:[78,81,83,66,59,37]},{thread:"thread-4af2",labels:it,values:[24,36,63,54,71,44]},{thread:"thread-f9c3",labels:it,values:[18,25,41,38,52,22]}],diagnostics:[{title:"Usage Drain",status:"Ready",finding:"Projected weekly credits declined from the baseline and partially recovered in the latest window.",confidence:"High",metric:"33,842 credits / week",series:[Q("usage-drain","Projected credits","#2563eb",[41.8,46.7,45.9,32.1,33.8],1e3)]},{title:"Cache Behavior",status:"Ready",finding:"Cache hit rate is healthy overall, with spikes aligned to large cache misses and cold resumes.",confidence:"Medium",metric:"41.1% hit rate",series:[Q("cache","Cache hit %","#059669",[44,48,38,33,40])]},{title:"Thread Efficiency",status:"Ready",finding:"Long threads account for most estimated cost and are the clearest optimization target.",confidence:"High",metric:"65% cost share",series:[Q("threads","Cost share","#1d4ed8",[65,23,8,4])]},{title:"Tool And Command Activity",status:"Stale",finding:"Command volume is stable with a slight upward trend; read and shell commands dominate.",confidence:"Medium",metric:"912 commands",series:[Q("commands","Commands","#7c3aed",[542,488,611,883,912])]}],reports:[{title:"Weekly Credits",status:"Ready",owner:"Usage Drain",description:"Plan-specific weekly credits, trend lines, and confidence intervals."},{title:"Usage Remaining",status:"Ready",owner:"Usage Drain",description:"Observed remaining usage over time with reset handling."},{title:"Cost Curves",status:"Ready",owner:"Threads",description:"Cumulative estimated cost by thread and concentration metrics."},{title:"Usage Drain Model",status:"Ready",owner:"Reports",description:"Actual-vs-predicted drain and feature group comparisons."},{title:"Fast Mode Proxy",status:"Planned",owner:"Calls",description:"Candidate detection, speedup histogram, and confidence breakdowns."},{title:"Allowance Change",status:"Planned",owner:"Reports",description:"Week-to-week allowance estimate changes with careful language."}]};function Q(e,t,n,a,r=1,s=!1){return{id:e,label:t,color:n,dashed:s,points:a.map((i,o)=>({label:Mt[o]??`Point ${o+1}`,value:i*r}))}}function ar(e){if(window.location.protocol==="file:")throw new Error("Live refresh requires the localhost dashboard server.");if(!(e!=null&&e.api_token))throw new Error("Live refresh requires localhost dashboard API token.")}function gn(e){return{Accept:"application/json","X-Codex-Usage-Token":e.api_token||""}}function io(e,t){return new Promise((n,a)=>{if(t!=null&&t.aborted){a(ta(t));return}const r=window.setTimeout(()=>{t==null||t.removeEventListener("abort",s),n()},e);function s(){window.clearTimeout(r),a(ta(t))}t==null||t.addEventListener("abort",s,{once:!0})})}function rr(e){return(e instanceof DOMException||e instanceof Error)&&e.name==="AbortError"}function ta(e){return(e==null?void 0:e.reason)instanceof Error?e.reason:new DOMException("The request was cancelled.","AbortError")}const oo=["#2563eb","#1d4ed8","#059669","#f59e0b","#94a3b8"];function sr(e){const t=new Map;e.forEach(r=>{const s=r.model||"unknown";t.set(s,(t.get(s)??0)+r.cost)});const n=[...t.entries()].sort((r,s)=>s[1]-r[1]||r[0].localeCompare(s[0]));return(n.length>5?[...n.slice(0,4),["other",n.slice(4).reduce((r,s)=>r+s[1],0)]]:n).map(([r,s],i)=>({label:r,value:s,color:oo[i]??"#94a3b8"}))}function ir(e){if(!e.length)return[];const t=Math.max(e.reduce((n,a)=>n+pt(a),0),1);return[co(e,t),lo(e,t),uo(e,t),ho(e,t)].filter(n=>!!n).sort((n,a)=>a.credits-n.credits).map((n,a)=>({...n,rank:a+1}))}function or(e){if(!e.length)return[];const t=[{title:"Cost Curves",status:"Ready",owner:"Threads",description:"Estimated cost concentration by loaded aggregate thread."},{title:"Usage Drain Model",status:"Ready",owner:"Reports",description:"Highest estimated credit-impact calls from loaded aggregate rows."}];return e.some(n=>n.fast||n.effort.toLowerCase()==="low")&&t.push({title:"Fast Mode Proxy",status:"Ready",owner:"Calls",description:"Low-effort and fast-call candidates inferred from aggregate rows."}),t}function co(e,t){const n=new Map;e.forEach(i=>n.set(i.thread,[...n.get(i.thread)??[],i]));const[a,r]=[...n.entries()].sort((i,o)=>Ae(o[1],f=>f.totalTokens)-Ae(i[1],f=>f.totalTokens)||o[1].length-i[1].length)[0]??[];if(!a||!(r!=null&&r.length)||r.length<2)return null;const s=Ae(r,pt);return{rank:0,title:`Long Thread: ${a}`,severity:s/t>=.25||r.length>=8?"High":"Medium",credits:Math.round(s),share:s/t*100,summary:`${r.length.toLocaleString()} loaded calls and ${Ae(r,i=>i.totalTokens).toLocaleString()} tokens in this thread.`}}function lo(e,t){const n=e.filter(r=>r.signal==="cache-risk"||r.cachedPct<35||r.uncachedInput>5e4);if(!n.length)return null;const a=Ae(n,pt);return{rank:0,title:"Cache Misses (Large Inputs)",severity:n.some(r=>r.cachedPct<20||r.uncachedInput>5e4)?"High":"Medium",credits:Math.round(a),share:a/t*100,summary:`${n.length.toLocaleString()} loaded calls show low cache reuse or large uncached input.`}}function uo(e,t){const n=e.filter(r=>r.effort.toLowerCase()==="high"||r.reasoningOutput>0);if(!n.length)return null;const a=Ae(n,pt);return{rank:0,title:"High Model Effort",severity:a/t>=.25?"High":"Medium",credits:Math.round(a),share:a/t*100,summary:`${n.length.toLocaleString()} loaded calls use high effort or report reasoning output.`}}function ho(e,t){const n=Math.max(25e3,fo(e.map(s=>s.output),.75)),a=e.filter(s=>s.output>=n||s.tags.some(i=>["file-heavy","subagent","large"].includes(i)));if(!a.length)return null;const r=Ae(a,pt);return{rank:0,title:"Tool Output Volume",severity:a.some(s=>s.output>5e4)?"High":"Medium",credits:Math.round(r),share:r/t*100,summary:`${a.length.toLocaleString()} loaded calls have high output volume or file-heavy/subagent tags.`}}function pt(e){return e.credits>0?e.credits:e.cost*25}function Ae(e,t){return e.reduce((n,a)=>n+t(a),0)}function fo(e,t){const n=e.filter(a=>Number.isFinite(a)).sort((a,r)=>a-r);return n.length?n[Math.min(n.length-1,Math.max(0,Math.floor((n.length-1)*t)))]??0:0}function cr(e){const t=new Map;for(const a of e){if(!Number.isFinite(a.timestamp))continue;const r=lr(a.timestamp),s=t.get(r)??{label:ur(a.timestamp),timestamp:go(a.timestamp),cached:0,cost:0,input:0,output:0};s.cached+=a.cached,s.cost+=a.cost,s.input+=a.input,s.output+=a.output,t.set(r,s)}const n=mo(t);return n.length?{tokenSeries:[{id:"input",label:"Input",color:"#2563eb",points:n.map(a=>({label:a.label,value:a.input}))},{id:"output",label:"Output",color:"#059669",points:n.map(a=>({label:a.label,value:a.output}))},{id:"cached",label:"Cached",color:"#7c3aed",dashed:!0,points:n.map(a=>({label:a.label,value:a.cached}))}],costSeries:[{id:"cost",label:"Estimated Cost",color:"#f59e0b",points:n.map(a=>({label:a.label,value:a.cost}))}],cacheSeries:[{id:"cache",label:"Cache hit %",color:"#2563eb",points:n.map(a=>({label:a.label,value:a.input>0?a.cached/a.input*100:0}))}]}:{tokenSeries:[],costSeries:[],cacheSeries:[]}}function mo(e){const t=[...e.values()].sort((s,i)=>s.timestamp-i.timestamp),n=t.at(0),a=t.at(-1);if(!n||!a)return[];const r=[];for(const s=new Date(n.timestamp);s.getTime()<=a.timestamp;s.setDate(s.getDate()+1)){const i=s.getTime(),o=lr(i);r.push(e.get(o)??{label:ur(i),timestamp:i,cached:0,cost:0,input:0,output:0})}return r}function lr(e){const t=new Date(e),n=String(t.getMonth()+1).padStart(2,"0"),a=String(t.getDate()).padStart(2,"0");return`${t.getFullYear()}-${n}-${a}`}function go(e){const t=new Date(e);return new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime()}function ur(e){return new Intl.DateTimeFormat("en-US",{month:"short",day:"numeric"}).format(new Date(e))}function W(e,t){var a;const n=Number(((a=e.summary)==null?void 0:a[t])??0);return Number.isFinite(n)?n:0}function po(e){if(!(!e.summary||e.load_window==="rows"))return{visibleCalls:W(e,"visible_calls"),inputTokens:W(e,"input_tokens"),cachedInputTokens:W(e,"cached_input_tokens"),uncachedInputTokens:W(e,"uncached_input_tokens"),outputTokens:W(e,"output_tokens"),reasoningOutputTokens:W(e,"reasoning_output_tokens"),totalTokens:W(e,"total_tokens"),estimatedCostUsd:W(e,"estimated_cost_usd"),usageCredits:W(e,"usage_credits")}}const bo=1e4,vo=500;async function na(e,t,n){var s,i;const a=t.limit&&t.limit>0?t.limit:vo,r=await n(e,{...t,limit:a});return(i=t.onProgress)==null||i.call(t,{status:"completed",phase:"loading_rows",message:`Loaded ${t.loadWindow==="all"?"all-history":t.loadWindow} evidence window`,completed:Number(r.loaded_row_count??((s=r.rows)==null?void 0:s.length)??0),total:Number(r.total_available_rows??r.loaded_row_count??0),percent:100}),r}async function aa(e,t,n){var o,h;const a=[];let r=0,s=null,i=Number((e==null?void 0:e.total_available_rows)??0);for(let f=0;f<1e3;f+=1){(o=t.signal)==null||o.throwIfAborted();const d=await n(e,{...t,refresh:f===0?t.refresh:!1,limit:bo,offset:r}),v=d.rows??[];s=d,a.push(...v),i=Number(d.total_available_rows??i??a.length);const u=a.length>=i||!d.has_more;if((h=t.onProgress)==null||h.call(t,{status:u?"completed":"running",phase:"loading_rows",message:"Loading all rows",completed:a.length,total:i,percent:u?100:i>0?Math.min(99,Math.floor(a.length/i*100)):0}),r+=v.length,!d.has_more||v.length===0||a.length>=i)break}return{...s??e??{},rows:a,loaded_row_count:a.length,limit:null,limit_label:"All",has_more:!1,total_available_rows:i||a.length}}function wo(){if(window.__CODEX_USAGE_BOOT__)return window.__CODEX_USAGE_BOOT__;const e=document.getElementById("usage-data");if(!(e!=null&&e.textContent))return null;try{return JSON.parse(e.textContent)}catch{return null}}async function yo(e,t={}){if(t.refresh&&(e!=null&&e.refresh_jobs_available)){const n=await _o(e,t),a={...t,refresh:!n};return a.loadWindow&&a.loadWindow!=="rows"?na(e,a,He):a.limit===0?aa(e,a,He):He(e,a)}return t.loadWindow&&t.loadWindow!=="rows"?na(e,t,He):t.limit===0?aa(e,t,He):He(e,t)}async function _o(e,t){try{return await So(e,t),!0}catch(n){if(rr(n)||n instanceof dr)throw n;return!1}}async function He(e,t={}){ar(e);const n=new URLSearchParams({refresh:t.refresh?"1":"0",limit:String(t.limit??(e==null?void 0:e.limit)??(e==null?void 0:e.loaded_row_count)??500),_:String(Date.now())});t.loadWindow&&n.set("load_window",t.loadWindow),t.since&&n.set("since",t.since),t.offset&&t.offset>0&&n.set("offset",String(t.offset)),(t.includeArchived??vr(e))&&n.set("include_archived","1");const r=await fetch(`/api/usage?${n.toString()}`,{headers:gn(e),cache:"no-store",signal:t.signal});return await pn(r,"Usage refresh")}async function So(e,t){var o;ar(e);const n=new URLSearchParams({_:String(Date.now())});(t.includeArchived??vr(e))&&n.set("include_archived","1");const r=await fetch(`/api/refresh/start?${n.toString()}`,{headers:gn(e),cache:"no-store",signal:t.signal}),s=await pn(r,"Usage refresh start");(o=t.onProgress)==null||o.call(t,s);const i=typeof s.job_id=="string"?s.job_id:"";if(!i)throw new Error("Usage refresh start did not return a job id.");return Co(e,i,t.onProgress,t.signal)}async function Co(e,t,n,a){for(let r=0;r<600;r+=1){a==null||a.throwIfAborted();const s=new URLSearchParams({job_id:t,_:String(Date.now())}),i=await fetch(`/api/refresh/status?${s.toString()}`,{headers:gn(e),cache:"no-store",signal:a}),o=await pn(i,"Usage refresh status");if(n==null||n(o),o.status==="completed")return o;if(o.status==="failed")throw new dr(o.error||o.message||"Usage refresh failed.");await io(Math.min(1e3,150+r*50),a)}throw new Error("Usage refresh did not complete before the polling timeout.")}class dr extends Error{}function ra(e){if(!e)return{...nr,contextRuntime:rn(e)};const t=e.rows??[],n=t.map(fr),a=t.length?ke(t,b=>Number(b.total_tokens??0)):W(e,"total_tokens"),r=t.length?ke(t,b=>Number(b.estimated_cost_usd??0)):W(e,"estimated_cost_usd"),s=t.length?ke(t,b=>Number(b.cached_input_tokens??0)):W(e,"cached_input_tokens"),i=t.length?ke(t,b=>Number(b.input_tokens??0)):W(e,"input_tokens"),o=t.length?ke(t,b=>{const w=Number(b.input_tokens??0),L=Number(b.cached_input_tokens??0);return Number(b.uncached_input_tokens??Math.max(w-L,0))}):Math.max(i-s,0),h=t.length?ke(t,b=>Number(b.output_tokens??0)):W(e,"output_tokens"),f=t.length?ke(t,b=>Number(b.reasoning_output_tokens??0)):W(e,"reasoning_output_tokens"),d=i>0?s/i*100:0,v=n.length||Math.max(0,Number(e.loaded_row_count??0)),u=ko(t),p=Lo(t),g=Po({cachePct:d,cachedTokens:s,estimatedCost:r,historyScope:e.history_scope??"active",totalCalls:v,totalTokens:a,tokenBreakdown:{cachedInput:s,uncachedInput:o,output:h,reasoningOutput:f},usageRemainingCard:jo(e)});return{...xo(e),contextRuntime:rn(e),scopeSummary:po(e),cards:g,...u,...p,calls:n,threads:wr(n),findings:ir(n),modelCosts:sr(n),reports:or(n),cacheSegments:[{label:"Cache read",value:d,color:"#2563eb"},{label:"Uncached input",value:Math.max(100-d,0),color:"#7c3aed"}]}}function xo(e){return{...nr,contextRuntime:rn(e),tokenSeries:[],costSeries:[],cacheSeries:[],weeklyCreditSeries:[],usageRemainingSeries:[],actualVsPredictedSeries:[],calls:[],threads:[],findings:[],weeklyWindows:[],modelCosts:[],commandActions:[],cacheSegments:[],cacheHeatmap:[],diagnostics:[],reports:[]}}function ko(e){return cr(e.map(t=>({timestamp:vn(t),cached:Number(t.cached_input_tokens??0),cost:Number(t.estimated_cost_usd??0),input:Number(t.input_tokens??0),output:Number(t.output_tokens??0)})))}function Lo(e){const t=[...e].map(f=>({row:f,timestamp:vn(f)})).filter(f=>Number.isFinite(f.timestamp)).sort((f,d)=>f.timestamp-d.timestamp),n=new Map;for(const{row:f,timestamp:d}of t){const v=an(d),u=n.get(v)??{timestamp:d,credits:0,weeklyUsedPercent:null};u.credits+=Math.max(0,Number(f.usage_credits??0));const p=Oe(f.rate_limit_secondary_used_percent);p!==null&&(u.weeklyUsedPercent=p),n.set(v,u)}const a=[...n.entries()].map(([f,d])=>({label:f,...d}));let r=0;const s=a.map(f=>(r+=f.credits,{label:f.label,value:r})),i=s.map((f,d)=>{var v;return{label:f.label,value:s.length>1?(((v=s.at(-1))==null?void 0:v.value)??0)*((d+1)/s.length):f.value}}),o=a.filter(f=>f.weeklyUsedPercent!==null).map(f=>({label:f.label,value:Pt(100-(f.weeklyUsedPercent??0))})),h=Ro(e);return{weeklyCreditSeries:To(h),usageRemainingSeries:o.length?[{id:"weekly-remaining",label:"Weekly remaining",color:"#059669",points:o}]:[],actualVsPredictedSeries:s.length?[{id:"observed-drain",label:"Observed drain",color:"#2563eb",points:s},{id:"loaded-baseline",label:"Loaded-row baseline",color:"#1d4ed8",dashed:!0,points:i}]:[],weeklyWindows:h}}function Ro(e){const t=new Map;for(const n of e){const a=vn(n);if(!Number.isFinite(a))continue;const r=Mo(n,a),s=t.get(r)??{rows:[],latestTimestamp:0,latestUsedPercent:null};s.rows.push(n),a>=s.latestTimestamp&&(s.latestTimestamp=a,s.latestUsedPercent=Oe(n.rate_limit_secondary_used_percent)),t.set(r,s)}return[...t.entries()].map(([n,a])=>{var i;const r=a.rows.reduce((o,h)=>o+Math.max(0,Number(h.usage_credits??0)),0),s=a.latestUsedPercent??0;return{week:n,plan:String(((i=a.rows[0])==null?void 0:i.rate_limit_plan_type)??"unknown"),observedPct:s,credits:r,projected:s>0?r/(s/100):r,ciLow:s>0?r/(s/100)*.85:r,ciHigh:s>0?r/(s/100)*1.15:r,confidence:a.rows.length>=20?"Medium":"Low",note:`Loaded ${bn(a.rows.length)} rows`}}).sort((n,a)=>n.week.localeCompare(a.week))}function To(e){return e.length?[{id:"weekly-projected",label:"Projected weekly credits",color:"#2563eb",points:e.map(t=>({label:t.week,value:t.projected,low:t.ciLow,high:t.ciHigh}))},{id:"loaded-credits",label:"Loaded-row credits",color:"#0f766e",dashed:!0,points:e.map(t=>({label:t.week,value:t.credits}))}]:[]}function Mo(e,t){const n=We(e.rate_limit_secondary_resets_at);return n!==null&&n>0?an(n*1e3):an(t)}function an(e){return new Intl.DateTimeFormat("en-US",{month:"short",day:"numeric"}).format(new Date(e))}function rn(e){return{apiToken:String((e==null?void 0:e.api_token)??""),contextApiEnabled:!!(e!=null&&e.context_api_enabled),fileMode:window.location.protocol==="file:"}}function Po(e){return[{label:"Total Tokens",value:Me(e.totalTokens),detail:`${e.historyScope} history scope`,trend:"loaded aggregate rows",tone:"blue",breakdown:[{label:"Cached",value:Me(e.tokenBreakdown.cachedInput)},{label:"Uncached",value:Me(e.tokenBreakdown.uncachedInput)},{label:"Output",value:Me(e.tokenBreakdown.output)},{label:"Reasoning",value:Me(e.tokenBreakdown.reasoningOutput)}]},{label:"Estimated Cost",value:$o(e.estimatedCost),detail:"local pricing config",trend:"privacy-safe estimate",tone:"green"},{label:"Cache Hit Rate",value:`${e.cachePct.toFixed(1)}%`,detail:`${Me(e.cachedTokens)} cached input`,trend:e.cachePct>=40?"healthy cache reuse":"cache risk",tone:e.cachePct>=40?"purple":"orange"},{label:"Total Calls",value:bn(e.totalCalls),detail:"loaded calls in this dashboard",trend:"privacy-safe",tone:"blue"},e.usageRemainingCard]}function jo(e){const t=Ao(e.observed_usage);if(t)return t;const n=No(e.allowance_windows);return n||{label:"Usage Remaining",value:"Unknown",detail:e.allowance_configured?"allowance configured; no current window":"no observed usage or allowance window",trend:e.allowance_error?`config issue: ${e.allowance_error}`:"not available in payload",tone:e.allowance_error?"red":"orange"}}function Ao(e){const t=Array.isArray(e==null?void 0:e.windows)?e.windows:[];if(!(e!=null&&e.available)||!t.length)return null;const n=hr(t,s=>Oe(s.used_percent)!==null);if(!n)return null;const a=Oe(n.used_percent)??0,r=Pt(100-a);return{label:"Usage Remaining",value:mr(r),detail:`${pr(n.label||n.key,"Observed usage")} observed usage`,trend:gr(n.resets_at)||e.source||"observed locally",tone:br(r)}}function No(e){if(!Array.isArray(e)||!e.length)return null;const t=hr(e,o=>{const h=Oe(o.remaining_percent),f=We(o.remaining_credits),d=We(o.total_credits);return h!==null||f!==null||d!==null&&d>0});if(!t)return null;const n=We(t.remaining_credits),a=We(t.total_credits),r=n!==null&&a!==null&&a>0?n/a*100:null,s=Oe(t.remaining_percent)??r;return{label:"Usage Remaining",value:s!==null?mr(Pt(s)):`${sa(n??0)} left`,detail:`${pr(t.label||t.key,"Allowance")} allowance window`,trend:[n===null?"":`${sa(n)} left`,gr(t.reset_at)].filter(Boolean).join(" · ")||"configured allowance",tone:s===null?"green":br(Pt(s))}}function hr(e,t){const n=e.filter(t);return n.find(Eo)??n[0]??null}function Eo(e){const t=We(e.window_minutes),n=`${e.key??""} ${e.label??""}`.toLowerCase();return t===10080||/\b(weekly|week|7d|7-day|7 day)\b/.test(n)}function fr(e,t=0){const n=String(e.event_timestamp??e.time??e.turn_timestamp??e.started_at??e.call_started_at??""),a=n,r=String(e.call_started_at??e.started_at??n),s=Number(e.input_tokens??0),i=Number(e.output_tokens??0),o=Number(e.reasoning_output_tokens??0),h=Number(e.cached_input_tokens??0),f=Number(e.cache_hit_ratio??e.cache_ratio??0),d=s>0?h/s*100:f*100,v=Number(e.total_tokens??s+i),u=Number(e.duration_seconds??e.call_duration_seconds??0),p=String(e.previous_call_event_timestamp??""),g=Number(e.previous_call_delta_seconds??0),b=Number(e.uncached_input_tokens??Math.max(s-h,0)),w=String(e.record_id??e.id??`${a||"row"}-${t}`),L=String(e.primary_signal??"").trim(),N=Number(e.line_number),T=Oe(e.context_window_percent),_=Number(e.model_context_window),R=Number(e.cumulative_total_tokens);return{id:w,threadKey:String(e.thread_key??""),rawTime:a,eventTimestamp:n,callStartedAt:r,time:yr(a),thread:Fo(e),model:String(e.model??"unknown"),effort:String(e.effort??"blank"),input:s,output:i,reasoningOutput:o,totalTokens:v,cachedInput:h,uncachedInput:b,cachedPct:d,cost:Number(e.estimated_cost_usd??0),credits:Number(e.usage_credits??0),duration:jt(u),durationSeconds:u,previousCallGap:jt(g),previousCallEventTimestamp:p,previousCallGapSeconds:Number.isFinite(g)?g:0,initiator:String(e.call_initiator??"unknown"),initiatorReason:String(e.call_initiator_reason??""),initiatorConfidence:String(e.call_initiator_confidence??""),fast:u>0&&v/Math.max(u,1)>4e3,usageCreditConfidence:String(e.usage_credit_confidence??"unknown"),usageCreditModel:String(e.usage_credit_model??""),usageCreditSource:String(e.usage_credit_source??""),usageCreditFetchedAt:String(e.usage_credit_fetched_at??""),usageCreditTier:String(e.usage_credit_tier??""),usageCreditNote:String(e.usage_credit_note??""),pricingModel:String(e.pricing_model??""),pricingEstimated:!!e.pricing_estimated,signal:L||(d<25?"cache-risk":"aggregate"),recommendation:String(e.recommended_action??""),tags:d<25?["uncached"]:d>60?["healthy-cache"]:[],sessionId:String(e.session_id??""),turnId:String(e.turn_id??""),parentSessionId:String(e.parent_session_id??""),parentSessionUpdatedAt:String(e.resolved_parent_session_updated_at??e.parent_session_updated_at??""),parentThread:String(e.resolved_parent_thread_name??e.parent_thread_name??""),threadAttachmentLabel:String(e.thread_attachment_label??""),threadSource:String(e.thread_source??""),subagentType:String(e.subagent_type??""),agentRole:String(e.agent_role??""),agentNickname:String(e.agent_nickname??""),project:String(e.project_name??""),projectRelativeCwd:String(e.project_relative_cwd??""),projectTags:Array.isArray(e.project_tags)?e.project_tags.map(y=>String(y)):[],cwd:String(e.cwd??""),sourceFile:String(e.source_file??""),lineNumber:Number.isFinite(N)&&N>0?N:null,gitBranch:String(e.git_branch??""),gitRemoteLabel:String(e.git_remote_label??""),gitRemoteHash:String(e.git_remote_hash??""),contextWindowPct:T,modelContextWindow:Number.isFinite(_)&&_>0?_:null,cumulativeTotalTokens:Number.isFinite(R)&&R>0?R:null,estimatedCacheSavings:Number(e.estimated_cache_savings_usd??0),efficiencyFlags:Array.isArray(e.efficiency_flags)?e.efficiency_flags.map(y=>String(y)):[]}}function Oe(e){const t=Number(e);return Number.isFinite(t)?t<=1?t*100:t:null}function We(e){const t=Number(e);return Number.isFinite(t)?t:null}function Pt(e){return Math.max(0,Math.min(100,e))}function mr(e){const t=Math.abs(e)>=10?0:1;return`${e.toFixed(t)}%`}function sa(e){return`${Me(e)} cr`}function gr(e){if(e==null||e==="")return"";const t=typeof e=="number"?e*1e3:Date.parse(e);return Number.isFinite(t)?`resets ${Do(t)}`:""}function Do(e){const t=new Date(e);return Number.isNaN(t.getTime())?String(e):`${t.toISOString().slice(0,16).replace("T"," ")} UTC`}function pr(e,t){return(e==null?void 0:e.trim())||t}function br(e){return e<20?"red":e<40?"orange":"green"}function vr(e){return typeof e.include_archived=="boolean"?e.include_archived:e.history_scope==="all-history"||e.history_scope==="all"}function Fo(e){const t=e.thread_name??e.thread??e.resolved_parent_thread_name??e.parent_thread_name;if(t&&String(t).trim())return String(t);const n=e.project_name??e.project_relative_cwd,a=e.session_id?e.session_id.slice(-6):"";return n&&a?`${n} ${a}`:e.thread_attachment_label?String(e.thread_attachment_label):"Untitled thread"}function wr(e){const t=new Map;for(const n of e)t.set(n.thread,[...t.get(n.thread)??[],n]);return[...t.entries()].map(([n,a])=>{const r=a.length,i=[...a].sort((y,k)=>ia(k)-ia(y))[0]??null,o=(i==null?void 0:i.rawTime)||(i==null?void 0:i.time)||"",h=a.reduce((y,k)=>y+k.totalTokens,0),f=a.reduce((y,k)=>y+k.cachedInput,0),d=a.reduce((y,k)=>y+k.uncachedInput,0),v=a.reduce((y,k)=>y+k.output,0),u=a.reduce((y,k)=>y+k.reasoningOutput,0),p=a.reduce((y,k)=>y+k.cost,0),g=a.reduce((y,k)=>y+k.credits,0),b=a.reduce((y,k)=>y+k.durationSeconds,0),w=a.filter(y=>y.previousCallGapSeconds>0),L=w.reduce((y,k)=>y+k.previousCallGapSeconds,0)/Math.max(w.length,1),N=f+d,T=N>0?f/N*100:a.reduce((y,k)=>y+k.cachedPct,0)/Math.max(r,1),_=a.map(y=>y.contextWindowPct).filter(y=>typeof y=="number"&&Number.isFinite(y)),R=T<25?"High":T<45?"Medium":"Low";return{name:n,latestCallId:(i==null?void 0:i.id)??"",latestActivity:yr(o),latestActivityRaw:o,turns:r,totalDurationSeconds:b,totalDuration:jt(b),averageGapSeconds:L,averageGap:jt(L),initiatorSummary:zt(a.map(y=>y.initiator)),modelSummary:zt(a.map(y=>y.model)),effortSummary:zt(a.map(y=>y.effort)),totalTokens:h,cachedInput:f,uncachedInput:d,outputTokens:v,reasoningOutput:u,cost:p,credits:g,cachePct:T,contextPct:_.length?Math.max(..._):null,costPerCall:p/Math.max(r,1),coldResumeRisk:R,productivity:Math.max(20,Math.round(T-p/Math.max(r,1)*4))}}).sort((n,a)=>a.cost-n.cost).slice(0,20)}function ia(e){const t=Date.parse(e.rawTime||e.time);return Number.isFinite(t)?t:0}function zt(e,t=3){const n=new Map;for(const a of e){const r=a.trim()||"unknown";n.set(r,(n.get(r)??0)+1)}return[...n.entries()].sort((a,r)=>r[1]-a[1]||a[0].localeCompare(r[0])).slice(0,t).map(([a,r])=>`${a} x${bn(r)}`).join(", ")||"unknown"}function ke(e,t){return e.reduce((n,a)=>n+t(a),0)}async function pn(e,t){let n;try{n=await e.json()}catch(a){if(e.ok)throw new Error(`${t} response could not be read as JSON: ${Io(a)}`);n={}}if(!e.ok){const a=typeof n.error=="string"?n.error:`${t} request failed (${e.status})`;throw new Error(a)}return n}function Io(e){return e instanceof Error?e.message:String(e)}function bn(e){return new Intl.NumberFormat("en-US").format(Math.round(e))}function Me(e){return new Intl.NumberFormat("en-US",{notation:"compact",maximumFractionDigits:2}).format(e)}function $o(e){return new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",minimumFractionDigits:2,maximumFractionDigits:2}).format(e)}function jt(e){if(!Number.isFinite(e)||e<=0)return"-";if(e<60)return`${e.toFixed(0)}s`;const t=Math.floor(e/60),n=Math.round(e%60);return`${t}m ${n}s`}function yr(e){const t=new Date(e);return Number.isNaN(t.getTime())?e||"-":new Intl.DateTimeFormat("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit"}).format(t)}function vn(e){return Date.parse(String(e.started_at??e.call_started_at??e.time??e.event_timestamp??e.turn_timestamp??""))}const Pe=1440*60*1e3;function Oo(e,t,n=window.location.search){const a=_r(n);if(!a.active)return e;const r=e.calls.filter(s=>Uo(s,a));return r.length===e.calls.length?e:Wo(e,r,`${t} filtered`)}function _r(e){var h,f,d,v,u,p;const t=new URLSearchParams(e),n=((h=t.get("model"))==null?void 0:h.trim())??"",a=((f=t.get("effort"))==null?void 0:f.trim())??"",r=((d=t.get("confidence")||t.get("pricing"))==null?void 0:d.trim())??"",s=((v=t.get("date")||t.get("time"))==null?void 0:v.trim())??"",i=((u=t.get("from"))==null?void 0:u.trim())??"",o=((p=t.get("to"))==null?void 0:p.trim())??"";return{model:n,effort:a,confidence:r,datePreset:s,dateStart:i,dateEnd:o,active:!!(n||a||r||s||i||o)}}function Uo(e,t){return!(t.model&&e.model!==t.model||t.effort&&e.effort!==t.effort||t.confidence&&!Vo(e,t.confidence)||!Ko(e,t))}function Vo(e,t){return t==="official"||t==="cost-exact"?!!(e.pricingModel&&!e.pricingEstimated):t==="estimated"||t==="cost-estimated"?e.pricingEstimated:t==="unpriced"||t==="cost-unpriced"?!e.pricingModel:t==="credit-exact"?e.usageCreditConfidence==="exact":t==="credit-estimated"?e.usageCreditConfidence==="estimated":t==="credit-override"?e.usageCreditConfidence==="user_override":t==="credit-missing"?e.usageCreditConfidence==="missing"||e.usageCreditConfidence==="unknown":!0}function Ko(e,t){const n=qo(t);if(!n.active)return!0;if(n.invalid)return!1;const a=Date.parse(e.rawTime||e.time);return!(!Number.isFinite(a)||n.start!==null&&a=n.endExclusive)}function qo(e){if(e.datePreset&&e.datePreset!=="all"&&e.datePreset!=="custom"){const a=Ho(e.datePreset);return a?{active:!0,invalid:!1,...a}:{active:!1,invalid:!1,start:null,endExclusive:null}}const t=oa(e.dateStart),n=oa(e.dateEnd);return t!==null&&n!==null&&t>n?{active:!0,invalid:!0,start:t,endExclusive:n+Pe}:{active:t!==null||n!==null,invalid:!1,start:t,endExclusive:n===null?null:n+Pe}}function Ho(e){const t=Qo(new Date);if(e==="today")return{start:t,endExclusive:t+Pe};if(e==="last-7-days")return{start:t-6*Pe,endExclusive:t+Pe};if(e==="this-week"){const a=new Date(t).getDay(),r=a===0?-6:1-a,s=t+r*Pe;return{start:s,endExclusive:s+7*Pe}}if(e==="this-month"){const n=new Date(t),a=new Date(n.getFullYear(),n.getMonth(),1).getTime(),r=new Date(n.getFullYear(),n.getMonth()+1,1).getTime();return{start:a,endExclusive:r}}return null}function oa(e){if(!/^\d{4}-\d{2}-\d{2}$/.test(e))return null;const[t,n,a]=e.split("-").map(Number),r=new Date(t,n-1,a);return r.getFullYear()!==t||r.getMonth()!==n-1||r.getDate()!==a?null:r.getTime()}function Qo(e){return new Date(e.getFullYear(),e.getMonth(),e.getDate()).getTime()}function Wo(e,t,n="filtered"){const a=t.reduce((u,p)=>u+p.totalTokens,0),r=t.reduce((u,p)=>u+p.cost,0),s=t.reduce((u,p)=>u+p.cachedInput,0),i=t.reduce((u,p)=>u+p.uncachedInput,0),o=t.reduce((u,p)=>u+p.output,0),h=t.reduce((u,p)=>u+p.reasoningOutput,0),f=t.reduce((u,p)=>u+p.input,0),d=f>0?s/f*100:0,v=e.cards.find(u=>u.label==="Usage Remaining")??{label:"Usage Remaining",value:"Unknown",detail:"not available in filtered view",trend:"not available in payload",tone:"orange"};return{...e,cards:zo({cachePct:d,cachedTokens:s,estimatedCost:r,historyScope:n,totalCalls:t.length,totalTokens:a,tokenBreakdown:{cachedInput:s,uncachedInput:i,output:o,reasoningOutput:h},usageRemainingCard:v}),...Bo(t),calls:t,threads:wr(t),findings:ir(t),modelCosts:sr(t),reports:or(t),cacheSegments:[{label:"Cache read",value:d,color:"#2563eb"},{label:"Uncached input",value:Math.max(100-d,0),color:"#7c3aed"}]}}function zo({cachePct:e,cachedTokens:t,estimatedCost:n,historyScope:a,totalCalls:r,totalTokens:s,tokenBreakdown:i,usageRemainingCard:o}){return[{label:"Total Tokens",value:Qe(s),detail:`${a} history scope`,trend:"filtered aggregate rows",tone:"blue",breakdown:[{label:"Cached",value:Qe(i.cachedInput)},{label:"Uncached",value:Qe(i.uncachedInput)},{label:"Output",value:Qe(i.output)},{label:"Reasoning",value:Qe(i.reasoningOutput)}]},{label:"Estimated Cost",value:Jo(n),detail:"local pricing config",trend:"privacy-safe estimate",tone:"green"},{label:"Cache Hit Rate",value:`${e.toFixed(1)}%`,detail:`${Qe(t)} cached input`,trend:e>=40?"healthy cache reuse":"cache risk",tone:e>=40?"purple":"orange"},{label:"Total Calls",value:Go(r),detail:"loaded calls matching filters",trend:"legacy shell filters",tone:"blue"},o]}function Bo(e){return cr(e.map(t=>({timestamp:Date.parse(t.rawTime||t.time),cached:t.cachedInput,cost:t.cost,input:t.input,output:t.output})))}function Qe(e){return new Intl.NumberFormat("en-US",{notation:"compact",maximumFractionDigits:2}).format(e)}function Go(e){return new Intl.NumberFormat("en-US").format(Math.round(e))}function Jo(e){return new Intl.NumberFormat("en-US",{currency:"USD",maximumFractionDigits:2,minimumFractionDigits:2,style:"currency"}).format(e)}const Sr=[{id:"overview",label:"Overview",description:"High-level telemetry",icon:Es},{id:"investigator",label:"Investigate",description:"Root-cause evidence",icon:Ns},{id:"compression-lab",label:"Compression Lab",description:"Context savings",icon:Ls},{id:"calls",label:"Calls",description:"Model-call table",icon:$s},{id:"threads",label:"Threads",description:"Thread efficiency",icon:Vs},{id:"usage-drain",label:"Limits",description:"Allowance intelligence",icon:Us},{id:"cache-context",label:"Cache And Context",description:"Cache and cold resumes",icon:Ms},{id:"diagnostics",label:"Diagnostics Notebook",description:"Technical report",icon:xs},{id:"reports",label:"Reports",description:"Generated analyses",icon:Rs},{id:"settings",label:"Settings",description:"Local configuration",icon:Fs}],Cr=[{label:"Files",icon:js,target:"settings"},{label:"Commands",icon:Ss,target:"investigator"},{label:"Models",icon:ks,target:"calls"}];function xr(e){return vs(e)}const Zo=[{value:"day",label:"24 hours",ariaLabel:"Last 24h"},{value:"week",label:"7 days",ariaLabel:"Last 7 days"},{value:"rows",label:"Recent",ariaLabel:"Recent rows"},{value:"all",label:"All time",ariaLabel:"All time"}];function Xo(e){const t=e.refreshing||!e.canUseLiveApi,n=e.loadWindow==="rows",a=`${e.loadedRowCount.toLocaleString()} detail row${e.loadedRowCount===1?"":"s"} cached`,r=n?`${e.loadedRowCount.toLocaleString()} loaded / ${e.totalAvailableRows.toLocaleString()} total`:`${e.totalAvailableRows.toLocaleString()} calls analyzed · ${a}`,s=n?`${e.loadedRowCount.toLocaleString()} of ${e.totalAvailableRows.toLocaleString()} evidence rows loaded`:`Focused pages analyze all ${e.totalAvailableRows.toLocaleString()} calls in scope; ${e.loadedRowCount.toLocaleString()} call rows are cached for immediate detail views.`,i=n?e.rowLoadStatus:`${e.rowLoadModeLabel} analysis uses ${e.totalAvailableRows.toLocaleString()} calls; ${a}`;return c.jsxs("section",{className:"data-window-control","aria-label":"Analysis scope",children:[c.jsxs("div",{className:"data-window-summary",children:[c.jsx("span",{children:"Analysis scope"}),c.jsx("strong",{children:e.rowLoadModeLabel}),c.jsx("small",{title:s,children:r})]}),c.jsx("div",{className:"data-window-options",role:"group","aria-label":"Choose loaded call window",children:Zo.map(o=>c.jsx("button",{type:"button","aria-label":o.ariaLabel,"aria-pressed":e.loadWindow===o.value,disabled:t,onClick:()=>e.onWindowChange(o.value),children:o.label},o.value))}),e.loadWindow==="rows"?c.jsxs("div",{className:"row-window-editor",children:[c.jsx("input",{"aria-label":"Rows to load slider","aria-valuetext":`${e.finitePendingLoadLimit.toLocaleString()} recent rows`,type:"range",min:1,max:e.rowLimitSliderMax,step:100,value:e.rowLimitSliderValue,onChange:o=>e.onSliderChange(o.target.value),disabled:t}),c.jsx("input",{"aria-label":"Rows to load",type:"number",min:1,step:100,value:e.pendingLoadLimit,onChange:o=>e.onDraftChange(o.target.value),disabled:t}),c.jsx("button",{type:"button","data-primary":"true",onClick:e.onApply,disabled:t||!e.rowLimitChanged,children:e.loadLabel}),c.jsx("button",{type:"button",onClick:e.onLoadMore,disabled:t||!e.hasMoreRows,children:e.loadMoreLabel})]}):null,e.refreshing?c.jsxs("div",{className:"row-load-progress","aria-label":"Row loading progress",children:[c.jsx("div",{className:"row-load-progress-track",role:"progressbar","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":e.refreshProgressPercent??void 0,children:c.jsx("span",{style:{width:`${e.refreshProgressPercent??8}%`}})}),c.jsxs("span",{children:[e.refreshProgressPercent===null?e.refreshProgressText:`${Math.round(e.refreshProgressPercent)}% loaded`,c.jsx("button",{className:"icon-button",type:"button",onClick:()=>void e.onCancel(),"aria-label":"Cancel refresh",title:"Cancel refresh",children:c.jsx(un,{size:13})})]})]}):null,c.jsx("span",{className:"sr-only",role:"status","aria-live":"polite",children:i})]})}const wn=[{value:"all",label:"All time"},{value:"today",label:"Today"},{value:"this-week",label:"This week"},{value:"last-7-days",label:"Last 7 days"},{value:"this-month",label:"This month"},{value:"custom",label:"Custom"}],kr=[{value:"all",label:"All confidence"},{value:"cost-exact",label:"Exact cost"},{value:"cost-estimated",label:"Estimated cost"},{value:"cost-unpriced",label:"Unpriced cost"},{value:"credit-exact",label:"Exact credit"},{value:"credit-estimated",label:"Estimated credit"},{value:"credit-override",label:"Credit override"},{value:"credit-missing",label:"Missing credit"}];function Yo({activeView:e,locationSearch:t,model:n,onUrlChange:a}){const r=_r(t),s=C.useMemo(()=>ca(n.calls.map(g=>g.model)),[n.calls]),i=C.useMemo(()=>ca(n.calls.map(g=>g.effort)),[n.calls]),o=r.dateStart||r.dateEnd?"custom":ec(r.datePreset),h=nc(r,o);if(e==="calls"||e==="call"||!n.calls.length)return null;function f(g){const b=new URL(window.location.href);g(b),a(b)}function d(g,b){f(w=>{g==="confidence"&&w.searchParams.delete("pricing"),!b||b==="all"?w.searchParams.delete(g):w.searchParams.set(g,b)})}function v(g){f(b=>{if(!g||g==="all"){b.searchParams.delete("date"),b.searchParams.delete("time"),b.searchParams.delete("from"),b.searchParams.delete("to");return}b.searchParams.set("date",g),b.searchParams.set("time",g),g!=="custom"&&(b.searchParams.delete("from"),b.searchParams.delete("to"))})}function u(g,b){f(w=>{b?(w.searchParams.set(g,b),w.searchParams.set("date","custom"),w.searchParams.set("time","custom")):(w.searchParams.delete(g),!w.searchParams.get("from")&&!w.searchParams.get("to")&&(w.searchParams.delete("date"),w.searchParams.delete("time")))})}function p(){f(g=>{for(const b of["model","effort","confidence","pricing","date","time","from","to"])g.searchParams.delete(b)})}return c.jsxs("section",{className:"global-filter-strip span-all","aria-label":"Dashboard filters",children:[c.jsxs("strong",{children:[c.jsx(As,{size:15}),"Filters"]}),c.jsxs("label",{children:[c.jsx("span",{children:"Model"}),c.jsxs("select",{"aria-label":"Global model filter",value:r.model||"all",onChange:g=>d("model",g.target.value),children:[c.jsx("option",{value:"all",children:"All models"}),s.map(g=>c.jsx("option",{value:g,children:g},g))]})]}),c.jsxs("label",{children:[c.jsx("span",{children:"Effort"}),c.jsxs("select",{"aria-label":"Global effort filter",value:r.effort||"all",onChange:g=>d("effort",g.target.value),children:[c.jsx("option",{value:"all",children:"All effort"}),i.map(g=>c.jsx("option",{value:g,children:g},g))]})]}),c.jsxs("label",{children:[c.jsx("span",{children:"Confidence"}),c.jsx("select",{"aria-label":"Global confidence filter",value:tc(r.confidence),onChange:g=>d("confidence",g.target.value),children:kr.map(g=>c.jsx("option",{value:g.value,children:g.label},g.value))})]}),c.jsxs("label",{children:[c.jsx("span",{children:"Time"}),c.jsx("select",{"aria-label":"Global time filter",value:o,onChange:g=>v(g.target.value),children:wn.map(g=>c.jsx("option",{value:g.value,children:g.label},g.value))}),h?c.jsx("span",{className:"filter-status","data-state":h.state,"aria-live":"polite",children:h.label}):null]}),c.jsxs("label",{children:[c.jsx("span",{children:"Start"}),c.jsx("input",{"aria-label":"Global start date",type:"date",value:r.dateStart,onChange:g=>u("from",g.target.value)})]}),c.jsxs("label",{children:[c.jsx("span",{children:"End"}),c.jsx("input",{"aria-label":"Global end date",type:"date",value:r.dateEnd,onChange:g=>u("to",g.target.value)})]}),c.jsxs("button",{className:"toolbar-button",type:"button",disabled:!r.active,onClick:p,children:[c.jsx(un,{size:15}),"Clear filters"]})]})}function ca(e){return Array.from(new Set(e.map(t=>t.trim()).filter(Boolean))).sort((t,n)=>t.localeCompare(n))}function ec(e){return wn.some(t=>t.value===e)?e:"all"}function tc(e){return e==="official"?"cost-exact":e==="estimated"?"cost-estimated":e==="unpriced"?"cost-unpriced":kr.some(t=>t.value===e)?e:"all"}function nc(e,t){var r;const n=ua(e.dateStart),a=ua(e.dateEnd);if(n!==null&&a!==null&&n>a)return{label:"Invalid date range",state:"error"};if(t==="custom"&&(e.dateStart||e.dateEnd))return{label:ac(e.dateStart,e.dateEnd),state:"active"};if(t!=="all"){const s=((r=wn.find(o=>o.value===t))==null?void 0:r.label)??t,i=rc(t);return{label:i?`${s}: ${la(i.start)} to ${la(ze(i.endExclusive,-1))}`:s,state:"active"}}return null}function ac(e,t){return e&&t?`Custom: ${e} to ${t}`:e?`Custom: from ${e}`:t?`Custom: through ${t}`:"Custom range"}function rc(e){const t=sc();if(e==="today")return{start:t,endExclusive:ze(t,1)};if(e==="this-week"){const n=ic(t);return{start:n,endExclusive:ze(n,7)}}return e==="last-7-days"?{start:ze(t,-6),endExclusive:ze(t,1)}:e==="this-month"?{start:new Date(t.getFullYear(),t.getMonth(),1),endExclusive:new Date(t.getFullYear(),t.getMonth()+1,1)}:null}function sc(e=new Date){return new Date(e.getFullYear(),e.getMonth(),e.getDate())}function ic(e){const t=e.getDay();return ze(e,t===0?-6:1-t)}function ze(e,t){return new Date(e.getFullYear(),e.getMonth(),e.getDate()+t)}function la(e){const t=String(e.getMonth()+1).padStart(2,"0"),n=String(e.getDate()).padStart(2,"0");return`${e.getFullYear()}-${t}-${n}`}function ua(e){if(!/^\d{4}-\d{2}-\d{2}$/.test(e))return null;const[t,n,a]=e.split("-").map(Number),r=new Date(t,n-1,a);return r.getFullYear()!==t||r.getMonth()!==n-1||r.getDate()!==a?null:r.getTime()}function oc(e){return xr(e)&&e!=="call"}function sn(e,t="overview"){return e==="insights"?"overview":xr(e)?e:t}function da(e=window.location.search,t="calls"){const n=new URLSearchParams(e).get("return"),a=sn(n,t);return oc(a)?a:t}function ha(e=window.location.search){return new URLSearchParams(e).has("return")}function cc(e){var t,n;return((t=Sr.find(a=>a.id===e))==null?void 0:t.label)??((n=Cr.find(a=>a.target===e))==null?void 0:n.label)??"Calls"}function fa(e,t=window.location.search){return new URLSearchParams(t).get("history")==="all"?"all":e}function ma(e,t=window.location.href){const n=new URL(t);return e==="all"?n.searchParams.set("history","all"):n.searchParams.delete("history"),n}const lc={investigator:["finding"],threads:["thread","expand","threads","thread_q","risk","thread_call_sort","thread_call_page"],"cache-context":["cache_thread"],reports:["report"],"usage-drain":["usage_plan","usage_effort","usage_subagents","usage_sample","usage_confidence","limit_window","limit_hypothesis"],diagnostics:["diagnostic_source","diagnostic_fact"],calls:["explore","detail","call_q","source","sort","direction","density","page"],call:["record","return","mode","max_entries","max_chars","include_tool_output","include_compaction_history"]};function xt(e,t,n=[]){const a=new Set([t,...Array.isArray(n)?n:[n]]);for(const[r,s]of Object.entries(lc))if(!a.has(r))for(const i of s)e.searchParams.delete(i)}function ga(e){let t=!1;return e.searchParams.get("view")==="insights"&&(e.searchParams.set("view","overview"),t=!0),e.searchParams.get("return")==="insights"&&(e.searchParams.set("return","overview"),t=!0),t}const Lr=[{key:"overview",title:"Overview",path:"/api/diagnostics/overview",refreshPath:"/api/diagnostics/overview/refresh"},{key:"toolOutput",title:"Tool Output",path:"/api/diagnostics/tool-output",refreshPath:"/api/diagnostics/tool-output/refresh"},{key:"commands",title:"Commands",path:"/api/diagnostics/commands",refreshPath:"/api/diagnostics/commands/refresh"},{key:"gitInteractions",title:"Git Interactions",path:"/api/diagnostics/git-interactions",refreshPath:"/api/diagnostics/git-interactions/refresh"},{key:"fileReads",title:"File Reads",path:"/api/diagnostics/file-reads",refreshPath:"/api/diagnostics/file-reads/refresh"},{key:"fileModifications",title:"File Modifications",path:"/api/diagnostics/file-modifications",refreshPath:"/api/diagnostics/file-modifications/refresh"},{key:"readProductivity",title:"Read Productivity",path:"/api/diagnostics/read-productivity",refreshPath:"/api/diagnostics/read-productivity/refresh"},{key:"concentration",title:"Concentration",path:"/api/diagnostics/concentration",refreshPath:"/api/diagnostics/concentration/refresh"},{key:"guidedSummary",title:"What Is Driving Usage?",path:"/api/diagnostics/guided-summary",refreshPath:"/api/diagnostics/guided-summary/refresh"},{key:"usageDrain",title:"Usage Drain",path:"/api/diagnostics/usage-drain",refreshPath:"/api/diagnostics/usage-drain/refresh"}],At=new Map,Dt=new Map;function uc(){At.clear(),Dt.clear()}async function Vl(e,t,n={}){Sn(t);const a=gc(e);return n.signal?Nt(a,t,n.signal):mc(Dt,_n(e,t,n.cacheKey),()=>Nt(a,t))}async function Kl(e,t={}){Sn(e);const n=await fetch(`/api/diagnostics/refresh?_=${Date.now()}`,{method:"POST",headers:{Accept:"application/json","X-Codex-Usage-Token":e.apiToken},cache:"no-store",signal:t.signal}),a=await Ft(n,"Diagnostic snapshot refresh"),r=a.sections??(Tr(a)?await dc(e,await Rr(a,e,t),t.signal):{});At.set(yn(e),r);for(const[s,i]of Object.entries(r))Dt.set(_n(s,e),i);return r}async function ql(e,t,n={}){Sn(t);const a=await fetch(`${e.refreshPath}?_=${Date.now()}`,{method:"POST",headers:{Accept:"application/json","X-Codex-Usage-Token":t.apiToken},cache:"no-store",signal:n.signal}),r=await Ft(a,e.title),s=Tr(r)?await hc(t,e,await Rr(r,t,n),n.signal):r,i=yn(t),o=At.get(i),h=o?await Promise.resolve(o):{};return At.set(i,{...h,[e.key]:s}),Dt.set(_n(e.key,t),s),s}async function Rr(e,t,n){var r,s,i,o;let a=e;for((r=n.onProgress)==null||r.call(n,a);a.status==="pending"||a.status==="running";){const h=n.pollIntervalMs??((s=a.next)==null?void 0:s.poll_after_ms)??250;await fc(h,n.signal);const f=new URLSearchParams({job_id:a.job_id,_:String(Date.now())}),d=await fetch(`/api/diagnostics/refresh/status?${f.toString()}`,{headers:{Accept:"application/json","X-Codex-Usage-Token":t.apiToken},cache:"no-store",signal:n.signal});a=await Ft(d,"Diagnostic refresh status"),(i=n.onProgress)==null||i.call(n,a)}if(a.status!=="completed")throw new Error(`Diagnostic refresh failed: ${((o=a.error)==null?void 0:o.code)??a.status}`);return a}async function dc(e,t,n){const a=await Promise.all(Lr.map(async r=>[r.key,await Nt(r,e,n)]));return Object.fromEntries(a)}async function hc(e,t,n,a){return Nt(t,e,a)}function Tr(e){if(!e||typeof e!="object")return!1;const t=e;return t.schema==="codex-usage-tracker-analysis-job-v1"&&t.job_kind==="diagnostic-refresh"&&typeof t.job_id=="string"}function fc(e,t){return t!=null&&t.aborted?Promise.reject(t.reason??new DOMException("Aborted","AbortError")):e<=0?Promise.resolve():new Promise((n,a)=>{const r=()=>{window.clearTimeout(s),t==null||t.removeEventListener("abort",r),a((t==null?void 0:t.reason)??new DOMException("Aborted","AbortError"))},s=window.setTimeout(()=>{t==null||t.removeEventListener("abort",r),n()},e);t==null||t.addEventListener("abort",r,{once:!0})})}function mc(e,t,n){const a=e.get(t);if(a!==void 0)return Promise.resolve(a);const r=n().then(s=>(e.set(t,s),s)).catch(s=>{throw e.delete(t),s});return e.set(t,r),r}function yn(e){return`${e.fileMode?"file":"live"}:${e.apiToken}`}function _n(e,t,n=""){return`snapshot:${yn(t)}:${n}:${e}`}function gc(e){const t=Lr.find(n=>n.key===e);if(!t)throw new Error(`Unknown diagnostic snapshot: ${e}`);return t}async function Nt(e,t,n){const a=await fetch(`${e.path}?_=${Date.now()}`,{headers:{Accept:"application/json","X-Codex-Usage-Token":t.apiToken},cache:"no-store",signal:n});return await Ft(a,e.title)}function Sn(e){if(e.fileMode)throw new Error("Diagnostic facts require localhost dashboard server.");if(!e.apiToken)throw new Error("Diagnostic facts require localhost dashboard API token.")}async function Ft(e,t){if(!e.ok)throw new Error(`${t} request failed with HTTP ${e.status}`);const n=await e.json();if(typeof n.error=="string"&&n.error)throw new Error(n.error);return n}const pa=[{key:"facts",label:"Top Facts",title:"Top Diagnostic Facts",path:"/api/diagnostics/facts",limit:50},{key:"tools",label:"Tools",title:"Tool and Function Activity",path:"/api/diagnostics/tools",limit:25},{key:"compactions",label:"Compactions",title:"Compaction Activity",path:"/api/diagnostics/compactions",limit:25}],Mr=new Map,Pr=new Map;function pc(){uc(),Mr.clear(),Pr.clear()}async function Hl(e,t,n={}){Er(t);const a=pa.find(f=>f.key===e)??pa[0],r=vc(n.sort??"uncached"),s=n.direction??"desc",i=Math.max(1,Math.round(n.limit??a.limit)),o=Math.max(0,Math.round(n.offset??0)),h=async()=>{const f=new URLSearchParams({limit:String(i),offset:String(o),sort:r,direction:s});Nr(f,"include_archived",n.includeArchived);const d=await fetch(`${a.path}?${f.toString()}`,{headers:{Accept:"application/json","X-Codex-Usage-Token":t.apiToken},cache:"no-store",signal:n.signal});return await Dr(d,a.title)};return n.signal?h():jr(Mr,bc(a.key,t,i,o,r,s,n.cacheKey,n.includeArchived),h)}async function Ql(e,t,n={}){Er(t);const a=Math.max(1,Math.round(n.limit??8)),r=Math.max(0,Math.round(n.offset??0)),s=n.sort??"tokens",i=n.direction??"desc",o=async()=>{const h=String(e.fact_type??""),f=String(e.fact_name??"");if(!h||!f)throw new Error("Diagnostic fact type and name are required.");const d=new URLSearchParams({fact_type:h,fact_name:f,limit:String(a),offset:String(r),sort:s,direction:i});Nr(d,"include_archived",n.includeArchived);const v=await fetch(`/api/diagnostics/fact-calls?${d.toString()}`,{headers:{Accept:"application/json","X-Codex-Usage-Token":t.apiToken},cache:"no-store",signal:n.signal}),u=await Dr(v,"Diagnostic fact calls");return{calls:(u.rows??[]).map((p,g)=>fr(p,g)),rawPayload:u}};return n.signal?o():jr(Pr,wc(e,t,a,r,s,i,n.cacheKey,n.includeArchived),o)}function jr(e,t,n){const a=e.get(t);if(a!==void 0)return Promise.resolve(a);const r=n().then(s=>(e.set(t,s),s)).catch(s=>{throw e.delete(t),s});return e.set(t,r),r}function Ar(e){return`${e.fileMode?"file":"live"}:${e.apiToken}`}function bc(e,t,n,a,r,s,i="",o){return["facts",e,Ar(t),i,o??"default",n,a,r,s].join(":")}function vc(e){return e==="total"?"tokens":e==="latest"?"time":e}function wc(e,t,n,a,r,s,i="",o){return["fact-calls",Ar(t),i,o??"default",String(e.fact_type??""),String(e.fact_name??""),n,a,r,s].join(":")}function Nr(e,t,n){n!==void 0&&e.set(t,String(n))}function Er(e){if(e.fileMode)throw new Error("Diagnostic facts require localhost dashboard server.");if(!e.apiToken)throw new Error("Diagnostic facts require localhost dashboard API token.")}async function Dr(e,t){if(!e.ok)throw new Error(`${t} request failed with HTTP ${e.status}`);const n=await e.json();if(n.error)throw new Error(n.error);return n}const yc=[{id:"usage-snapshot",endpoint:"/api/usage",dataClass:"snapshot",schema:"codex-usage-tracker-dashboard-v1"},{id:"overview-summary",endpoint:"/api/summary",dataClass:"aggregate",schema:"codex-usage-tracker-summary-v1"},{id:"overview-recommendations",endpoint:"/api/recommendations",dataClass:"aggregate",schema:"codex-usage-tracker-recommendations-v1"},{id:"calls",endpoint:"/api/calls",dataClass:"aggregate",schema:"codex-usage-tracker-calls-v1"},{id:"threads",endpoint:"/api/threads",dataClass:"aggregate",schema:"codex-usage-tracker-threads-v1"},{id:"thread-calls",endpoint:"/api/thread-calls",dataClass:"detail",schema:"codex-usage-tracker-thread-calls-v1"},{id:"investigator-agentic",endpoint:"/api/investigations/agentic",dataClass:"aggregate",schema:"codex-usage-tracker-agentic-investigation-v1"},{id:"investigator-walk",endpoint:"/api/investigations/walk",dataClass:"userAction",schema:"codex-usage-tracker-investigation-walk-v1"},{id:"diagnostics-facts",endpoint:"/api/diagnostics/facts",dataClass:"aggregate",schema:null},{id:"diagnostics-fact-calls",endpoint:"/api/diagnostics/fact-calls",dataClass:"detail",schema:null},{id:"diagnostics-dedupe",endpoint:"/api/diagnostics/dedupe",dataClass:"aggregate",schema:"codex-usage-tracker-dedupe-diagnostics-v1"},{id:"diagnostics-snapshot",endpoint:"/api/diagnostics/{snapshot}",dataClass:"aggregate",schema:null},{id:"compression-profile",endpoint:"/api/compression/profile",dataClass:"aggregate",schema:"codex-usage-tracker-compression-api-v1"},{id:"reports",endpoint:"/api/reports/pack",dataClass:"aggregate",schema:"codex-usage-tracker-reports-pack-v1"}],_c=yc,Sc=new Map(_c.map(e=>[e.id,e])),Cc=15*6e4,xc=3e4,Fr={snapshot:ot({persistedCache:"aggregate-only"}),aggregate:ot({persistedCache:"aggregate-only"}),detail:ot(),heavyJob:ot({cancellation:"shared-job",staleTime:1e3}),userAction:ot({retry:0,staleTime:0})};function kc({sourceKey:e,sourceRevision:t}){return{sourceKey:ba(e,"local-api"),sourceRevision:ba(t,"unversioned")}}function Lc(e,t,n={},...a){return["dashboard",e.id,t.sourceKey,t.sourceRevision,Mc(n),...a]}function Rc(e){return["dashboard",e.id]}function Tc(e){const t=Sc.get(e);if(!t)throw new Error(`Unknown dashboard query definition: ${e}`);return t}function Ir(e){const t=Fr[e];return{gcTime:t.gcTime,refetchOnReconnect:t.refetchOnReconnect,refetchOnWindowFocus:t.refetchOnWindowFocus,retry:t.retry,staleTime:t.staleTime}}function Wl({enabled:e,hasData:t,isError:n,isFetching:a,isPending:r}){return e?t?a?"updating":"ready":n?"error":a||r?"loading":"waiting":"waiting"}function zl(e){const t=e.filter(a=>a==="ready"||a==="updating").length,n=e.length;return{ready:t,total:n,percent:n===0?100:Math.round(t/n*100),loading:e.filter(a=>a==="loading").length,errors:e.filter(a=>a==="error").length}}function Mc(e){return{historyScope:e.historyScope??"active",loadWindow:e.loadWindow??"all",limit:e.limit??null,since:e.since??""}}function ba(e,t){return(e==null?void 0:e.trim())||t}function ot(e={}){return{staleTime:xc,gcTime:Cc,retry:1,refetchOnReconnect:!1,refetchOnWindowFocus:!1,cancellation:"observer",persistedCache:"none",...e}}const Pc=new Set(["command_text","content_fragment","content_fragments","excerpt","indexed_content","indexed_fragment","indexed_fragments","prompt","raw_context","raw_output","snippet","tool_output"]),jc=new Set(["includes_indexed_content","includes_raw_fragment","includes_raw_fragments","indexed_content_included","raw_content_included","raw_context_included"]),Ac=new Set(["codex-usage-tracker-context-v1"]);function va(e){return on(e,new WeakSet)}function on(e,t){if(!e||typeof e!="object")return!0;if(t.has(e))return!1;t.add(e);const n=Array.isArray(e)?e.every(a=>on(a,t)):Object.entries(e).every(([a,r])=>{const s=a.toLowerCase();return Pc.has(s)||jc.has(s)&&r===!0||s==="schema"&&typeof r=="string"&&Ac.has(r)?!1:on(r,t)});return t.delete(e),n}const Nc="codexUsageDashboard",Ec=1,Y="usageSnapshots",Dc=6,Fc=10080*60*1e3,Ic={async read(e){if(!e.sourceRevision)return null;try{const t=await ya();if(!t)return null;const n=await $r(t.transaction(Y,"readonly").objectStore(Y).get(wa(e)));return!n||n.sourceRevision!==e.sourceRevision||Date.now()-n.storedAt>Fc?null:va(n.payload)?n.payload:(await $c(t,n.cacheKey),null)}catch{return null}},async write(e,t){if(!(!e.sourceRevision||!va(t)))try{const n=await ya();if(!n)return;const a=n.transaction(Y,"readwrite");a.objectStore(Y).put({...e,cacheKey:wa(e),payload:t,storedAt:Date.now()}),await Cn(a),await Oc(n)}catch{}}};async function $c(e,t){const n=e.transaction(Y,"readwrite");n.objectStore(Y).delete(t),await Cn(n)}function wa(e){const{historyScope:t,loadWindow:n,limit:a,since:r}=e.scope;return JSON.stringify([e.sourceKey,t,n,a,r])}function ya(){return typeof indexedDB>"u"?Promise.resolve(null):new Promise(e=>{const t=indexedDB.open(Nc,Ec);t.onupgradeneeded=()=>{t.result.objectStoreNames.contains(Y)||t.result.createObjectStore(Y,{keyPath:"cacheKey"})},t.onsuccess=()=>e(t.result),t.onerror=()=>e(null),t.onblocked=()=>e(null)})}function $r(e){return new Promise((t,n)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>n(e.error??new Error("IndexedDB request failed"))})}function Cn(e){return new Promise((t,n)=>{e.oncomplete=()=>t(),e.onerror=()=>n(e.error??new Error("IndexedDB transaction failed")),e.onabort=()=>n(e.error??new Error("IndexedDB transaction aborted"))})}async function Oc(e){const t=e.transaction(Y,"readonly"),a=(await $r(t.objectStore(Y).getAll())).sort((i,o)=>o.storedAt-i.storedAt).slice(Dc);if(!a.length)return;const r=e.transaction(Y,"readwrite"),s=r.objectStore(Y);a.forEach(i=>s.delete(i.cacheKey)),await Cn(r)}const Uc={kind:"production",load:yo},Vc="codexUsageDashboardRuntimeMetadata",Kc=2048,cn=Tc("usage-snapshot"),Or={all:Rc(cn),snapshot:(e,t,n)=>Lc(cn,kc({sourceKey:e,sourceRevision:t}),n)};function qc(){return new ki({defaultOptions:{queries:{...Ir("aggregate")}}})}const xn=qc();async function Hc({currentPayload:e,historyScope:t,loadWindow:n,loadLimit:a,since:r=null,onProgress:s,queryClient:i=xn,refresh:o=!1,snapshotStore:h=Ic,transport:f=Uc}){const d=Ui(a,t,r,n),v=kn(e),u=Or.snapshot(v.sourceKey,v.sourceRevision,d);!i.getQueryData(u)&&Gc(e,d)&&i.setQueryData(u,e),o&&await i.invalidateQueries({queryKey:u,exact:!0});const p=_a(e,d),g=await i.fetchQuery({queryKey:u,...Ir(cn.dataClass),queryFn:async({signal:b})=>{var T;const w=!o&&p?await h.read(p):null;if(w)return s==null||s({status:"completed",phase:"loading_rows",message:"Loaded cached dashboard snapshot",completed:Number(w.loaded_row_count??((T=w.rows)==null?void 0:T.length)??0),total:Number(w.total_available_rows??w.loaded_row_count??0),percent:100}),w;const L=await f.load(e,{refresh:o,limit:Vi(d),includeArchived:d.historyScope==="all",loadWindow:n,since:d.since,onProgress:s,signal:b}),N=p?{...p,sourceRevision:String(L.latest_refresh_at??p.sourceRevision)}:_a(L,d);return N&&await h.write(N,L),L},staleTime:o?0:Fr.snapshot.staleTime});return Bc(zc(g,d)),g}function _a(e,t){if(!(e!=null&&e.api_token))return null;const n=String(e.latest_refresh_at??"");return n?{sourceKey:Ur(e),sourceRevision:n,scope:t}:null}async function Qc(e=xn){await e.cancelQueries({queryKey:Or.all})}function Wc(e){return bi(e)||rr(e)}function zc(e,t){return{schema:"codex-usage-dashboard-runtime-v1",...kn(e),scope:t,updatedAt:Date.now()}}function kn(e){return{sourceKey:Ur(e),sourceRevision:Jc(e)}}function Bc(e,t=Zc()){if(t)try{const n=JSON.stringify(e);n.length<=Kc&&t.setItem(Vc,n)}catch{}}function Gc(e,t){var o;if(!e||!(((o=e.rows)==null?void 0:o.length)??0))return!1;const n=ct(e),a=t.limit===null?n===0:n===t.limit,r=(e.since??null)===t.since,s=fn(e)===t.loadWindow,i=e.include_archived||e.history_scope==="all-history"?"all":"active";return a&&r&&s&&i===t.historyScope}function Ur(e){const t=e,n=Number((t==null?void 0:t.payload_cache_version)??0),a=String((t==null?void 0:t.payload_cache_key)??(e!=null&&e.api_token?"live":"static"));return`${n}:${a}`}function Jc(e){return String((e==null?void 0:e.latest_refresh_at)??"unversioned")}function Zc(){try{return typeof window>"u"?null:window.sessionStorage}catch{return null}}const Xc=new Set(["duplicate_cumulative_total"]);function Yc({canUseLiveApi:e,payload:t,shellI18n:n}){const a=el(t,e,n);return c.jsxs("section",{className:"environment-status","aria-label":n.t("aria.dashboard_status","Dashboard status"),children:[c.jsx("span",{className:"environment-status-title",children:n.t("aria.dashboard_status","Dashboard status")}),c.jsx("div",{className:"environment-status-grid",children:a.map(r=>c.jsx("span",{className:"environment-chip","data-state":r.state,title:r.title,children:r.label},r.label))})]})}function el(e,t,n){const a=sl(e==null?void 0:e.parser_diagnostics,n);return[{label:n.t("badge.unofficial_project","Unofficial project"),state:"neutral",title:n.t("badge.unofficial_project_title","Codex Usage Tracker is independent and is not made by, affiliated with, endorsed by, sponsored by, or supported by OpenAI. OpenAI and Codex are trademarks of OpenAI.")},{label:t?`${n.t("badge.live","Live")} API`:n.t("badge.static","Static"),state:t?"ready":"warn",title:t?"Local API token present for refresh actions.":"Static embedded snapshot; live refresh is unavailable."},nl(e,n),al(e,n),rl(e,n),tl(e),...a?[a]:[]]}function tl(e){const t=e==null?void 0:e.dedupe,n=Number((t==null?void 0:t.excluded_copied_rows)||0),a=Number((t==null?void 0:t.canonical_rows)||0),r=Number((t==null?void 0:t.physical_rows)||0);return{label:`Deduped · ${n.toLocaleString()} copied excluded`,state:"ready",title:`Billable totals use ${a.toLocaleString()} canonical rows while preserving ${r.toLocaleString()} physical source rows.`}}function nl(e,t){var s;const n=!!(e!=null&&e.pricing_configured),a=((s=e==null?void 0:e.pricing_snapshot_warning)==null?void 0:s.trim())??"",r=Vr(e==null?void 0:e.pricing_source);return{label:n?t.t("badge.costs","Costs"):t.t("badge.no_costs","No costs"),state:n?a?"warn":"ready":"missing",title:n?[r||"Pricing configured",a].filter(Boolean).join(" - "):t.t("pricing.configure_hint","Run codex-usage-tracker update-pricing to configure estimated costs.")}}function al(e,t){var s,i;const n=((s=e==null?void 0:e.allowance_error)==null?void 0:s.trim())??"",a=((i=e==null?void 0:e.rate_card_error)==null?void 0:i.trim())??"",r=Vr(e==null?void 0:e.allowance_source)||"Codex credit rates";return n?{label:t.t("state.allowance_config_error","Allowance config error"),state:"missing",title:`Config error: ${n}`}:a?{label:"Rate-card error",state:"missing",title:`Rate-card error: ${a}`}:e!=null&&e.allowance_configured?{label:t.t("state.allowance_configured","Allowance configured"),state:"ready",title:r}:e!=null&&e.rate_card_configured?{label:"Credit rates loaded",state:"ready",title:r}:{label:t.t("action.set_limits","Set limits"),state:"warn",title:"No local allowance windows are configured."}}function rl(e,t){const n=e==null?void 0:e.project_metadata_privacy,a=(n==null?void 0:n.mode)||(e==null?void 0:e.privacy_mode)||"normal",r=a==="normal",s=[n!=null&&n.cwd_redacted?"cwd redacted":"",n!=null&&n.project_names_redacted?"project names redacted":"",n!=null&&n.git_remote_label_hidden?"git remote hidden":"",n!=null&&n.relative_cwd_hidden?"relative cwd hidden":"",n!=null&&n.git_branch_hidden?"git branch hidden":"",n!=null&&n.tags_hidden?"tags hidden":""].filter(Boolean);return{label:r?t.t("badge.metadata_normal","Metadata normal"):Kr(t.t("badge.metadata_mode","Metadata {mode}"),{mode:a}),state:r?"ready":"warn",title:r?"Project metadata is shown normally.":[`Metadata mode: ${a}`,...s].join(" - ")}}function sl(e,t){const n=Object.entries(e??{}).filter(([s,i])=>Number(i||0)>0&&!Xc.has(s));if(!n.length)return null;const a=n.reduce((s,[,i])=>s+Number(i||0),0),r=n.map(([s,i])=>`${s}=${Number(i||0)}`).join(", ");return{label:t.t("badge.parser_warnings","Parser warnings"),state:"missing",title:Kr(t.t("parser.warnings_title","Latest refresh reported {count} parser diagnostics: {entries}. Run codex-usage-tracker inspect-log to investigate schema drift."),{count:a.toLocaleString(),entries:r})}}function Vr(e){if(!e)return"";if(typeof e=="string")return e;const t=e.label??e.name??e.type??e.path??"";return typeof t=="string"?t:""}function Kr(e,t){return e.replace(/\{([a-zA-Z0-9_]+)\}/g,(n,a)=>t[a]??n)}async function Sa(e){var n;if((n=navigator.clipboard)!=null&&n.writeText)return await navigator.clipboard.writeText(e),!0;const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly","readonly"),t.style.position="fixed",t.style.left="-9999px",t.style.top="0",document.body.appendChild(t),t.select();try{return document.execCommand("copy")}finally{t.remove()}}const il={"highest-cost":"Highest Cost Threads","context-bloat":"Context Bloat","cache-misses":"Cache Misses","pricing-gaps":"Pricing Gaps","codex-credits":"Codex Credits","reasoning-spike":"Reasoning Spike"};function Bl(e,t){return t==="context-bloat"?Number(e.contextWindowPct??0)>=60||e.totalTokens>=2e5:t==="cache-misses"?e.signal==="cache-risk"||e.cachedPct<30||e.uncachedInput>=5e4:t==="pricing-gaps"?e.pricingEstimated||!Number.isFinite(e.cost):t==="codex-credits"?e.credits>0:t==="reasoning-spike"?e.reasoningOutput>0:!0}function ol(e){return il[e]??e}const cl=se(()=>O(()=>import("./OverviewPage.js"),__vite__mapDeps([49,1,2,34,4,20,21,9,10,31,32,6,16,17,18,45,22,11,12,13,14,35,23,24,25,26,50])),"OverviewPage"),ll=se(()=>O(()=>import("./InvestigatorPage.js"),__vite__mapDeps([51,1,2,34,4,6,41,7,20,21,9,10,16,17,18,45,22,11,12,13,23,24,38,25,26,52])),"InvestigatorPage"),ul=se(()=>O(()=>import("./CompressionLabPage.js"),__vite__mapDeps([53,1,2,34,4,6,9,10,17,25,26,12,54])),"CompressionLabPage"),dl=se(()=>O(()=>import("./ExploreRoutePage.js"),__vite__mapDeps([55,1,2,44,3,4,5,6,7,14,29,33,19,45,22,11,12,13,35,23,24,34,46,47,38,25,26,48,8,9,10,56])),"ExploreRoutePage"),hl=se(()=>O(()=>import("./CallInvestigatorPage.js"),__vite__mapDeps([57,1,2,46,33,19,37,38,47,25,26,12])),"CallInvestigatorPage"),fl=se(()=>O(()=>import("./ThreadsPage.js"),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27])),"ThreadsPage"),ml=se(()=>O(()=>import("./UsageDrainPage.js"),__vite__mapDeps([36,1,2,34,4,20,21,9,10,16,17,18,24,37,38,25,26,12,39])),"UsageDrainPage"),gl=se(()=>O(()=>import("./CacheContextPage.js"),__vite__mapDeps([28,1,2,29,30,22,31,32,6,33,19,20,21,9,10,23,34,4,3,5,7,15,35,24,25,26,12])),"CacheContextPage"),pl=se(()=>O(()=>import("./DiagnosticsPage.js"),__vite__mapDeps([40,1,2,29,33,19,20,21,9,10,23,24,41,4,6,7,34,3,25,26,12])),"DiagnosticsPage"),bl=se(()=>O(()=>import("./ReportsPage.js"),__vite__mapDeps([42,1,2,34,4,6,33,19,20,21,9,10,16,17,18,30,22,35,23,24,25,26,12,43])),"ReportsPage"),vl=se(()=>O(()=>import("./SettingsPage.js"),__vite__mapDeps([58,1,2,33,19,32,38,47,17,25,26,12,59])),"SettingsPage");function wl(e){return c.jsx(C.Suspense,{fallback:c.jsx(_l,{activeView:e.activeView}),children:yl(e)})}function yl(e){const{activePreset:t,activeRecordId:n,activeView:a,autoRefreshEnabled:r,applicationI18n:s,backFromCallInvestigator:i,callBackLabel:o,canLoadAllRows:h,canUseLiveApi:f,contextRuntime:d,copyCallInvestigatorLink:v,dashboardPayload:u,globalFilters:p,globalQuery:g,hasMoreRows:b,historyScope:w,loadWindow:L,loadAllRows:N,loadedRowCount:T,loadLimit:_,loadMoreRows:R,scopeSince:y,model:k,navigateView:j,onRefresh:E,openCallInvestigator:D,refreshing:me,refreshState:bt,setContextApiEnabled:vt,sourceIdentity:$,totalAvailableRows:at}=e;switch(a){case"overview":return c.jsx(cl,{model:k,contextRuntime:d,sourceKey:$.sourceKey,sourceRevision:$.sourceRevision,onRefresh:E,globalQuery:g,runtime:{historyScope:w,loadLimit:_,loadWindow:L,loadedRowCount:T,scopeSince:y,totalAvailableRows:at},refreshing:me,canLoadMoreRows:f&&b,onLoadMoreRows:R,onOpenInvestigator:D,onCopyCallLink:v,onNavigateView:j,globalFilters:p});case"investigator":return c.jsx(ll,{model:k,contextRuntime:d,includeArchived:w==="all",sourceKey:$.sourceKey,sourceRevision:$.sourceRevision,onOpenInvestigator:D,onCopyCallLink:v,onNavigateView:j});case"compression-lab":return c.jsx(ul,{contextRuntime:d,includeArchived:w==="all",since:y,sourceKey:$.sourceKey,sourceRevision:$.sourceRevision});case"calls":return c.jsx(dl,{model:k,globalQuery:g,activePreset:t,onRefresh:E,contextRuntime:d,includeArchived:w==="all",scopeSince:y,sourceKey:$.sourceKey,sourceRevision:$.sourceRevision,onContextApiEnabledChange:vt,onOpenInvestigator:D,onCopyCallLink:v,onNavigateView:j});case"call":return c.jsx(hl,{model:k,recordId:n,contextRuntime:d,onContextApiEnabledChange:vt,onNavigateRecord:D,onCopyCallLink:v,onBackToCalls:i,backLabel:o});case"threads":return c.jsx(fl,{model:k,globalQuery:g,onOpenInvestigator:D,onCopyCallLink:v,globalFilters:p,contextRuntime:d,includeArchived:w==="all",sourceKey:$.sourceKey,sourceRevision:$.sourceRevision,onNavigateView:j});case"usage-drain":return c.jsx(ml,{model:k,contextRuntime:d,includeArchived:w==="all",sourceRevision:$.sourceRevision,onOpenInvestigator:D,onCopyCallLink:v});case"cache-context":return c.jsx(gl,{model:k,contextRuntime:d,includeArchived:w==="all",scopeSince:y,sourceKey:$.sourceKey,sourceRevision:$.sourceRevision,onOpenInvestigator:D,onCopyCallLink:v});case"diagnostics":return c.jsx(pl,{model:k,contextRuntime:d,includeArchived:w==="all",sourceKey:$.sourceKey,sourceRevision:$.sourceRevision,rowLoadControls:{loadedRowCount:T,totalAvailableRows:at,canLoadMoreRows:f&&b,canLoadAllRows:h,refreshing:me,onLoadMoreRows:R,onLoadAllRows:N},onOpenInvestigator:D,onCopyCallLink:v,globalFilters:p});case"reports":return c.jsx(bl,{model:k,refreshState:bt,includeArchived:w==="all",loadWindow:L,loadLimit:_,sourceKey:$.sourceKey,sourceRevision:$.sourceRevision,onOpenInvestigator:D,onCopyCallLink:v});case"settings":return c.jsx(vl,{model:k,payload:u,historyScope:w,loadWindow:L,loadLimit:_,scopeSince:y,loadedRowCount:T,totalAvailableRows:at,canUseLiveApi:f,autoRefreshEnabled:r,refreshState:bt,applicationI18n:s})}}function _l({activeView:e}){return c.jsxs("section",{"aria-busy":"true","aria-live":"polite",className:"route-state",role:"status",children:["Loading ",e.replace("-"," "),"..."]})}const Ca=1e4,Sl={1:"overview",2:"calls",3:"threads",4:"diagnostics"},Cl=new Set(["call","compression-lab","diagnostics","investigator"]);function qr(e){return!Cl.has(e)}function xl(e){return e instanceof Element?!!e.closest('input, select, textarea, button, [contenteditable="true"]'):!1}function Hr(){var Un;const e=C.useRef(null),t=C.useMemo(()=>wo(),[]),[n,a]=C.useState(t),[r,s]=C.useState(()=>ra(t)),[i,o]=C.useState(()=>{const m=new URLSearchParams(window.location.search);return sn(m.get("view"))}),[h,f]=C.useState(()=>new URLSearchParams(window.location.search).get("record")??""),[d,v]=C.useState(()=>da()),[u,p]=C.useState(()=>ha()),[g,b]=C.useState(()=>new URLSearchParams(window.location.search).get("q")??""),[w,L]=C.useState(()=>new URLSearchParams(window.location.search).get("preset")??""),[N,T]=C.useState(()=>window.location.search),[_,R]=C.useState("Stored snapshot loaded just now"),[y,k]=C.useState(null),[j,E]=C.useState(!1),[D,me]=C.useState(!1),[bt,vt]=C.useState(!1),[$,at]=C.useState(()=>Ws(t)),It=C.useRef(!1),[G,Ln]=C.useState(()=>je(ct(t),500)),[Ue,wt]=C.useState(()=>je(ct(t),500)),[q,Rn]=C.useState(()=>Ki(t)),[J,yt]=C.useState(()=>fa(Qt(t))),[Tn,Mn]=C.useState(r.contextRuntime.contextApiEnabled),H=!!(n!=null&&n.api_token),Qr=C.useMemo(()=>kn(n),[n]),A=C.useMemo(()=>Da(n,$),[n,$]),Pn=C.useMemo(()=>({...r.contextRuntime,contextApiEnabled:Tn}),[Tn,r.contextRuntime]),Wr=C.useMemo(()=>Oo(r,J,N),[J,N,r]),jn=i==="calls"||i==="call"?r:Wr,rt=qr(i),$t=je(Ue,G,500),ge=Math.max(0,Number((n==null?void 0:n.loaded_row_count)??((Un=n==null?void 0:n.rows)==null?void 0:Un.length)??r.calls.length??0)),Ve=Math.max(0,Number((n==null?void 0:n.total_available_rows)??ge)),zr=Ue!==G,An=Qi({currentLimit:G,loadedRows:ge,pendingLimit:Ue}),Br=Math.min($t,An),Nn=!!(n!=null&&n.has_more)||Ve>0&&ge{const m=new URL(window.location.href);ga(m)&&Ke(m)},[]),C.useEffect(()=>{function m(){const S=window.location.search,M=new URLSearchParams(S);o(sn(M.get("view"))),f(M.get("record")??""),v(da(S)),p(ha(S)),b(M.get("q")??""),L(M.get("preset")??""),yt(fa(Qt(n),S)),T(S)}return window.addEventListener("popstate",m),()=>window.removeEventListener("popstate",m)},[n]),C.useEffect(()=>{function m(S){var oe;if(xl(S.target))return;if(S.key==="/"){S.preventDefault(),(oe=e.current)==null||oe.focus();return}const M=Sl[S.key];M&&(S.preventDefault(),st(M))}return window.addEventListener("keydown",m),()=>window.removeEventListener("keydown",m)},[]),C.useEffect(()=>{function m(){vt(window.scrollY>320)}return m(),window.addEventListener("scroll",m,{passive:!0}),()=>window.removeEventListener("scroll",m)},[]);function ts(m){const S=i==="call"?d:i;v(S),p(i==="call"?u:!0),o("call"),f(m);const M=new URL(window.location.href);xt(M,i,i==="call"?d:[]),M.searchParams.set("view","call"),M.searchParams.set("record",m),M.searchParams.set("return",S),Fn(M)}function ns(){st(d)}function as(m){b(m),L("");const S=new URL(window.location.href);S.searchParams.delete("preset"),m?S.searchParams.set("q",m):S.searchParams.delete("q"),Ke(S)}async function ie(m={}){var _t;if(j)return;It.current=!0;const S=m.loadLimit??G,M=m.loadWindow??q,oe=Ht(M),pe=m.historyScope??J,ee=m.refresh??!0,Ot=!!(n!=null&&n.refresh_jobs_available);E(!0),k(Ot?{status:"running",phase:ee?"refreshing_index":"loading_rows",message:`${ee?"Refreshing":"Loading"} ${Te(M,S)}`}:null),R(`${ee?"Refreshing index for":"Loading"} ${Te(M,S)}...`);let qe=!1;try{const B=await Hc({currentPayload:n,refresh:ee,loadLimit:S,loadWindow:M,since:oe,historyScope:pe,onProgress:Vt=>{qe||(qe=Vt.message==="Loaded cached dashboard snapshot"),k(Vt),R(Xn(Vt,pe))}});pc(),a(B),s(ra(B)),Mn(!!B.context_api_enabled);const xe=M==="rows"?je(ct(B,S),S):S;Ln(xe),wt(xe),Rn(M);const Vn=Qt(B,pe);yt(Vn),Hi(xe,Vn,M);const Ut=B.loaded_row_count??((_t=B.rows)==null?void 0:_t.length)??0,Kn=B.total_available_rows??Ut;R(M==="rows"?`${qe?"Cache hit; l":ee?"Refreshed index; l":"L"}oaded ${Ut.toLocaleString()} of ${Kn.toLocaleString()} calls from ${Te(M,xe)}`:`${qe?"Cache hit; ":ee?"Refreshed index; ":""}${Te(M,xe)} analysis ready across ${Kn.toLocaleString()} calls; ${Ut.toLocaleString()} detail rows cached`)}catch(B){if(k(null),Wc(B)){R("Refresh cancelled; stored snapshot remains visible");return}const xe=new Date().toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});R(`${Zi(B)} Stored snapshot kept at ${xe}`)}finally{k(null),E(!1)}}async function rs(){await Qc(),k(null),E(!1),R("Refresh cancelled; stored snapshot remains visible")}C.useEffect(()=>{document.documentElement.lang=A.language,document.documentElement.dir=A.direction},[A.direction,A.language]),C.useEffect(()=>{var B;const m=Number((n==null?void 0:n.total_available_rows)??0),S=Number((n==null?void 0:n.loaded_row_count)??((B=n==null?void 0:n.rows)==null?void 0:B.length)??0),M=qi(),oe=(M==null?void 0:M.historyScope)??J,pe=(M==null?void 0:M.loadLimit)??G,ee=(M==null?void 0:M.loadWindow)??q,Ot=fn(n),qe=oe!==J||ee!==Ot||ee==="rows"&&pe!==ct(n),_t=S>0;!H||!qe&&_t||m<=0||j||It.current||(It.current=!0,qe&&(yt(oe),Ln(pe),wt(pe),Rn(ee),Ke(ma(oe))),ie({refresh:!1,historyScope:oe,loadLimit:pe,loadWindow:ee}))},[H,n,J,G,q,j]),C.useEffect(()=>{!H&&D&&me(!1)},[D,H]),C.useEffect(()=>{if(!D||!H||!rt)return;const m=window.setInterval(()=>{ie()},Ca);return()=>window.clearInterval(m)},[D,rt,H,n,J,G,q,j]),C.useEffect(()=>{if(!D||!H||!rt)return;function m(){document.visibilityState==="visible"&&ie()}return document.addEventListener("visibilitychange",m),()=>document.removeEventListener("visibilitychange",m)},[D,rt,H,n,J,G,q,j]);function In(m){const S=m.trim();wt(Math.max(1,gt(S===""?Number.NaN:Number(S))))}function ss(m){In(m)}function is(){ie({refresh:!1,loadLimit:Ue,loadWindow:"rows"})}function os(){ie({refresh:!1,loadWindow:"all"})}function $n(){wt(En),ie({refresh:!1,loadLimit:En,loadWindow:q})}function cs(m){m!==q&&ie({refresh:!1,loadWindow:m})}function ls(m){const S=m==="all"?"all":"active";yt(S),Ke(ma(S)),ie({refresh:!1,historyScope:S})}function us(m){if(me(m),m){if(!rt){R("Auto refresh pauses on this evidence-heavy view");return}R(`Auto refresh every ${Ca/1e3}s`),ie()}else R("Auto refresh paused")}function ds(m){at(m),zs(m)}async function hs(){try{const m=new URL(window.location.href);if(ga(m),xt(m,i,i==="call"?d:[]),!await Sa(m.toString()))throw new Error("Clipboard unavailable");R("Copied current view link")}catch{R("Copy unavailable in browser")}}async function fs(m){try{const S=new URL(window.location.href);xt(S,i,i==="call"?d:[]);const M=i==="call"?d:i;if(S.searchParams.set("view","call"),S.searchParams.set("record",m),S.searchParams.set("return",M),!await Sa(S.toString()))throw new Error("Clipboard unavailable");R("Copied call investigator link")}catch{R("Copy unavailable in browser")}}async function ms(){const m=await Bi(i,jn,{contextRuntime:Pn,historyScope:J,loadWindow:q,loadLimit:G,scopeSince:(n==null?void 0:n.since)??Ht(q),loadedRowCount:ge,totalAvailableRows:Ve,canUseLiveApi:H,autoRefreshEnabled:D,refreshState:_},g,w);if(!m.rowCount){R(`No ${m.label} to export`);return}Ai(m.filename,m.csv),R(`Exported ${m.rowCount} ${m.label}`)}function gs(){L("");const m=new URL(window.location.href);m.searchParams.delete("preset"),Ke(m),R("Investigation preset cleared")}function ps(){window.scrollTo({top:0,behavior:"smooth"})}return c.jsx(no,{value:A,children:c.jsxs("div",{className:"app-shell","data-dashboard-localization-root":!0,children:[c.jsxs("aside",{className:"sidebar",children:[c.jsxs("div",{className:"brand",children:[c.jsx("div",{className:"brand-mark",children:c.jsx(Os,{size:22})}),c.jsxs("div",{children:[c.jsx("strong",{children:"Codex Usage Tracker"}),c.jsx("span",{children:A.t("dashboard.eyebrow","Local telemetry console")})]})]}),c.jsxs("div",{className:"local-pill",children:[c.jsx("span",{"aria-hidden":"true"}),"Local data only"]}),c.jsx(Yc,{payload:n,canUseLiveApi:H,shellI18n:A}),c.jsx("nav",{className:"primary-nav","aria-label":"Primary",children:Sr.map(m=>{const S=m.icon,M=i===m.id||i==="call"&&m.id==="calls";return c.jsxs("button",{type:"button","aria-pressed":M,className:M?"active":"",onClick:()=>st(m.id),children:[c.jsx(S,{size:18}),c.jsx("span",{children:A.navLabel(m.id,m.label)})]},m.id)})}),c.jsxs("div",{className:"secondary-block",role:"group","aria-label":"Quick Links",children:[c.jsx("span",{children:"Quick Links"}),Cr.map(m=>{const S=m.icon;return c.jsxs("button",{type:"button",onClick:()=>st(m.target),children:[c.jsx(S,{size:16}),m.label]},m.label)})]})]}),c.jsxs("main",{className:"workspace",children:[c.jsxs("div",{className:"unofficial-banner",role:"note","aria-label":"Unofficial project notice",children:[c.jsx(Is,{size:16}),c.jsxs("span",{children:[c.jsx("strong",{children:"Unofficial project."})," Not made by, affiliated with, endorsed by, sponsored by, or supported by OpenAI."]})]}),c.jsxs("header",{className:"topbar","aria-label":"Dashboard toolbar",children:[c.jsxs("label",{className:"global-search",children:[c.jsx("span",{className:"sr-only",children:A.t("filter.search","Search dashboard")}),c.jsx("input",{ref:e,"aria-label":A.t("filter.search","Search dashboard"),value:g,onChange:m=>as(m.target.value),placeholder:A.t("filter.search_placeholder","Search calls, threads, models, diagnostics...")})]}),c.jsxs("div",{className:"topbar-actions",children:[c.jsxs("div",{className:"topbar-scope-controls",children:[A.languages.length>1?c.jsxs("label",{className:"topbar-select",children:[c.jsx("span",{children:A.t("language.label","Language")}),c.jsx("select",{"data-localization-skip":"true","aria-label":A.t("language.label","Language"),value:A.language,onChange:m=>ds(m.target.value),children:A.languages.map(m=>c.jsx("option",{value:m.code,children:m.native_name||m.english_name||m.code},m.code))})]}):null,c.jsxs("label",{className:"topbar-select",children:[c.jsx("span",{children:A.t("nav.history","History")}),c.jsxs("select",{"aria-label":"History scope",title:Dn,value:J,onChange:m=>ls(m.target.value),disabled:j||!H,children:[c.jsx("option",{value:"active",children:A.t("option.active_sessions_only","Active")}),c.jsx("option",{value:"all",children:A.t("option.all_history","All history")})]}),c.jsx("small",{className:"sr-only",children:Dn})]})]}),c.jsx(Xo,{canUseLiveApi:H,finitePendingLoadLimit:$t,hasMoreRows:Nn,loadLabel:A.t("nav.load","Load"),loadMoreLabel:A.t("button.load_more","Load more"),loadWindow:q,loadedRowCount:ge,pendingLoadLimit:Ue,refreshProgressPercent:Xr,refreshProgressText:Yr,refreshing:j,rowLimitChanged:zr,rowLimitSliderMax:An,rowLimitSliderValue:Br,rowLoadModeLabel:Jr,rowLoadStatus:Gr,totalAvailableRows:Ve,onApply:is,onCancel:rs,onDraftChange:In,onLoadMore:$n,onSliderChange:ss,onWindowChange:cs}),c.jsxs("div",{className:"topbar-meta",children:[c.jsxs("div",{className:"topbar-statuses",children:[w?c.jsxs("button",{className:"toolbar-button",type:"button",onClick:gs,children:[c.jsx(un,{size:15}),A.t("button.clear","Clear")," ",ol(w)]}):null,c.jsxs("label",{className:"topbar-toggle",children:[c.jsx("input",{"aria-label":"Auto refresh",type:"checkbox",checked:D,onChange:m=>us(m.target.checked),disabled:j||!H}),c.jsx("span",{children:"Auto"})]})]}),c.jsxs("div",{className:"topbar-icon-actions",children:[c.jsx("button",{className:"icon-button",type:"button",onClick:hs,"aria-label":A.t("button.copy_link","Copy link"),title:A.t("button.copy_link","Copy link"),children:c.jsx(Ts,{size:17})}),c.jsx("button",{className:"icon-button",type:"button",onClick:ms,"aria-label":A.t("button.export_csv","Export CSV"),title:A.t("button.export_csv","Export CSV"),children:c.jsx(Ps,{size:17})}),c.jsx("button",{className:"icon-button",type:"button",onClick:On,"aria-label":A.t("button.refresh","Refresh"),title:A.t("button.refresh","Refresh"),disabled:j,children:c.jsx(Ds,{size:17})})]})]})]})]}),c.jsx("p",{className:"sr-only",role:"status","aria-live":"polite",children:_}),c.jsx(wl,{activeView:i,model:jn,navigateView:st,onRefresh:On,refreshState:_,globalQuery:g,activePreset:w,activeRecordId:h,contextRuntime:Pn,setContextApiEnabled:Mn,openCallInvestigator:ts,copyCallInvestigatorLink:fs,callBackLabel:u?`Back to ${cc(d)}`:A.t("button.back_to_dashboard","Back to dashboard"),backFromCallInvestigator:ns,dashboardPayload:n,sourceIdentity:Qr,historyScope:J,loadWindow:q,scopeSince:(n==null?void 0:n.since)??Ht(q),loadLimit:G,loadedRowCount:ge,totalAvailableRows:Ve,canUseLiveApi:H,autoRefreshEnabled:D,applicationI18n:A,refreshing:j,hasMoreRows:Nn,canLoadAllRows:es,loadMoreRows:$n,loadAllRows:os,globalFilters:c.jsx(Yo,{activeView:i,locationSearch:N,model:r,onUrlChange:Ke})})]}),bt?c.jsxs("button",{className:"to-top-button",type:"button",onClick:ps,"aria-label":"Back to top",children:[c.jsx(Cs,{size:18}),A.t("button.top","Top")]}):null]})});function On(){ie()}}function kl(){return c.jsx(Li,{client:xn,children:c.jsx(Hr,{})})}const Gl=Object.freeze(Object.defineProperty({__proto__:null,App:Hr,RoutedApp:kl,shouldAutoRefreshUsageView:qr},Symbol.toStringTag,{value:"Module"}));export{Dl as $,Cs as A,jl as B,Di as C,Ps as D,$l as E,Ns as F,Ss as G,Ms as H,va as I,Et as J,z as K,Al as L,Va as M,ne as N,Hl as O,vc as P,Vl as Q,Ds as R,Fs as S,Ql as T,Rs as U,$s as V,Bl as W,Sa as X,As as Y,un as Z,ol as _,Ts as a,El as a0,hi as a1,si as a2,Gt as a3,qa as a4,ai as a5,ri as a6,Bt as a7,Ua as a8,yi as a9,ci as aa,Nl as ab,Il as ac,xt as ad,da as ae,Te as af,Gl as ag,Ol as b,I as c,Ai as d,ce as e,Re as f,Ni as g,zl as h,Wl as i,Le as j,cr as k,Fl as l,Xt as m,Lr as n,ql as o,Rt as p,Kl as q,ji as r,pa as s,Ul as t,Oa as u,fr as v,Ir as w,Lc as x,kc as y,Tc as z}; + */const dn=$("X",[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]]),Da="codex-usage-dashboard-language",Ws={"button.back_to_dashboard":"Back to dashboard","button.clear":"Clear","button.copy_link":"Copy link","button.enable_context_loading":"Enable context loading","button.export_csv":"Export CSV","button.full_serialized_analysis":"Run full serialized analysis","button.hide_details":"Hide details","button.include_tool_output":"Include tool output","button.load_more":"Load more","button.load_older_context":"Load older entries","button.next_call":"Next call","button.no_char_limit":"No char limit","button.open_investigator":"Open investigator","button.previous_call":"Previous call","button.refresh":"Refresh","button.show_compaction_history":"Show compacted replacement","button.show_tool_output":"Show tool output","button.top":"Top","button.show_turn_evidence":"Show turn log evidence","badge.live":"Live","dashboard.eyebrow":"Local Codex analytics","dashboard.call_details":"Call Details","dashboard.title":"Usage Dashboard","dashboard.view.call":"Call Investigator","dashboard.view.calls":"Calls","dashboard.view.insights":"Overview","dashboard.view.overview":"Overview","dashboard.view.threads":"Threads","detail.next_action":"Next action","filter.search":"Search dashboard","filter.search_placeholder":"Search calls, threads, models, diagnostics...","language.label":"Language","nav.history":"History","nav.load":"Load","nav.live":"Live","option.active_sessions_only":"Active","option.all_history":"All history","status.paused":"Paused","status.static":"Static"},Bs={overview:"dashboard.view.overview",calls:"dashboard.view.calls",call:"dashboard.view.call",threads:"dashboard.view.threads"};function Il(e,t){return typeof t=="string"?e.translateText(t):e.formatText(t.template,t.values)}function $a(e,t){const n=Ia(e),a=Oa(t,n),r=(e==null?void 0:e.translation_catalog)??{},s=r[a]??((e==null?void 0:e.language)===a?e==null?void 0:e.translations:void 0)??{},i=r.en??((e==null?void 0:e.language)==="en"?e.translations:void 0)??{},o={...Ws,...i,...s},d=zs(i,s),f=Qs(i,s),h=n.find(p=>p.code===a),v=(h==null?void 0:h.dir)==="rtl"||!h&&(e==null?void 0:e.language_direction)==="rtl"?"rtl":"ltr",u=p=>a==="zh-Hans"?ys(p,d,f):d.get(p)??p;return{language:a,direction:v,languages:n,t:(p,g)=>o[p]??g??p,translateText:u,formatText:(p,g)=>u(p).replace(/\{([A-Za-z][A-Za-z0-9_]*)\}/gu,(b,w)=>String(g[String(w)]??b)),navLabel:(p,g)=>{const b=Bs[p];return b?o[b]??g:g}}}function Qs(e,t){const n=[];for(const[a,r]of Object.entries(e)){const s=t[a];if(!s||s===r||!r.includes("{"))continue;const i=[],o=[];let d=0;for(const f of r.matchAll(/\{([A-Za-z][A-Za-z0-9_]*)\}/gu)){const h=f.index??d;o.push(Wn(r.slice(d,h))),o.push("(.+?)"),i.push(f[1]),d=h+f[0].length}o.push(Wn(r.slice(d))),n.push({pattern:new RegExp(`^${o.join("")}$`,"u"),placeholders:i,translatedTemplate:s})}return n.sort((a,r)=>r.translatedTemplate.length-a.translatedTemplate.length)}function Wn(e){return e.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")}function zs(e,t){const n=new Map;for(const[a,r]of Object.entries(e)){const s=t[a];s&&s!==r&&n.set(r,s)}return n}function Ia(e){var n;const t=((n=e==null?void 0:e.available_languages)==null?void 0:n.filter(a=>a.code))??[];return t.length?t:[{code:"en",english_name:"English",native_name:"English",dir:"ltr"}]}function Gs(e){return Oa(Zs()||(e==null?void 0:e.language)||"en",Ia(e))}function Js(e){var t;try{(t=window.localStorage)==null||t.setItem(Da,e)}catch{}}function Zs(){var e;try{return((e=window.localStorage)==null?void 0:e.getItem(Da))??""}catch{return""}}function Oa(e,t){if(new Set(t.map(s=>s.code)).has(e))return e;const a=e.toLowerCase(),r=t.find(s=>s.code.toLowerCase()===a);return(r==null?void 0:r.code)??"en"}const Xs=$a(null,"en"),Ua=C.createContext(Xs);function Ys({value:e,children:t}){return c.jsx(Ua.Provider,{value:e,children:t})}function Va(){return C.useContext(Ua)}var Et=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(e){return this.listeners.add(e),this.onSubscribe(),()=>{this.listeners.delete(e),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}},Ne,we,ze,Ta,ei=(Ta=class extends Et{constructor(){super();P(this,Ne);P(this,we);P(this,ze);x(this,ze,t=>{if(typeof window<"u"&&window.addEventListener){const n=()=>t();return window.addEventListener("visibilitychange",n,!1),()=>{window.removeEventListener("visibilitychange",n)}}})}onSubscribe(){l(this,we)||this.setEventListener(l(this,ze))}onUnsubscribe(){var t;this.hasListeners()||((t=l(this,we))==null||t.call(this),x(this,we,void 0))}setEventListener(t){var n;x(this,ze,t),(n=l(this,we))==null||n.call(this),x(this,we,t(a=>{typeof a=="boolean"?this.setFocused(a):this.onFocus()}))}setFocused(t){l(this,Ne)!==t&&(x(this,Ne,t),this.onFocus())}onFocus(){const t=this.isFocused();this.listeners.forEach(n=>{n(t)})}isFocused(){var t;return typeof l(this,Ne)=="boolean"?l(this,Ne):((t=globalThis.document)==null?void 0:t.visibilityState)!=="hidden"}},Ne=new WeakMap,we=new WeakMap,ze=new WeakMap,Ta),Ka=new ei,ti={setTimeout:(e,t)=>setTimeout(e,t),clearTimeout:e=>clearTimeout(e),setInterval:(e,t)=>setInterval(e,t),clearInterval:e=>clearInterval(e)},ye,un,La,ni=(La=class{constructor(){P(this,ye,ti);P(this,un,!1)}setTimeoutProvider(e){x(this,ye,e)}setTimeout(e,t){return l(this,ye).setTimeout(e,t)}clearTimeout(e){l(this,ye).clearTimeout(e)}setInterval(e,t){return l(this,ye).setInterval(e,t)}clearInterval(e){l(this,ye).clearInterval(e)}},ye=new WeakMap,un=new WeakMap,La),zt=new ni;function ai(e){setTimeout(e,0)}var ri=typeof window>"u"||"Deno"in globalThis;function ne(){}function si(e,t){return typeof e=="function"?e(t):e}function ii(e){return typeof e=="number"&&e>=0&&e!==1/0}function oi(e,t){return Math.max(e+(t||0)-Date.now(),0)}function Gt(e,t){return typeof e=="function"?e(t):e}function ci(e,t){return typeof e=="function"?e(t):e}function Bn(e,t){const{type:n="all",exact:a,fetchStatus:r,predicate:s,queryKey:i,stale:o}=e;if(i){if(a){if(t.queryHash!==hn(i,t.options))return!1}else if(!ut(t.queryKey,i))return!1}if(n!=="all"){const d=t.isActive();if(n==="active"&&!d||n==="inactive"&&d)return!1}return!(typeof o=="boolean"&&t.isStale()!==o||r&&r!==t.state.fetchStatus||s&&!s(t))}function Qn(e,t){const{exact:n,status:a,predicate:r,mutationKey:s}=e;if(s){if(!t.options.mutationKey)return!1;if(n){if(lt(t.options.mutationKey)!==lt(s))return!1}else if(!ut(t.options.mutationKey,s))return!1}return!(a&&t.state.status!==a||r&&!r(t))}function hn(e,t){return((t==null?void 0:t.queryKeyHashFn)||lt)(e)}function lt(e){return JSON.stringify(e,(t,n)=>Jt(n)?Object.keys(n).sort().reduce((a,r)=>(a[r]=n[r],a),{}):n)}function ut(e,t){return e===t?!0:typeof e!=typeof t?!1:e&&t&&typeof e=="object"&&typeof t=="object"?Object.keys(t).every(n=>ut(e[n],t[n])):!1}var li=Object.prototype.hasOwnProperty;function qa(e,t,n=0){if(e===t)return e;if(n>500)return t;const a=zn(e)&&zn(t);if(!a&&!(Jt(e)&&Jt(t)))return t;const s=(a?e:Object.keys(e)).length,i=a?t:Object.keys(t),o=i.length,d=a?new Array(o):{};let f=0;for(let h=0;h{zt.setTimeout(t,e)})}function di(e,t,n){return typeof n.structuralSharing=="function"?n.structuralSharing(e,t):n.structuralSharing!==!1?qa(e,t):t}function hi(e,t,n=0){const a=[...e,t];return n&&a.length>n?a.slice(1):a}function fi(e,t,n=0){const a=[t,...e];return n&&a.length>n?a.slice(0,-1):a}var fn=Symbol();function Ha(e,t){return!e.queryFn&&(t!=null&&t.initialPromise)?()=>t.initialPromise:!e.queryFn||e.queryFn===fn?()=>Promise.reject(new Error(`Missing queryFn: '${e.queryHash}'`)):e.queryFn}function Ul(e,t){return typeof e=="function"?e(...t):!!e}function mi(e,t,n){let a=!1,r;return Object.defineProperty(e,"signal",{enumerable:!0,get:()=>(r??(r=t()),a||(a=!0,r.aborted?n():r.addEventListener("abort",n,{once:!0})),r)}),e}var Wa=(()=>{let e=()=>ri;return{isServer(){return e()},setIsServer(t){e=t}}})();function gi(){let e,t;const n=new Promise((r,s)=>{e=r,t=s});n.status="pending",n.catch(()=>{});function a(r){Object.assign(n,r),delete n.resolve,delete n.reject}return n.resolve=r=>{a({status:"fulfilled",value:r}),e(r)},n.reject=r=>{a({status:"rejected",reason:r}),t(r)},n}var pi=ai;function bi(){let e=[],t=0,n=o=>{o()},a=o=>{o()},r=pi;const s=o=>{t?e.push(o):r(()=>{n(o)})},i=()=>{const o=e;e=[],o.length&&r(()=>{a(()=>{o.forEach(d=>{n(d)})})})};return{batch:o=>{let d;t++;try{d=o()}finally{t--,t||i()}return d},batchCalls:o=>(...d)=>{s(()=>{o(...d)})},schedule:s,setNotifyFunction:o=>{n=o},setBatchNotifyFunction:o=>{a=o},setScheduler:o=>{r=o}}}var Q=bi(),Ge,_e,Je,Ra,vi=(Ra=class extends Et{constructor(){super();P(this,Ge,!0);P(this,_e);P(this,Je);x(this,Je,t=>{if(typeof window<"u"&&window.addEventListener){const n=()=>t(!0),a=()=>t(!1);return window.addEventListener("online",n,!1),window.addEventListener("offline",a,!1),()=>{window.removeEventListener("online",n),window.removeEventListener("offline",a)}}})}onSubscribe(){l(this,_e)||this.setEventListener(l(this,Je))}onUnsubscribe(){var t;this.hasListeners()||((t=l(this,_e))==null||t.call(this),x(this,_e,void 0))}setEventListener(t){var n;x(this,Je,t),(n=l(this,_e))==null||n.call(this),x(this,_e,t(this.setOnline.bind(this)))}setOnline(t){l(this,Ge)!==t&&(x(this,Ge,t),this.listeners.forEach(a=>{a(t)}))}isOnline(){return l(this,Ge)}},Ge=new WeakMap,_e=new WeakMap,Je=new WeakMap,Ra),Tt=new vi;function wi(e){return Math.min(1e3*2**e,3e4)}function Ba(e){return(e??"online")==="online"?Tt.isOnline():!0}var Lt=class extends Error{constructor(e){super("CancelledError"),this.revert=e==null?void 0:e.revert,this.silent=e==null?void 0:e.silent}};function yi(e){return e instanceof Lt}function Qa(e){let t=!1,n=0,a;const r=gi(),s=()=>r.status!=="pending",i=b=>{var w;if(!s()){const T=new Lt(b);u(T),(w=e.onCancel)==null||w.call(e,T)}},o=()=>{t=!0},d=()=>{t=!1},f=()=>Ka.isFocused()&&(e.networkMode==="always"||Tt.isOnline())&&e.canRun(),h=()=>Ba(e.networkMode)&&e.canRun(),v=b=>{s()||(a==null||a(),r.resolve(b))},u=b=>{s()||(a==null||a(),r.reject(b))},p=()=>new Promise(b=>{var w;a=T=>{(s()||f())&&b(T)},(w=e.onPause)==null||w.call(e)}).then(()=>{var b;a=void 0,s()||(b=e.onContinue)==null||b.call(e)}),g=()=>{if(s())return;let b;const w=n===0?e.initialPromise:void 0;try{b=w??e.fn()}catch(T){b=Promise.reject(T)}Promise.resolve(b).then(v).catch(T=>{var y;if(s())return;const N=e.retry??(Wa.isServer()?0:3),R=e.retryDelay??wi,_=typeof R=="function"?R(n,T):R,L=N===!0||typeof N=="number"&&nf()?void 0:p()).then(()=>{t?u(T):g()})})};return{promise:r,status:()=>r.status,cancel:i,continue:()=>(a==null||a(),r),cancelRetry:o,continueRetry:d,canStart:h,start:()=>(h()?g():p().then(g),r)}}var Ee,Ma,za=(Ma=class{constructor(){P(this,Ee)}destroy(){this.clearGcTimeout()}scheduleGc(){this.clearGcTimeout(),ii(this.gcTime)&&x(this,Ee,zt.setTimeout(()=>{this.optionalRemove()},this.gcTime))}updateGcTime(e){this.gcTime=Math.max(this.gcTime||0,e??(Wa.isServer()?1/0:300*1e3))}clearGcTimeout(){l(this,Ee)!==void 0&&(zt.clearTimeout(l(this,Ee)),x(this,Ee,void 0))}},Ee=new WeakMap,Ma);function _i(e){return{onFetch:(t,n)=>{var h,v,u,p,g;const a=t.options,r=(u=(v=(h=t.fetchOptions)==null?void 0:h.meta)==null?void 0:v.fetchMore)==null?void 0:u.direction,s=((p=t.state.data)==null?void 0:p.pages)||[],i=((g=t.state.data)==null?void 0:g.pageParams)||[];let o={pages:[],pageParams:[]},d=0;const f=async()=>{let b=!1;const w=R=>{mi(R,()=>t.signal,()=>b=!0)},T=Ha(t.options,t.fetchOptions),N=async(R,_,L)=>{if(b)return Promise.reject(t.signal.reason);if(_==null&&R.pages.length)return Promise.resolve(R);const k=(()=>{const me={client:t.client,queryKey:t.queryKey,pageParam:_,direction:L?"backward":"forward",meta:t.options.meta};return w(me),me})(),j=await T(k),{maxPages:E}=t.options,F=L?fi:hi;return{pages:F(R.pages,j,E),pageParams:F(R.pageParams,_,E)}};if(r&&s.length){const R=r==="backward",_=R?Ga:Zt,L={pages:s,pageParams:i},y=_(a,L);o=await N(L,y,R)}else{const R=e??s.length;do{const _=d===0?i[0]??a.initialPageParam:Zt(a,o);if(d>0&&_==null)break;o=await N(o,_),d++}while(d{var b,w;return(w=(b=t.options).persister)==null?void 0:w.call(b,f,{client:t.client,queryKey:t.queryKey,meta:t.options.meta,signal:t.signal},n)}:t.fetchFn=f}}}function Zt(e,{pages:t,pageParams:n}){const a=t.length-1;return t.length>0?e.getNextPageParam(t[a],t,n[a],n):void 0}function Ga(e,{pages:t,pageParams:n}){var a;return t.length>0?(a=e.getPreviousPageParam)==null?void 0:a.call(e,t[0],t,n[0],n):void 0}function Vl(e,t){return t?Zt(e,t)!=null:!1}function Kl(e,t){return!t||!e.getPreviousPageParam?!1:Ga(e,t)!=null}var Ze,Fe,Xe,X,De,U,ft,$e,Z,Ja,he,Pa,Si=(Pa=class extends za{constructor(t){super();P(this,Z);P(this,Ze);P(this,Fe);P(this,Xe);P(this,X);P(this,De);P(this,U);P(this,ft);P(this,$e);x(this,$e,!1),x(this,ft,t.defaultOptions),this.setOptions(t.options),this.observers=[],x(this,De,t.client),x(this,X,l(this,De).getQueryCache()),this.queryKey=t.queryKey,this.queryHash=t.queryHash,x(this,Fe,Zn(this.options)),this.state=t.state??l(this,Fe),this.scheduleGc()}get meta(){return this.options.meta}get queryType(){return l(this,Ze)}get promise(){var t;return(t=l(this,U))==null?void 0:t.promise}setOptions(t){if(this.options={...l(this,ft),...t},t!=null&&t._type&&x(this,Ze,t._type),this.updateGcTime(this.options.gcTime),this.state&&this.state.data===void 0){const n=Zn(this.options);n.data!==void 0&&(this.setState(Jn(n.data,n.dataUpdatedAt)),x(this,Fe,n))}}optionalRemove(){!this.observers.length&&this.state.fetchStatus==="idle"&&l(this,X).remove(this)}setData(t,n){const a=di(this.state.data,t,this.options);return V(this,Z,he).call(this,{data:a,type:"success",dataUpdatedAt:n==null?void 0:n.updatedAt,manual:n==null?void 0:n.manual}),a}setState(t){V(this,Z,he).call(this,{type:"setState",state:t})}cancel(t){var a,r;const n=(a=l(this,U))==null?void 0:a.promise;return(r=l(this,U))==null||r.cancel(t),n?n.then(ne).catch(ne):Promise.resolve()}destroy(){super.destroy(),this.cancel({silent:!0})}get resetState(){return l(this,Fe)}reset(){this.destroy(),this.setState(this.resetState)}isActive(){return this.observers.some(t=>ci(t.options.enabled,this)!==!1)}isDisabled(){return this.getObserversCount()>0?!this.isActive():this.options.queryFn===fn||!this.isFetched()}isFetched(){return this.state.dataUpdateCount+this.state.errorUpdateCount>0}isStatic(){return this.getObserversCount()>0?this.observers.some(t=>Gt(t.options.staleTime,this)==="static"):!1}isStale(){return this.getObserversCount()>0?this.observers.some(t=>t.getCurrentResult().isStale):this.state.data===void 0||this.state.isInvalidated}isStaleByTime(t=0){return this.state.data===void 0?!0:t==="static"?!1:this.state.isInvalidated?!0:!oi(this.state.dataUpdatedAt,t)}onFocus(){var n;const t=this.observers.find(a=>a.shouldFetchOnWindowFocus());t==null||t.refetch({cancelRefetch:!1}),(n=l(this,U))==null||n.continue()}onOnline(){var n;const t=this.observers.find(a=>a.shouldFetchOnReconnect());t==null||t.refetch({cancelRefetch:!1}),(n=l(this,U))==null||n.continue()}addObserver(t){this.observers.includes(t)||(this.observers.push(t),this.clearGcTimeout(),l(this,X).notify({type:"observerAdded",query:this,observer:t}))}removeObserver(t){this.observers.includes(t)&&(this.observers=this.observers.filter(n=>n!==t),this.observers.length||(l(this,U)&&(l(this,$e)||V(this,Z,Ja).call(this)?l(this,U).cancel({revert:!0}):l(this,U).cancelRetry()),this.scheduleGc()),l(this,X).notify({type:"observerRemoved",query:this,observer:t}))}getObserversCount(){return this.observers.length}invalidate(){this.state.isInvalidated||V(this,Z,he).call(this,{type:"invalidate"})}async fetch(t,n){var f,h,v,u,p,g,b,w,T,N,R;if(this.state.fetchStatus!=="idle"&&((f=l(this,U))==null?void 0:f.status())!=="rejected"){if(this.state.data!==void 0&&(n!=null&&n.cancelRefetch))this.cancel({silent:!0});else if(l(this,U))return l(this,U).continueRetry(),l(this,U).promise}if(t&&this.setOptions(t),!this.options.queryFn){const _=this.observers.find(L=>L.options.queryFn);_&&this.setOptions(_.options)}const a=new AbortController,r=_=>{Object.defineProperty(_,"signal",{enumerable:!0,get:()=>(x(this,$e,!0),a.signal)})},s=()=>{const _=Ha(this.options,n),y=(()=>{const k={client:l(this,De),queryKey:this.queryKey,meta:this.meta};return r(k),k})();return x(this,$e,!1),this.options.persister?this.options.persister(_,y,this):_(y)},o=(()=>{const _={fetchOptions:n,options:this.options,queryKey:this.queryKey,client:l(this,De),state:this.state,fetchFn:s};return r(_),_})(),d=l(this,Ze)==="infinite"?_i(this.options.pages):this.options.behavior;d==null||d.onFetch(o,this),x(this,Xe,this.state),(this.state.fetchStatus==="idle"||this.state.fetchMeta!==((h=o.fetchOptions)==null?void 0:h.meta))&&V(this,Z,he).call(this,{type:"fetch",meta:(v=o.fetchOptions)==null?void 0:v.meta}),x(this,U,Qa({initialPromise:n==null?void 0:n.initialPromise,fn:o.fetchFn,onCancel:_=>{_ instanceof Lt&&_.revert&&this.setState({...l(this,Xe),fetchStatus:"idle"}),a.abort()},onFail:(_,L)=>{V(this,Z,he).call(this,{type:"failed",failureCount:_,error:L})},onPause:()=>{V(this,Z,he).call(this,{type:"pause"})},onContinue:()=>{V(this,Z,he).call(this,{type:"continue"})},retry:o.options.retry,retryDelay:o.options.retryDelay,networkMode:o.options.networkMode,canRun:()=>!0}));try{const _=await l(this,U).start();if(_===void 0)throw new Error(`${this.queryHash} data is undefined`);return this.setData(_),(p=(u=l(this,X).config).onSuccess)==null||p.call(u,_,this),(b=(g=l(this,X).config).onSettled)==null||b.call(g,_,this.state.error,this),_}catch(_){if(_ instanceof Lt){if(_.silent)return l(this,U).promise;if(_.revert){if(this.state.data===void 0)throw _;return this.state.data}}throw V(this,Z,he).call(this,{type:"error",error:_}),(T=(w=l(this,X).config).onError)==null||T.call(w,_,this),(R=(N=l(this,X).config).onSettled)==null||R.call(N,this.state.data,_,this),_}finally{this.scheduleGc()}}},Ze=new WeakMap,Fe=new WeakMap,Xe=new WeakMap,X=new WeakMap,De=new WeakMap,U=new WeakMap,ft=new WeakMap,$e=new WeakMap,Z=new WeakSet,Ja=function(){return this.state.fetchStatus==="paused"&&this.state.status==="pending"},he=function(t){const n=a=>{switch(t.type){case"failed":return{...a,fetchFailureCount:t.failureCount,fetchFailureReason:t.error};case"pause":return{...a,fetchStatus:"paused"};case"continue":return{...a,fetchStatus:"fetching"};case"fetch":return{...a,...Ci(a.data,this.options),fetchMeta:t.meta??null};case"success":const r={...a,...Jn(t.data,t.dataUpdatedAt),dataUpdateCount:a.dataUpdateCount+1,...!t.manual&&{fetchStatus:"idle",fetchFailureCount:0,fetchFailureReason:null}};return x(this,Xe,t.manual?r:void 0),r;case"error":const s=t.error;return{...a,error:s,errorUpdateCount:a.errorUpdateCount+1,errorUpdatedAt:Date.now(),fetchFailureCount:a.fetchFailureCount+1,fetchFailureReason:s,fetchStatus:"idle",status:"error",isInvalidated:!0};case"invalidate":return{...a,isInvalidated:!0};case"setState":return{...a,...t.state}}};this.state=n(this.state),Q.batch(()=>{this.observers.forEach(a=>{a.onQueryUpdate()}),l(this,X).notify({query:this,type:"updated",action:t})})},Pa);function Ci(e,t){return{fetchFailureCount:0,fetchFailureReason:null,fetchStatus:Ba(t.networkMode)?"fetching":"paused",...e===void 0&&{error:null,status:"pending"}}}function Jn(e,t){return{data:e,dataUpdatedAt:t??Date.now(),error:null,isInvalidated:!1,status:"success"}}function Zn(e){const t=typeof e.initialData=="function"?e.initialData():e.initialData,n=t!==void 0,a=n?typeof e.initialDataUpdatedAt=="function"?e.initialDataUpdatedAt():e.initialDataUpdatedAt:0;return{data:t,dataUpdateCount:0,dataUpdatedAt:n?a??Date.now():0,error:null,errorUpdateCount:0,errorUpdatedAt:0,fetchFailureCount:0,fetchFailureReason:null,fetchMeta:null,isInvalidated:!1,status:n?"success":"pending",fetchStatus:"idle"}}var mt,le,K,Ie,ue,be,ja,xi=(ja=class extends za{constructor(t){super();P(this,ue);P(this,mt);P(this,le);P(this,K);P(this,Ie);x(this,mt,t.client),this.mutationId=t.mutationId,x(this,K,t.mutationCache),x(this,le,[]),this.state=t.state||ki(),this.setOptions(t.options),this.scheduleGc()}setOptions(t){this.options=t,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(t){l(this,le).includes(t)||(l(this,le).push(t),this.clearGcTimeout(),l(this,K).notify({type:"observerAdded",mutation:this,observer:t}))}removeObserver(t){x(this,le,l(this,le).filter(n=>n!==t)),this.scheduleGc(),l(this,K).notify({type:"observerRemoved",mutation:this,observer:t})}optionalRemove(){l(this,le).length||(this.state.status==="pending"?this.scheduleGc():l(this,K).remove(this))}continue(){var t;return((t=l(this,Ie))==null?void 0:t.continue())??this.execute(this.state.variables)}async execute(t){var i,o,d,f,h,v,u,p,g,b,w,T,N,R,_,L,y,k;const n=()=>{V(this,ue,be).call(this,{type:"continue"})},a={client:l(this,mt),meta:this.options.meta,mutationKey:this.options.mutationKey};x(this,Ie,Qa({fn:()=>this.options.mutationFn?this.options.mutationFn(t,a):Promise.reject(new Error("No mutationFn found")),onFail:(j,E)=>{V(this,ue,be).call(this,{type:"failed",failureCount:j,error:E})},onPause:()=>{V(this,ue,be).call(this,{type:"pause"})},onContinue:n,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>l(this,K).canRun(this)}));const r=this.state.status==="pending",s=!l(this,Ie).canStart();try{if(r)n();else{V(this,ue,be).call(this,{type:"pending",variables:t,isPaused:s}),l(this,K).config.onMutate&&await l(this,K).config.onMutate(t,this,a);const E=await((o=(i=this.options).onMutate)==null?void 0:o.call(i,t,a));E!==this.state.context&&V(this,ue,be).call(this,{type:"pending",context:E,variables:t,isPaused:s})}const j=await l(this,Ie).start();return await((f=(d=l(this,K).config).onSuccess)==null?void 0:f.call(d,j,t,this.state.context,this,a)),await((v=(h=this.options).onSuccess)==null?void 0:v.call(h,j,t,this.state.context,a)),await((p=(u=l(this,K).config).onSettled)==null?void 0:p.call(u,j,null,this.state.variables,this.state.context,this,a)),await((b=(g=this.options).onSettled)==null?void 0:b.call(g,j,null,t,this.state.context,a)),V(this,ue,be).call(this,{type:"success",data:j}),j}catch(j){try{await((T=(w=l(this,K).config).onError)==null?void 0:T.call(w,j,t,this.state.context,this,a))}catch(E){Promise.reject(E)}try{await((R=(N=this.options).onError)==null?void 0:R.call(N,j,t,this.state.context,a))}catch(E){Promise.reject(E)}try{await((L=(_=l(this,K).config).onSettled)==null?void 0:L.call(_,void 0,j,this.state.variables,this.state.context,this,a))}catch(E){Promise.reject(E)}try{await((k=(y=this.options).onSettled)==null?void 0:k.call(y,void 0,j,t,this.state.context,a))}catch(E){Promise.reject(E)}throw V(this,ue,be).call(this,{type:"error",error:j}),j}finally{l(this,K).runNext(this)}}},mt=new WeakMap,le=new WeakMap,K=new WeakMap,Ie=new WeakMap,ue=new WeakSet,be=function(t){const n=a=>{switch(t.type){case"failed":return{...a,failureCount:t.failureCount,failureReason:t.error};case"pause":return{...a,isPaused:!0};case"continue":return{...a,isPaused:!1};case"pending":return{...a,context:t.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:t.isPaused,status:"pending",variables:t.variables,submittedAt:Date.now()};case"success":return{...a,data:t.data,failureCount:0,failureReason:null,error:null,status:"success",isPaused:!1};case"error":return{...a,data:void 0,error:t.error,failureCount:a.failureCount+1,failureReason:t.error,isPaused:!1,status:"error"}}};this.state=n(this.state),Q.batch(()=>{l(this,le).forEach(a=>{a.onMutationUpdate(t)}),l(this,K).notify({mutation:this,type:"updated",action:t})})},ja);function ki(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:"idle",variables:void 0,submittedAt:0}}var fe,ae,gt,Aa,Ti=(Aa=class extends Et{constructor(t={}){super();P(this,fe);P(this,ae);P(this,gt);this.config=t,x(this,fe,new Set),x(this,ae,new Map),x(this,gt,0)}build(t,n,a){const r=new xi({client:t,mutationCache:this,mutationId:++Ct(this,gt)._,options:t.defaultMutationOptions(n),state:a});return this.add(r),r}add(t){l(this,fe).add(t);const n=xt(t);if(typeof n=="string"){const a=l(this,ae).get(n);a?a.push(t):l(this,ae).set(n,[t])}this.notify({type:"added",mutation:t})}remove(t){if(l(this,fe).delete(t)){const n=xt(t);if(typeof n=="string"){const a=l(this,ae).get(n);if(a)if(a.length>1){const r=a.indexOf(t);r!==-1&&a.splice(r,1)}else a[0]===t&&l(this,ae).delete(n)}}this.notify({type:"removed",mutation:t})}canRun(t){const n=xt(t);if(typeof n=="string"){const a=l(this,ae).get(n),r=a==null?void 0:a.find(s=>s.state.status==="pending");return!r||r===t}else return!0}runNext(t){var a;const n=xt(t);if(typeof n=="string"){const r=(a=l(this,ae).get(n))==null?void 0:a.find(s=>s!==t&&s.state.isPaused);return(r==null?void 0:r.continue())??Promise.resolve()}else return Promise.resolve()}clear(){Q.batch(()=>{l(this,fe).forEach(t=>{this.notify({type:"removed",mutation:t})}),l(this,fe).clear(),l(this,ae).clear()})}getAll(){return Array.from(l(this,fe))}find(t){const n={exact:!0,...t};return this.getAll().find(a=>Qn(n,a))}findAll(t={}){return this.getAll().filter(n=>Qn(t,n))}notify(t){Q.batch(()=>{this.listeners.forEach(n=>{n(t)})})}resumePausedMutations(){const t=this.getAll().filter(n=>n.state.isPaused);return Q.batch(()=>Promise.all(t.map(n=>n.continue().catch(ne))))}},fe=new WeakMap,ae=new WeakMap,gt=new WeakMap,Aa);function xt(e){var t;return(t=e.options.scope)==null?void 0:t.id}var de,Na,Li=(Na=class extends Et{constructor(t={}){super();P(this,de);this.config=t,x(this,de,new Map)}build(t,n,a){const r=n.queryKey,s=n.queryHash??hn(r,n);let i=this.get(s);return i||(i=new Si({client:t,queryKey:r,queryHash:s,options:t.defaultQueryOptions(n),state:a,defaultOptions:t.getQueryDefaults(r)}),this.add(i)),i}add(t){l(this,de).has(t.queryHash)||(l(this,de).set(t.queryHash,t),this.notify({type:"added",query:t}))}remove(t){const n=l(this,de).get(t.queryHash);n&&(t.destroy(),n===t&&l(this,de).delete(t.queryHash),this.notify({type:"removed",query:t}))}clear(){Q.batch(()=>{this.getAll().forEach(t=>{this.remove(t)})})}get(t){return l(this,de).get(t)}getAll(){return[...l(this,de).values()]}find(t){const n={exact:!0,...t};return this.getAll().find(a=>Bn(n,a))}findAll(t={}){const n=this.getAll();return Object.keys(t).length>0?n.filter(a=>Bn(t,a)):n}notify(t){Q.batch(()=>{this.listeners.forEach(n=>{n(t)})})}onFocus(){Q.batch(()=>{this.getAll().forEach(t=>{t.onFocus()})})}onOnline(){Q.batch(()=>{this.getAll().forEach(t=>{t.onOnline()})})}},de=new WeakMap,Na),D,Se,Ce,Ye,et,xe,tt,nt,Ea,Ri=(Ea=class{constructor(e={}){P(this,D);P(this,Se);P(this,Ce);P(this,Ye);P(this,et);P(this,xe);P(this,tt);P(this,nt);x(this,D,e.queryCache||new Li),x(this,Se,e.mutationCache||new Ti),x(this,Ce,e.defaultOptions||{}),x(this,Ye,new Map),x(this,et,new Map),x(this,xe,0)}mount(){Ct(this,xe)._++,l(this,xe)===1&&(x(this,tt,Ka.subscribe(async e=>{e&&(await this.resumePausedMutations(),l(this,D).onFocus())})),x(this,nt,Tt.subscribe(async e=>{e&&(await this.resumePausedMutations(),l(this,D).onOnline())})))}unmount(){var e,t;Ct(this,xe)._--,l(this,xe)===0&&((e=l(this,tt))==null||e.call(this),x(this,tt,void 0),(t=l(this,nt))==null||t.call(this),x(this,nt,void 0))}isFetching(e){return l(this,D).findAll({...e,fetchStatus:"fetching"}).length}isMutating(e){return l(this,Se).findAll({...e,status:"pending"}).length}getQueryData(e){var n;const t=this.defaultQueryOptions({queryKey:e});return(n=l(this,D).get(t.queryHash))==null?void 0:n.state.data}ensureQueryData(e){const t=this.defaultQueryOptions(e),n=l(this,D).build(this,t),a=n.state.data;return a===void 0?this.fetchQuery(e):(e.revalidateIfStale&&n.isStaleByTime(Gt(t.staleTime,n))&&this.prefetchQuery(t),Promise.resolve(a))}getQueriesData(e){return l(this,D).findAll(e).map(({queryKey:t,state:n})=>{const a=n.data;return[t,a]})}setQueryData(e,t,n){const a=this.defaultQueryOptions({queryKey:e}),r=l(this,D).get(a.queryHash),s=r==null?void 0:r.state.data,i=si(t,s);if(i!==void 0)return l(this,D).build(this,a).setData(i,{...n,manual:!0})}setQueriesData(e,t,n){return Q.batch(()=>l(this,D).findAll(e).map(({queryKey:a})=>[a,this.setQueryData(a,t,n)]))}getQueryState(e){var n;const t=this.defaultQueryOptions({queryKey:e});return(n=l(this,D).get(t.queryHash))==null?void 0:n.state}removeQueries(e){const t=l(this,D);Q.batch(()=>{t.findAll(e).forEach(n=>{t.remove(n)})})}resetQueries(e,t){const n=l(this,D);return Q.batch(()=>(n.findAll(e).forEach(a=>{a.reset()}),this.refetchQueries({type:"active",...e},t)))}cancelQueries(e,t={}){const n={revert:!0,...t},a=Q.batch(()=>l(this,D).findAll(e).map(r=>r.cancel(n)));return Promise.all(a).then(ne).catch(ne)}invalidateQueries(e,t={}){return Q.batch(()=>(l(this,D).findAll(e).forEach(n=>{n.invalidate()}),(e==null?void 0:e.refetchType)==="none"?Promise.resolve():this.refetchQueries({...e,type:(e==null?void 0:e.refetchType)??(e==null?void 0:e.type)??"active"},t)))}refetchQueries(e,t={}){const n={...t,cancelRefetch:t.cancelRefetch??!0},a=Q.batch(()=>l(this,D).findAll(e).filter(r=>!r.isDisabled()&&!r.isStatic()).map(r=>{let s=r.fetch(void 0,n);return n.throwOnError||(s=s.catch(ne)),r.state.fetchStatus==="paused"?Promise.resolve():s}));return Promise.all(a).then(ne)}fetchQuery(e){const t=this.defaultQueryOptions(e);t.retry===void 0&&(t.retry=!1);const n=l(this,D).build(this,t);return n.isStaleByTime(Gt(t.staleTime,n))?n.fetch(t):Promise.resolve(n.state.data)}prefetchQuery(e){return this.fetchQuery(e).then(ne).catch(ne)}fetchInfiniteQuery(e){return e._type="infinite",this.fetchQuery(e)}prefetchInfiniteQuery(e){return this.fetchInfiniteQuery(e).then(ne).catch(ne)}ensureInfiniteQueryData(e){return e._type="infinite",this.ensureQueryData(e)}resumePausedMutations(){return Tt.isOnline()?l(this,Se).resumePausedMutations():Promise.resolve()}getQueryCache(){return l(this,D)}getMutationCache(){return l(this,Se)}getDefaultOptions(){return l(this,Ce)}setDefaultOptions(e){x(this,Ce,e)}setQueryDefaults(e,t){l(this,Ye).set(lt(e),{queryKey:e,defaultOptions:t})}getQueryDefaults(e){const t=[...l(this,Ye).values()],n={};return t.forEach(a=>{ut(e,a.queryKey)&&Object.assign(n,a.defaultOptions)}),n}setMutationDefaults(e,t){l(this,et).set(lt(e),{mutationKey:e,defaultOptions:t})}getMutationDefaults(e){const t=[...l(this,et).values()],n={};return t.forEach(a=>{ut(e,a.mutationKey)&&Object.assign(n,a.defaultOptions)}),n}defaultQueryOptions(e){if(e._defaulted)return e;const t={...l(this,Ce).queries,...this.getQueryDefaults(e.queryKey),...e,_defaulted:!0};return t.queryHash||(t.queryHash=hn(t.queryKey,t)),t.refetchOnReconnect===void 0&&(t.refetchOnReconnect=t.networkMode!=="always"),t.throwOnError===void 0&&(t.throwOnError=!!t.suspense),!t.networkMode&&t.persister&&(t.networkMode="offlineFirst"),t.queryFn===fn&&(t.enabled=!1),t}defaultMutationOptions(e){return e!=null&&e._defaulted?e:{...l(this,Ce).mutations,...(e==null?void 0:e.mutationKey)&&this.getMutationDefaults(e.mutationKey),...e,_defaulted:!0}}clear(){l(this,D).clear(),l(this,Se).clear()}},D=new WeakMap,Se=new WeakMap,Ce=new WeakMap,Ye=new WeakMap,et=new WeakMap,xe=new WeakMap,tt=new WeakMap,nt=new WeakMap,Ea),Za=C.createContext(void 0),ql=e=>{const t=C.useContext(Za);if(!t)throw new Error("No QueryClient set, use QueryClientProvider to set one");return t},Mi=({client:e,children:t})=>(C.useEffect(()=>(e.mount(),()=>{e.unmount()}),[e]),c.jsx(Za.Provider,{value:e,children:t}));function Pi(e,t=window.location.href){var a;const n=(a=new URL(t).searchParams.get("record"))==null?void 0:a.trim();if(n){const r=e.calls.find(s=>s.id===n);return r?[r]:[]}return e.calls[0]?[e.calls[0]]:[]}function Hl({calls:e,recordId:t,detail:n}){const a=e.findIndex(v=>v.id===t),r=a>=0?a:!t&&e.length?0:-1,s=(n==null?void 0:n.record.id)===t?n:null,i=(s==null?void 0:s.record)??(r>=0?e[r]:null),o=(s==null?void 0:s.previousRecord)??(r>0?e[r-1]:null),d=(s==null?void 0:s.nextRecord)??(r>=0&&r=0?`${r+1} of ${e.length} loaded calls`:"Record outside loaded snapshot";return{modelIndex:a,activeIndex:r,hydratedDetail:s,call:i,previous:o,next:d,threadCalls:f,positionLabel:h}}function ji(e,t,n){const a=e.filter(i=>i.thread===t.thread),r=[n==null?void 0:n.previousRecord,n==null?void 0:n.record,n==null?void 0:n.nextRecord].filter(Ai),s=new Map;for(const i of[...a,...r,t])s.set(i.id,i);return[...s.values()].sort(Ni)}function Ai(e){return!!(e!=null&&e.id)}function Ni(e,t){return Date.parse(t.rawTime||t.time)-Date.parse(e.rawTime||e.time)}function Ei(e,t){const n=t.map(r=>Xn(r.header)).join(","),a=e.map(r=>t.map(s=>Xn(s.value(r))).join(","));return[n,...a].join(` +`)}function Fi(e,t){const n=new Blob([t],{type:"text/csv;charset=utf-8"}),a=typeof URL.createObjectURL=="function"?URL.createObjectURL(n):`data:text/csv;charset=utf-8,${encodeURIComponent(t)}`,r=document.createElement("a");r.href=a,r.download=e,r.rel="noopener",document.body.append(r),r.click(),r.remove(),a.startsWith("blob:")&&typeof URL.revokeObjectURL=="function"&&URL.revokeObjectURL(a)}function Di(e=new Date){return e.toISOString().slice(0,10)}function Xn(e){const t=String(e??"");return/[",\n\r]/.test(t)?`"${t.replaceAll('"','""')}"`:t}function Le(e){return new Intl.NumberFormat("en-US").format(Math.round(e))}function ve(e){return new Intl.NumberFormat("en-US",{notation:"compact",maximumFractionDigits:2}).format(e)}function Xt(e){return new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",minimumFractionDigits:2,maximumFractionDigits:2}).format(e)}function dt(e){return`${e.toFixed(1)}%`}const $i={priority:"Priority / Fast",fast:"Fast",default:"Default / Standard",standard:"Standard",flex:"Flex",batch:"Batch"};function Xa(e){const t=e.trim().toLowerCase().replace(/[\s_]+/g,"-");if(!t)return null;const n=$i[t];return n||(t.length>48||!/^[a-z0-9.-]+$/.test(t)?"Other":t.split(/[-.]+/).filter(Boolean).map(a=>`${a.charAt(0).toUpperCase()}${a.slice(1)}`).join(" "))}function Yt(e){const t=Xa(e.serviceTier);return t||(e.fast===!0?"Fast":e.fast===!1?"Standard":"Unknown")}function Wl(e){return Xa(e.serviceTier)?`observed ${Yt(e)} · ${e.serviceTierConfidence||"exact"}`:e.fast!==null?`confirmed ${Yt(e)} · ${e.serviceTierConfidence||"exact"}`:e.fastProxyCandidate?"tier unknown · Fast proxy candidate":"tier unknown · normal throughput proxy"}function Bl(e){if(e.billingBasis==="api_tokens"){const t=e.pricingServiceTier.trim();return`API token estimate · ${t?`${t.charAt(0).toUpperCase()}${t.slice(1).toLowerCase()} rates`:"configured rates"}`}return e.billingBasis==="chatgpt_credits"?"API-equivalent scenario · ChatGPT credits selected":"API-equivalent scenario · billing basis unknown"}function Ql(e){return e.cachedPct<25?"cold or weak cache":e.cachedPct<50?"partial cache reuse":"healthy cache reuse"}function zl(e){return e.sourceFile?`${e.sourceFile}${e.lineNumber?`:${e.lineNumber}`:""}`:"Not available"}function Gl(e){if(e.contextWindowPct===null||e.contextWindowPct===void 0)return"Not reported";const t=e.modelContextWindow?` of ${ve(e.modelContextWindow)}`:"";return`${dt(e.contextWindowPct)}${t}`}function Jl(e,{limit:t=2,emptyLabel:n="No related calls",unknownLabel:a="Unknown",style:r="parenthetical"}={}){const s=new Map;for(const o of e){const d=o||a;s.set(d,(s.get(d)??0)+1)}const i=[...s.entries()].sort((o,d)=>d[1]-o[1]||o[0].localeCompare(d[0])).slice(0,t).map(([o,d])=>r==="x"?`${o} x${d}`:`${o} (${d})`);return i.length?i.join(", "):n}const Zl=[{id:"time",accessorFn:e=>Number(Date.parse(e.eventTimestamp||e.callStartedAt||e.rawTime||e.time))||0,header:"Time",cell:e=>e.row.original.time},{accessorKey:"thread",header:"Thread"},{accessorKey:"model",header:"Model"},{accessorKey:"effort",header:"Effort",cell:e=>c.jsx("span",{className:`pill effort-${String(e.getValue())}`,children:String(e.getValue())})},{accessorKey:"input",header:"Input Tokens",cell:e=>c.jsx("span",{className:"num",children:Le(Number(e.getValue()))})},{accessorKey:"totalTokens",header:"Total Tokens",cell:e=>c.jsx("span",{className:"num",children:Le(Number(e.getValue()))})},{accessorKey:"cachedInput",header:"Cached Input",cell:e=>c.jsx("span",{className:"num",children:Le(Number(e.getValue()))})},{accessorKey:"uncachedInput",header:"Uncached Input",cell:e=>c.jsx("span",{className:"num",children:Le(Number(e.getValue()))})},{accessorKey:"output",header:"Output Tokens",cell:e=>c.jsx("span",{className:"num",children:Le(Number(e.getValue()))})},{accessorKey:"reasoningOutput",header:"Reasoning Output",cell:e=>c.jsx("span",{className:"num",children:Le(Number(e.getValue()))})},{accessorKey:"cachedPct",header:"Cached %",cell:e=>c.jsx("span",{className:"cache-pill",children:dt(Number(e.getValue()))})},{accessorKey:"cost",header:"Est. Cost",cell:e=>c.jsx("span",{className:"num",children:Xt(Number(e.getValue()))})},{accessorKey:"credits",header:"Codex Credits",cell:e=>c.jsx("span",{className:"num",children:ve(Number(e.getValue()))})},{accessorKey:"contextWindowPct",header:"Context %",cell:e=>{const t=e.getValue(),n=typeof t=="number"?t:Number.NaN;return c.jsx("span",{className:"num",children:Number.isFinite(n)?dt(n):"-"})}},{accessorKey:"duration",header:"Duration"},{id:"serviceTier",accessorFn:e=>Yt(e),header:"Service Tier",cell:e=>{const t=String(e.getValue()),n=t.includes("Fast")?"green":t.includes("Standard")?"blue":"";return c.jsx("span",{className:`status-badge ${n}`.trim(),children:t})}},{accessorKey:"previousCallGap",header:"Prev Gap",cell:e=>c.jsx("span",{className:"num",children:String(e.getValue())})},{accessorKey:"initiator",header:"Initiated",cell:e=>c.jsx("span",{className:"status-badge blue",children:String(e.getValue())})},{accessorKey:"signal",header:"Signals",cell:e=>c.jsx(Oi,{call:e.row.original})}],ce=[{header:"timestamp",value:e=>e.eventTimestamp||e.rawTime||e.time},{header:"thread",value:e=>e.thread},{header:"call_started_at",value:e=>e.callStartedAt||e.rawTime||e.time},{header:"call_duration_seconds",value:e=>e.durationSeconds},{header:"previous_call_event_timestamp",value:e=>e.previousCallEventTimestamp},{header:"previous_call_delta_seconds",value:e=>e.previousCallGapSeconds},{header:"initiated",value:e=>e.initiator},{header:"initiated_reason",value:e=>e.initiatorReason},{header:"project",value:e=>e.project},{header:"model",value:e=>e.model},{header:"effort",value:e=>e.effort},{header:"total_tokens",value:e=>e.totalTokens},{header:"input_tokens",value:e=>e.input},{header:"cached_input_tokens",value:e=>e.cachedInput},{header:"uncached_input_tokens",value:e=>e.uncachedInput},{header:"output_tokens",value:e=>e.output},{header:"reasoning_output_tokens",value:e=>e.reasoningOutput},{header:"estimated_cost_usd",value:e=>e.cost.toFixed(6)},{header:"standard_cost_usd",value:e=>{var t;return((t=e.standardCost)==null?void 0:t.toFixed(6))??""}},{header:"priority_cost_usd",value:e=>{var t;return((t=e.priorityCost)==null?void 0:t.toFixed(6))??""}},{header:"pricing_service_tier",value:e=>e.pricingServiceTier},{header:"billing_basis",value:e=>e.billingBasis},{header:"cost_semantics",value:e=>e.costSemantics},{header:"usage_credits",value:e=>e.credits.toFixed(6)},{header:"standard_usage_credits",value:e=>e.standardUsageCredits.toFixed(6)},{header:"fast_usage_credits",value:e=>{var t;return((t=e.fastUsageCredits)==null?void 0:t.toFixed(6))??""}},{header:"service_tier",value:e=>e.serviceTier},{header:"fast",value:e=>e.fast===null?"":e.fast?1:0},{header:"service_tier_source",value:e=>e.serviceTierSource},{header:"service_tier_confidence",value:e=>e.serviceTierConfidence},{header:"fast_proxy_candidate",value:e=>String(e.fastProxyCandidate)},{header:"usage_credit_multiplier",value:e=>e.usageCreditMultiplier},{header:"usage_credit_multiplier_source",value:e=>e.usageCreditMultiplierSource},{header:"usage_credit_multiplier_source_url",value:e=>e.usageCreditMultiplierSourceUrl},{header:"usage_credit_multiplier_fetched_at",value:e=>e.usageCreditMultiplierFetchedAt},{header:"usage_credit_multiplier_confidence",value:e=>e.usageCreditMultiplierConfidence},{header:"cache_ratio",value:e=>e.cachedPct.toFixed(2)},{header:"context_window_percent",value:e=>{var t;return((t=e.contextWindowPct)==null?void 0:t.toFixed(2))??""}},{header:"pricing_model",value:e=>e.pricingModel},{header:"usage_credit_confidence",value:e=>e.usageCreditConfidence},{header:"recommendation",value:e=>e.recommendation},{header:"record_id",value:e=>e.id},{header:"thread_attachment",value:e=>e.threadAttachmentLabel},{header:"thread_source",value:e=>e.threadSource},{header:"parent_thread",value:e=>e.parentThread},{header:"session_id",value:e=>e.sessionId},{header:"turn_id",value:e=>e.turnId},{header:"parent_session_id",value:e=>e.parentSessionId},{header:"parent_session_updated_at",value:e=>e.parentSessionUpdatedAt},{header:"project_relative_cwd",value:e=>e.projectRelativeCwd},{header:"cwd",value:e=>e.cwd},{header:"source_file",value:e=>e.sourceFile},{header:"source_line",value:e=>e.lineNumber??""},{header:"git_branch",value:e=>e.gitBranch},{header:"git_remote_label",value:e=>e.gitRemoteLabel},{header:"git_remote_hash",value:e=>e.gitRemoteHash},{header:"pricing_estimated",value:e=>String(e.pricingEstimated)},{header:"usage_credit_model",value:e=>e.usageCreditModel},{header:"usage_credit_source",value:e=>e.usageCreditSource},{header:"usage_credit_tier",value:e=>e.usageCreditTier},{header:"usage_credit_fetched_at",value:e=>e.usageCreditFetchedAt},{header:"usage_credit_note",value:e=>e.usageCreditNote},{header:"model_context_window",value:e=>e.modelContextWindow??""},{header:"cumulative_total_tokens",value:e=>e.cumulativeTotalTokens??""},{header:"estimated_cache_savings_usd",value:e=>e.estimatedCacheSavings.toFixed(6)},{header:"initiated_confidence",value:e=>e.initiatorConfidence},{header:"signal",value:e=>e.signal},{header:"tags",value:e=>e.tags.join("|")},{header:"efficiency_flags",value:e=>e.efficiencyFlags.join("|")}];function Ii(e,t=3){const a=Ui([e.signal,...e.efficiencyFlags]).map((r,s)=>({key:`${r}-${s}`,label:Ya(r),shortLabel:Vi(r)}));return{visible:a.slice(0,t),hidden:a.slice(t)}}function Oi({call:e}){const t=Va(),{visible:n,hidden:a}=Ii(e);if(!n.length)return c.jsx("span",{className:"muted",children:"None"});const r=n.map(o=>({...o,label:t.translateText(o.label),shortLabel:t.translateText(o.shortLabel)})),s=a.map(o=>({...o,label:t.translateText(o.label),shortLabel:t.translateText(o.shortLabel)})),i=s.map(o=>o.label).join("、");return c.jsxs("span",{className:"flags compact-flags","aria-label":t.language==="zh-Hans"?`信号:${[...r,...s].map(o=>o.label).join("、")}`:`Signals: ${[...n,...a].map(o=>o.label).join(", ")}`,children:[r.map(o=>c.jsx("span",{className:"flag signal-puck",title:o.label,children:o.shortLabel},o.key)),s.length?c.jsxs("span",{className:"flag signal-puck more",title:i,children:["+",s.length]}):null]})}function Ui(e){const t=new Set;return e.map(n=>n.trim()).filter(n=>n&&n!=="aggregate").filter(n=>{const a=n.toLowerCase();return t.has(a)?!1:(t.add(a),!0)})}function Ya(e){return e.replace(/[-_]+/g," ").replace(/\b\w/g,t=>t.toUpperCase())}function Vi(e){const t=e.toLowerCase().replace(/[_\s]+/g,"-"),n={"cache-drop":"CACHE","cache-risk":"CACHE","context-bloat":"CTX","context-heavy":"CTX","elevated-context":"CTX","elevated-context-use":"CTX","estimated-pricing":"EST","expensive-low-output-call":"LO","high-context-use":"CTX","high-cost":"$","high-estimated-cost":"$","high-reasoning-share":"RSN","large-thread":"BIG","low-cache":"CACHE","low-cache-reuse":"CACHE","low-output":"LO","pricing-gap":"PRICE","reasoning-spike":"RSN","subagent-attribution":"SUB"};if(n[t])return n[t];const a=Ya(e).split(/\s+/).filter(Boolean);return a.length?a.length===1?a[0].slice(0,4).toUpperCase():a.slice(0,3).map(r=>r[0]).join("").toUpperCase():"?"}const Xl=[{accessorKey:"name",header:"Thread"},{accessorKey:"latestActivity",header:"Latest"},{accessorKey:"turns",header:"Turns",cell:e=>c.jsx("span",{className:"num",children:Le(Number(e.getValue()))})},{accessorKey:"totalDuration",header:"Duration"},{accessorKey:"averageGap",header:"Avg Gap",cell:e=>c.jsx("span",{className:"num",children:String(e.getValue())})},{accessorKey:"initiatorSummary",header:"Initiated",cell:e=>c.jsx("span",{className:"status-badge blue",children:String(e.getValue())})},{accessorKey:"modelSummary",header:"Models",cell:e=>c.jsx("span",{className:"pill model-pill",children:String(e.getValue())})},{accessorKey:"effortSummary",header:"Effort Mix"},{accessorKey:"totalTokens",header:"Total Tokens",cell:e=>c.jsx("span",{className:"num",children:ve(Number(e.getValue()))})},{accessorKey:"cachedInput",header:"Cached Input",cell:e=>c.jsx("span",{className:"num",children:ve(Number(e.getValue()))})},{accessorKey:"uncachedInput",header:"Uncached Input",cell:e=>c.jsx("span",{className:"num",children:ve(Number(e.getValue()))})},{accessorKey:"outputTokens",header:"Output Tokens",cell:e=>c.jsx("span",{className:"num",children:ve(Number(e.getValue()))})},{accessorKey:"reasoningOutput",header:"Reasoning Output",cell:e=>c.jsx("span",{className:"num",children:ve(Number(e.getValue()))})},{accessorKey:"cost",header:"Est. Cost",cell:e=>c.jsx("span",{className:"num",children:Xt(Number(e.getValue()))})},{accessorKey:"credits",header:"Codex Credits",cell:e=>c.jsx("span",{className:"num",children:ve(Number(e.getValue()))})},{accessorKey:"cachePct",header:"Cache %",cell:e=>c.jsx("span",{className:"cache-pill",children:dt(Number(e.getValue()))})},{accessorKey:"contextPct",header:"Context %",cell:e=>{const t=e.getValue();return c.jsx("span",{className:"num",children:typeof t=="number"?dt(t):"-"})}},{accessorKey:"costPerCall",header:"Cost / Call",cell:e=>c.jsx("span",{className:"num",children:Xt(Number(e.getValue()))})},{accessorKey:"coldResumeRisk",header:"Cold Resume Risk",cell:e=>c.jsx("span",{className:`status-badge ${Ki(String(e.getValue()))}`,children:String(e.getValue())})},{accessorKey:"productivity",header:"Productivity",cell:e=>c.jsx("span",{className:"score",children:Number(e.getValue())})}],Yl=[{id:"name",label:"Thread",locked:!0},{id:"latestActivity",label:"Latest"},{id:"turns",label:"Turns"},{id:"totalDuration",label:"Duration"},{id:"averageGap",label:"Avg Gap"},{id:"initiatorSummary",label:"Initiated"},{id:"modelSummary",label:"Models"},{id:"effortSummary",label:"Effort Mix"},{id:"totalTokens",label:"Total Tokens"},{id:"cachedInput",label:"Cached Input"},{id:"uncachedInput",label:"Uncached Input"},{id:"outputTokens",label:"Output Tokens"},{id:"reasoningOutput",label:"Reasoning Output"},{id:"cost",label:"Est. Cost"},{id:"credits",label:"Codex Credits"},{id:"cachePct",label:"Cache %"},{id:"contextPct",label:"Context %"},{id:"costPerCall",label:"Cost / Call"},{id:"coldResumeRisk",label:"Cold Resume Risk"},{id:"productivity",label:"Productivity"},{id:"investigate",label:"Investigate",locked:!0}];function Ki(e){return e==="High"?"red":e==="Medium"?"orange":e==="Low"?"green":"neutral"}const ht=1,re=0,qt=100,er=1e3,tr=500,nr="codexUsageDashboardLoadSettings",qi={day:1440*60*1e3,week:10080*60*1e3};function ct(e,t=tr){if((e==null?void 0:e.limit_label)==="All"||t===re&&(e==null?void 0:e.limit)==null)return re;const n=Number((e==null?void 0:e.limit)??(e==null?void 0:e.loaded_row_count)??t);return Number.isFinite(n)&&n>=0?n:t}function pt(e){return Number.isFinite(e)?e<=re?re:Math.max(ht,Math.round(e)):ht}function je(...e){const t=e.find(n=>typeof n=="number"&&Number.isFinite(n)&&n>0);return t?Math.max(ht,Math.round(t)):ht}function Hi(e,t,n=null,a="rows"){const r=pt(e);return{historyScope:t,loadWindow:a,limit:r===re?null:r,since:n}}function Wi(e){return e.limit??re}function mn(e){return gn(e==null?void 0:e.load_window)?e.load_window:e!=null&&e.since?"week":(e==null?void 0:e.limit_label)==="All"||(e==null?void 0:e.limit)==null?"all":"rows"}function Bi(e){return gn(e==null?void 0:e.default_load_window)?e.default_load_window:mn(e)}function Ht(e,t=new Date){const n=qi[e];if(!n)return null;const a=Math.floor(t.getTime()/6e4)*6e4;return new Date(a-n).toISOString()}function Re(e,t=tr){return e==="day"?"Last 24 hours":e==="week"?"Last 7 days":e==="all"?"All time":`Most recent ${pt(t).toLocaleString()}`}function Qi(e=ar()){if(!e)return null;try{const t=e.getItem(nr);if(!t)return null;const n=JSON.parse(t),a=typeof n.loadLimit=="number"&&Number.isFinite(n.loadLimit)?pt(n.loadLimit):void 0,r=n.historyScope==="active"||n.historyScope==="all"?n.historyScope:void 0,s=gn(n.loadWindow)?n.loadWindow:void 0;return a===void 0&&r===void 0&&s===void 0?null:{loadLimit:a,historyScope:r,loadWindow:s}}catch{return null}}function zi(e,t,n="rows",a=ar()){if(a)try{a.setItem(nr,JSON.stringify({loadLimit:pt(e),historyScope:t,loadWindow:n}))}catch{}}function gn(e){return e==="day"||e==="week"||e==="rows"||e==="all"}function Gi({currentLimit:e,loadedRows:t,pendingLimit:n}){const a=n===re?0:n+qt,s=Math.max(ht,e===re?0:e,a,t);return Math.ceil((s+er)/qt)*qt}function Ji({currentLimit:e,loadedRows:t,pendingLimit:n}){return Math.max(je(e),je(n),je(t))+er}function Zi({loadedRows:e,limit:t,totalRows:n}){return t===re&&e>0?`Loaded all ${e.toLocaleString()}`:n>e?`Loaded ${e.toLocaleString()} of ${n.toLocaleString()}`:`Loaded ${e.toLocaleString()} rows`}function ar(){try{return typeof window>"u"?null:window.sessionStorage}catch{return null}}async function Xi(e,t,n,a="",r=""){const s=Di();switch(e){case"threads":{const{threadCallsForCurrentUrl:i}=await O(async()=>{const{threadCallsForCurrentUrl:o}=await import("./ThreadsPage.js");return{threadCallsForCurrentUrl:o}},__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]));return te(`codex-thread-filtered-calls-${s}.csv`,i(t,a),ce,"call rows")}case"cache-context":{const{cacheContextCallsForCurrentUrl:i}=await O(async()=>{const{cacheContextCallsForCurrentUrl:o}=await import("./CacheContextPage.js");return{cacheContextCallsForCurrentUrl:o}},__vite__mapDeps([28,1,2,29,30,22,31,32,6,33,19,20,21,9,10,23,34,4,3,5,7,15,35,24,25,26,12]));return te(`codex-${e}-calls-${s}.csv`,i(t),ce,"call rows")}case"usage-drain":{const{usageDrainCallsForCurrentUrl:i}=await O(async()=>{const{usageDrainCallsForCurrentUrl:o}=await import("./UsageDrainPage.js");return{usageDrainCallsForCurrentUrl:o}},__vite__mapDeps([36,1,2,34,4,20,21,9,10,16,17,18,24,37,38,25,26,12,39]));return te(`codex-usage-drain-calls-${s}.csv`,i(t),ce,"call rows")}case"diagnostics":{const{diagnosticsCallsForCurrentUrl:i}=await O(async()=>{const{diagnosticsCallsForCurrentUrl:o}=await import("./DiagnosticsPage.js");return{diagnosticsCallsForCurrentUrl:o}},__vite__mapDeps([40,1,2,29,33,19,20,21,9,10,23,24,41,4,6,7,34,3,25,26,12]));return te(`codex-diagnostics-calls-${s}.csv`,i(t),ce,"call rows")}case"reports":{const{reportCallsForCurrentUrl:i}=await O(async()=>{const{reportCallsForCurrentUrl:o}=await import("./ReportsPage.js");return{reportCallsForCurrentUrl:o}},__vite__mapDeps([42,1,2,34,4,6,33,19,20,21,9,10,16,17,18,30,22,35,23,24,25,26,12,43]));return te(`codex-reports-evidence-${s}.csv`,i(t),ce,"call rows")}case"settings":return te(`codex-dashboard-settings-${s}.csv`,eo(t,n),Yi,"settings rows");case"calls":{const{callsForCurrentUrl:i}=await O(async()=>{const{callsForCurrentUrl:o}=await import("./CallsPage.js");return{callsForCurrentUrl:o}},__vite__mapDeps([44,1,2,3,4,5,6,7,14,29,33,19,45,22,11,12,13,35,23,24,34,46,47,38,25,26,48]));return te(`codex-calls-${s}.csv`,i(t.calls,a,r),ce,"call rows")}case"overview":{const{overviewCallsForQuery:i}=await O(async()=>{const{overviewCallsForQuery:o}=await import("./OverviewPage.js");return{overviewCallsForQuery:o}},__vite__mapDeps([49,1,2,34,4,20,21,9,10,31,32,6,16,17,18,45,22,11,12,13,14,35,23,24,25,26,50]));return te(`codex-overview-calls-${s}.csv`,i(t.calls,a),ce,"call rows")}case"investigator":{const{investigatorCallsForCurrentUrl:i}=await O(async()=>{const{investigatorCallsForCurrentUrl:o}=await import("./InvestigatorPage.js");return{investigatorCallsForCurrentUrl:o}},__vite__mapDeps([51,1,2,34,4,6,41,7,20,21,9,10,16,17,18,45,22,11,12,13,23,24,38,25,26,52]));return te(`codex-investigator-calls-${s}.csv`,i(t),ce,"call rows")}case"compression-lab":return te(`codex-compression-lab-scope-${s}.csv`,t.calls,ce,"call rows");case"call":return te(`codex-call-calls-${s}.csv`,Pi(t),ce,"call rows")}}function te(e,t,n,a){return{filename:e,csv:Ei(t,n),rowCount:t.length,label:a}}const Yi=[{header:"Field",value:e=>e.field},{header:"Value",value:e=>e.value}];function eo(e,t){return[{field:"live_api",value:t.canUseLiveApi?"available":"static snapshot"},{field:"context_api",value:t.contextRuntime.contextApiEnabled?"enabled":"gated"},{field:"history_scope",value:t.historyScope},{field:"data_window",value:Re(t.loadWindow,t.loadLimit)},{field:"scope_since",value:t.scopeSince??"none"},{field:"row_request",value:t.loadLimit===re?"no cap":String(t.loadLimit)},{field:"loaded_rows",value:String(t.loadedRowCount)},{field:"total_available_rows",value:String(t.totalAvailableRows)},{field:"auto_refresh",value:t.autoRefreshEnabled?"enabled":"paused"},{field:"refresh_state",value:t.refreshState},{field:"visible_calls",value:String(e.calls.length)},{field:"visible_threads",value:String(e.threads.length)}]}function to(e){return e instanceof Error?e.message:String(e)}function Yn(e,t){const n=t==="all"?"all history":"active history",a=e.message||"Refreshing usage index",r=typeof e.percent=="number"?` ${Math.round(e.percent)}%`:"";return`${a}${r} (${n})`}function Wt(e,t="active"){return e?typeof e.include_archived=="boolean"?e.include_archived?"all":"active":e.history_scope==="all-history"||e.history_scope==="all"?"all":e.history_scope==="active"?"active":t:t}function no({historyScope:e,activeRows:t,allRows:n,archivedRows:a}){const r=ao({activeRows:t,allRows:n,archivedRows:a});return e==="all"?r===null?"All history selected":r>0?`All history includes ${r.toLocaleString()} archived calls`:"All history selected; no archived calls are indexed yet":r===null||r<=0?"Active sessions only":`Active sessions only; ${r.toLocaleString()} archived calls hidden`}function ao({activeRows:e,allRows:t,archivedRows:n}){const a=Bt(n);if(a!==null)return a;const r=Bt(e),s=Bt(t);return r!==null&&s!==null?Math.max(s-r,0):null}function Bt(e){return typeof e=="number"&&Number.isFinite(e)&&e>=0?Math.round(e):null}const rr=["aria-description","aria-label","aria-valuetext","title","placeholder","alt"],ro="data-localization-attributes",so=new Set(["CODE","KBD","PRE","SAMP","SCRIPT","STYLE","TEXTAREA"]),en=new WeakMap,tn=new WeakMap;function io({value:e,children:t}){return c.jsxs(Ys,{value:e,children:[c.jsx(oo,{}),t]})}function oo(){const e=Va();return C.useLayoutEffect(()=>{const t=document.querySelector("[data-dashboard-localization-root]");if(!t)return;if(e.language!=="zh-Hans"){lo(t),document.title="Codex Usage Tracker React Dashboard";return}document.title="Codex Usage Tracker · 用量仪表盘",ea(t,e.translateText);const n=new MutationObserver(a=>{for(const r of a){if(r.type==="characterData"&&r.target instanceof Text){nn(r.target,e.translateText);continue}if(r.type==="attributes"&&r.target instanceof Element){an(r.target,e.translateText);continue}for(const s of r.addedNodes)s instanceof Text?nn(s,e.translateText):s instanceof Element&&ea(s,e.translateText)}});return n.observe(t,{attributes:!0,attributeFilter:[...rr],characterData:!0,childList:!0,subtree:!0}),()=>n.disconnect()},[e]),null}function ea(e,t){if(Rt(e))return;an(e,t);const n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT);let a=n.nextNode();for(;a;){if(a instanceof Element){if(Rt(a)){a=co(n,a,e);continue}an(a,t)}else a instanceof Text&&nn(a,t);a=n.nextNode()}}function co(e,t,n){let a=t;for(;a&&a!==n;){const r=e.nextSibling();if(r)return r;a=a.parentNode,a&&(e.currentNode=a)}return null}function nn(e,t){const n=e.parentElement;if(!n||Rt(n))return;const a=e.nodeValue??"",r=en.get(e),s=r&&(a===r.source||a===r.translated)?r.source:a,i=s.match(/^(\s*)([\s\S]*?)(\s*)$/u);if(!i||!i[2])return;const o=t(i[2]),d=`${i[1]}${o}${i[3]}`;en.set(e,{source:s,translated:d}),d!==a&&(e.nodeValue=d)}function an(e,t){if(Rt(e))return;const n=new Set((e.getAttribute(ro)??"").split(/[\s,]+/u).filter(Boolean));if(!n.size)return;const a=tn.get(e)??new Map;for(const r of rr){if(!n.has(r))continue;const s=e.getAttribute(r);if(!s)continue;const i=a.get(r),o=i&&(s===i.source||s===i.translated)?i.source:s,d=t(o);a.set(r,{source:o,translated:d}),d!==s&&e.setAttribute(r,d)}a.size&&tn.set(e,a)}function lo(e){const t=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT);ta(e);let n=t.nextNode();for(;n;){if(n instanceof Text){const a=en.get(n);a&&n.nodeValue===a.translated&&(n.nodeValue=a.source)}else n instanceof Element&&ta(n);n=t.nextNode()}}function ta(e){const t=tn.get(e);if(t)for(const[n,a]of t)e.getAttribute(n)===a.translated&&e.setAttribute(n,a.source)}function Rt(e){return so.has(e.tagName)||!!e.closest('[data-localization-skip="true"]')}const Mt=["May 12","May 19","May 26","Jun 02","Jun 09","Jun 16","Jun 23","Jun 30"],it=Mt.slice(2),sr={contextRuntime:{apiToken:"",contextApiEnabled:!1,fileMode:!1},cards:[{label:"Total Tokens",value:"24.83M",detail:"7-day total: 12.56M",trend:"up 18.7% vs prior 7 days",tone:"blue"},{label:"Estimated Cost",value:"$42.67",detail:"7-day total: $24.81",trend:"up 14.2% vs prior 7 days",tone:"green"},{label:"Cache Hit Rate",value:"38.7%",detail:"7-day average: 42.3%",trend:"down 3.6pp vs prior 7 days",tone:"purple"},{label:"Total Calls",value:"1,342",detail:"678 calls in last 7 days",trend:"up 11.3% vs prior 7 days",tone:"blue"},{label:"Usage Remaining",value:"32.4%",detail:"~15.9K credits estimated",trend:"resets in 4d 12h",tone:"green"}],tokenSeries:[W("input","Input","#2563eb",[5.2,6.4,7.5,9.2,6.7,7.5,6.6,9],1e6),W("output","Output","#059669",[2.5,3,3.6,5.1,3.7,5,4,5.4],1e6),W("cached","Cached","#7c3aed",[1.1,1.4,1.7,2.4,1.8,2.3,1.8,2.6],1e6,!0)],costSeries:[W("cost","Estimated Cost","#2563eb",[8.4,10.1,14.3,9.9,11.4,7.8,12.7,16.5])],cacheSeries:[W("cache","Cache Hit Rate","#059669",[58,61,39,45,50,44,48,41])],weeklyCreditSeries:[{id:"pro",label:"Pro observed",color:"#2563eb",points:Mt.map((e,t)=>({label:e,value:[59800,44900,45e3,40500,42800,38900,31400,35900][t]??0,low:[52100,38800,38900,34700,36200,32200,26100,29900][t]??0,high:[67400,51200,51100,46800,49200,45100,37300,41900][t]??0}))},W("pro-trend","Pro trend","#1d4ed8",[54.2,51,47.8,44.6,41.4,38.2,35,31.8],1e3,!0),W("prolite","Prolite observed","#0f766e",[15.9,15.8,15.9,16.1,15.7,15.6,15.9,15.8],1e3)],usageRemainingSeries:[W("remaining","Usage remaining","#059669",[87,71,63,56,56,48,50,54]),W("allowance","Allowance guide","#0f766e",[86,85,84,83,82,81,80,79],1,!0)],actualVsPredictedSeries:[W("observed","Observed drain","#2563eb",[18.7,22.1,45.3,31,34.8],1e3),W("predicted","Predicted baseline","#1d4ed8",[17.9,21.4,41.2,29.6,33.9],1e3,!0)],findings:[{rank:1,title:"Long Thread: data-engine-refactor",severity:"High",credits:12847,share:25.6,summary:"Very long duration with high model effort."},{rank:2,title:"Cache Misses (Large Inputs)",severity:"High",credits:9612,share:19.1,summary:"Large uncached inputs across 214 calls."},{rank:3,title:"High Model Effort",severity:"Medium",credits:7404,share:14.7,summary:"Reasoning and output token volume are concentrated."},{rank:4,title:"Tool Output Volume",severity:"Medium",credits:5231,share:10.4,summary:"Large tool outputs returned to the model."}],calls:[["2026-06-01T10:24:00Z","Jun 1, 10:24 AM","thread-9f3a1c","codex-1","high",128542,45231,62,.42,"18.4s",!1,["review"]],["2026-06-01T10:18:00Z","Jun 1, 10:18 AM","thread-7b2e91","o4-mini","medium",98731,32104,41,.31,"12.7s",!0,["analysis"]],["2026-06-01T10:12:00Z","Jun 1, 10:12 AM","thread-3c8d4e","o3","high",64221,18903,28,.12,"9.3s",!0,["uncached"]],["2026-06-01T10:06:00Z","Jun 1, 10:06 AM","thread-1a2b3c","codex-1","high",245123,98112,71,.87,"27.6s",!1,["large"]],["2026-06-01T10:01:00Z","Jun 1, 10:01 AM","thread-8d7c6b","gpt-4.1","low",12543,4231,12,.03,"3.6s",!0,["quick"]],["2026-06-01T09:55:00Z","Jun 1, 09:55 AM","thread-2f9e7d","o4-mini","medium",76881,28442,39,.24,"11.2s",!1,["subagent"]],["2026-06-01T09:50:00Z","Jun 1, 09:50 AM","thread-6a5b4c","codex-1","high",312654,112991,67,1.12,"31.8s",!1,["file-heavy"]],["2026-06-01T09:47:00Z","Jun 1, 09:47 AM","thread-0f1e2d","o3","low",8221,2903,15,.02,"2.8s",!0,["fast"]]].map(([e,t,n,a,r,s,i,o,d,f,h,v],u)=>{const p=Number(s),g=Number(i),b=Number(o),w=Math.round(p*Math.max(100-b,0)/100);return{id:`fixture-call-${u}`,rawTime:String(e),eventTimestamp:String(e),callStartedAt:String(e),time:String(t),thread:String(n),model:String(a),effort:String(r),input:p,output:g,reasoningOutput:Math.round(g*.2),totalTokens:p+g,cachedInput:Math.round(p*b/100),uncachedInput:w,cachedPct:b,cost:u===4?0:Number(d),standardCost:u===4?0:Number(d),priorityCost:u===4?0:Number(d)*2,pricingServiceTier:"standard",billingBasis:"unknown",costSemantics:"api_token_estimate",credits:(u===4?0:Number(d))*25,duration:String(f),durationSeconds:Number.parseFloat(String(f))||0,previousCallGap:u===0?"-":`${u*7}m 0s`,previousCallEventTimestamp:u===0?"":`2026-06-01T09:${String(40+u).padStart(2,"0")}:00Z`,previousCallGapSeconds:u*420,initiator:u%3===0?"user":u%3===1?"assistant":"tool",initiatorReason:u%3===0?"direct user request":u%3===1?"assistant follow-up":"tool-driven continuation",initiatorConfidence:u%2===0?"exact":"estimated",serviceTier:"",fast:null,serviceTierSource:"",serviceTierConfidence:"",fastProxyCandidate:!!h,standardUsageCredits:(u===4?0:Number(d))*25,fastUsageCredits:(u===4?0:Number(d))*50,usageCreditMultiplier:1,usageCreditMultiplierSource:"tier_unknown",usageCreditMultiplierSourceUrl:"https://example.invalid/fixture-fast",usageCreditMultiplierFetchedAt:"2026-07-16",usageCreditMultiplierConfidence:"exact",usageCreditConfidence:u===7?"user_override":u%4===0?"exact":u%4===1?"estimated":u%4===2?"missing":"fixture",usageCreditModel:u===4?"":`${a}-credits`,usageCreditSource:u===4?"":"fixture-rate-card",usageCreditFetchedAt:u===4?"":"2026-06-01T10:00:00Z",usageCreditTier:u%2===0?"standard":"estimated",usageCreditNote:u===2?"fixture inherited rate card":"",pricingModel:u===4?"":`${a}-pricing`,pricingEstimated:u===1||u===5,signal:w>5e4?"cache-risk":"aggregate",recommendation:w>5e4?"Review uncached aggregate input before continuing this thread.":"",tags:v,sessionId:`fixture-session-${u}`,turnId:`fixture-turn-${u}`,parentSessionId:u%3===0?"fixture-parent-session":"",parentSessionUpdatedAt:u%3===0?"2026-06-01T09:30:00Z":"",parentThread:u%3===0?"parent-thread-analysis":"",threadAttachmentLabel:u%3===0?"spawned child thread":"direct active thread",threadSource:u%3===0?"subagent":"user",subagentType:u%3===0?"analysis":"",agentRole:u%3===0?"reviewer":"",agentNickname:u%3===0?"usage-reviewer":"",project:u%2===0?"codex-usage-tracker":"local-ops",projectRelativeCwd:u%2===0?"frontend/dashboard":".",projectTags:u%2===0?["dashboard","rewrite"]:["local"],cwd:`/fixtures/${u%2===0?"codex-usage-tracker":"local-ops"}`,sourceFile:`fixture-thread-${u}.jsonl`,lineNumber:120+u,gitBranch:"experiment/frontend-rewrite",gitRemoteLabel:"origin",gitRemoteHash:`fixture-${u}`,contextWindowPct:Math.min(18+b,96),modelContextWindow:128e3,cumulativeTotalTokens:p+g+u*1e4,estimatedCacheSavings:Math.round((p-w)*1e-5*100)/100,efficiencyFlags:w>5e4?["cache-risk"]:[]}}),threads:[["thread-9f3a",142,58400,8.76,12,1.38,"High",42],["thread-7c2b",87,31200,4.21,22,1.12,"Medium",55],["thread-1a8c",64,22700,3.02,18,.98,"High",48],["thread-d3e1",53,18100,2.11,41,.81,"Low",72],["thread-b7f0",41,13600,1.65,47,.73,"Low",75],["thread-3c5d",36,9900,1.18,35,.66,"Medium",63],["thread-0e16",28,6400,.72,56,.58,"Low",82]].map(([e,t,n,a,r,s,i,o],d)=>{const f=Number(t),h=Number(n),v=Number(r),u=f*48,p=(d+1)*420;return{name:String(e),latestCallId:`fixture-call-${d}`,latestActivity:`Jun ${d+1}, 10:${String(24-d).padStart(2,"0")} AM`,latestActivityRaw:`2026-06-${String(d+1).padStart(2,"0")}T10:${String(24-d).padStart(2,"0")}:00Z`,turns:f,totalDurationSeconds:u,totalDuration:`${Math.floor(u/60)}m ${u%60}s`,averageGapSeconds:p,averageGap:`${Math.floor(p/60)}m ${p%60}s`,initiatorSummary:d%2===0?"user x4, assistant x2":"assistant x3, tool x1",modelSummary:d%2===0?"codex-1 x5, o4-mini x2":"o4-mini x3, o3 x1",effortSummary:d%2===0?"high x5, medium x2":"medium x3, low x1",totalTokens:h,cachedInput:Math.round(h*v/100),uncachedInput:Math.round(h*Math.max(100-v,0)/100),outputTokens:Math.round(h*.28),reasoningOutput:Math.round(h*.08),cost:Number(a),credits:Number(a)*25,cachePct:v,contextPct:Math.min(96,28+d*7),costPerCall:Number(s),coldResumeRisk:i,productivity:Number(o)}}),weeklyWindows:Mt.map((e,t)=>({week:e,plan:t===0?"Prolite":"Pro",observedPct:[61.4,49.2,47.7,48.3,44.5,37.4,40.1,35.8][t]??0,credits:[49812,41275,39887,40563,37284,31420,33842,35900][t]??0,projected:[49812,41275,39887,40563,37284,31420,33842,35900][t]??0,ciLow:[42156,34892,33424,34021,31241,26164,28234,29450][t]??0,ciHigh:[57467,47657,46349,47105,43326,36676,39450,41900][t]??0,confidence:t===0?"Medium":"High",note:["Prolite baseline","Drop in credits","Stable","Slight uptick","Down again","Lowest window","Recovery","Latest"][t]??""})),modelCosts:[{label:"codex-1",value:16.21,color:"#2563eb"},{label:"o3",value:12.43,color:"#1d4ed8"},{label:"o4-mini",value:7.62,color:"#059669"},{label:"gpt-4.1",value:4.91,color:"#f59e0b"},{label:"other",value:1.5,color:"#94a3b8"}],commandActions:[{title:"Show highest uncached calls",status:"Ready",owner:"Calls",description:"214 calls above the uncached threshold."},{title:"Compare Pro weeks",status:"Ready",owner:"Usage Drain",description:"Allowance windows with confidence intervals."},{title:"Find cold resumes",status:"Ready",owner:"Cache",description:"14 threads with long idle gaps."},{title:"Export support bundle",status:"Planned",owner:"Reports",description:"Local aggregate artifacts only."}],cacheSegments:[{label:"Cache read",value:38.7,color:"#2563eb"},{label:"Cache write",value:29.6,color:"#059669"},{label:"Uncached",value:31.7,color:"#7c3aed"}],cacheHeatmap:[{thread:"thread-8c1e",labels:it,values:[62,71,89,82,74,31]},{thread:"thread-2b9d",labels:it,values:[42,58,77,61,51,24]},{thread:"thread-713a",labels:it,values:[78,81,83,66,59,37]},{thread:"thread-4af2",labels:it,values:[24,36,63,54,71,44]},{thread:"thread-f9c3",labels:it,values:[18,25,41,38,52,22]}],diagnostics:[{title:"Usage Drain",status:"Ready",finding:"Projected weekly credits declined from the baseline and partially recovered in the latest window.",confidence:"High",metric:"33,842 credits / week",series:[W("usage-drain","Projected credits","#2563eb",[41.8,46.7,45.9,32.1,33.8],1e3)]},{title:"Cache Behavior",status:"Ready",finding:"Cache hit rate is healthy overall, with spikes aligned to large cache misses and cold resumes.",confidence:"Medium",metric:"41.1% hit rate",series:[W("cache","Cache hit %","#059669",[44,48,38,33,40])]},{title:"Thread Efficiency",status:"Ready",finding:"Long threads account for most estimated cost and are the clearest optimization target.",confidence:"High",metric:"65% cost share",series:[W("threads","Cost share","#1d4ed8",[65,23,8,4])]},{title:"Tool And Command Activity",status:"Stale",finding:"Command volume is stable with a slight upward trend; read and shell commands dominate.",confidence:"Medium",metric:"912 commands",series:[W("commands","Commands","#7c3aed",[542,488,611,883,912])]}],reports:[{title:"Weekly Credits",status:"Ready",owner:"Usage Drain",description:"Plan-specific weekly credits, trend lines, and confidence intervals."},{title:"Usage Remaining",status:"Ready",owner:"Usage Drain",description:"Observed remaining usage over time with reset handling."},{title:"Cost Curves",status:"Ready",owner:"Threads",description:"Cumulative estimated cost by thread and concentration metrics."},{title:"Usage Drain Model",status:"Ready",owner:"Reports",description:"Actual-vs-predicted drain and feature group comparisons."},{title:"Fast Mode Proxy",status:"Planned",owner:"Calls",description:"Candidate detection, speedup histogram, and confidence breakdowns."},{title:"Allowance Change",status:"Planned",owner:"Reports",description:"Week-to-week allowance estimate changes with careful language."}]};function W(e,t,n,a,r=1,s=!1){return{id:e,label:t,color:n,dashed:s,points:a.map((i,o)=>({label:Mt[o]??`Point ${o+1}`,value:i*r}))}}function ir(e){if(window.location.protocol==="file:")throw new Error("Live refresh requires the localhost dashboard server.");if(!(e!=null&&e.api_token))throw new Error("Live refresh requires localhost dashboard API token.")}function pn(e){return{Accept:"application/json","X-Codex-Usage-Token":e.api_token||""}}function uo(e,t){return new Promise((n,a)=>{if(t!=null&&t.aborted){a(na(t));return}const r=window.setTimeout(()=>{t==null||t.removeEventListener("abort",s),n()},e);function s(){window.clearTimeout(r),a(na(t))}t==null||t.addEventListener("abort",s,{once:!0})})}function or(e){return(e instanceof DOMException||e instanceof Error)&&e.name==="AbortError"}function na(e){return(e==null?void 0:e.reason)instanceof Error?e.reason:new DOMException("The request was cancelled.","AbortError")}const ho=["#2563eb","#1d4ed8","#059669","#f59e0b","#94a3b8"];function cr(e){const t=new Map;e.forEach(r=>{const s=r.model||"unknown";t.set(s,(t.get(s)??0)+r.cost)});const n=[...t.entries()].sort((r,s)=>s[1]-r[1]||r[0].localeCompare(s[0]));return(n.length>5?[...n.slice(0,4),["other",n.slice(4).reduce((r,s)=>r+s[1],0)]]:n).map(([r,s],i)=>({label:r,value:s,color:ho[i]??"#94a3b8"}))}function lr(e){if(!e.length)return[];const t=Math.max(e.reduce((n,a)=>n+bt(a),0),1);return[fo(e,t),mo(e,t),go(e,t),po(e,t)].filter(n=>!!n).sort((n,a)=>a.credits-n.credits).map((n,a)=>({...n,rank:a+1}))}function ur(e){if(!e.length)return[];const t=[{title:"Cost Curves",status:"Ready",owner:"Threads",description:"Estimated cost concentration by loaded aggregate thread."},{title:"Usage Drain Model",status:"Ready",owner:"Reports",description:"Highest estimated credit-impact calls from loaded aggregate rows."}];return e.some(n=>n.fastProxyCandidate||n.effort.toLowerCase()==="low")&&t.push({title:"Fast Mode Proxy",status:"Ready",owner:"Calls",description:"Low-effort and fast-call candidates inferred from aggregate rows."}),t}function fo(e,t){const n=new Map;e.forEach(i=>n.set(i.thread,[...n.get(i.thread)??[],i]));const[a,r]=[...n.entries()].sort((i,o)=>Ae(o[1],f=>f.totalTokens)-Ae(i[1],f=>f.totalTokens)||o[1].length-i[1].length)[0]??[];if(!a||!(r!=null&&r.length)||r.length<2)return null;const s=Ae(r,bt);return{rank:0,title:`Long Thread: ${a}`,severity:s/t>=.25||r.length>=8?"High":"Medium",credits:Math.round(s),share:s/t*100,summary:`${r.length.toLocaleString()} loaded calls and ${Ae(r,i=>i.totalTokens).toLocaleString()} tokens in this thread.`}}function mo(e,t){const n=e.filter(r=>r.signal==="cache-risk"||r.cachedPct<35||r.uncachedInput>5e4);if(!n.length)return null;const a=Ae(n,bt);return{rank:0,title:"Cache Misses (Large Inputs)",severity:n.some(r=>r.cachedPct<20||r.uncachedInput>5e4)?"High":"Medium",credits:Math.round(a),share:a/t*100,summary:`${n.length.toLocaleString()} loaded calls show low cache reuse or large uncached input.`}}function go(e,t){const n=e.filter(r=>r.effort.toLowerCase()==="high"||r.reasoningOutput>0);if(!n.length)return null;const a=Ae(n,bt);return{rank:0,title:"High Model Effort",severity:a/t>=.25?"High":"Medium",credits:Math.round(a),share:a/t*100,summary:`${n.length.toLocaleString()} loaded calls use high effort or report reasoning output.`}}function po(e,t){const n=Math.max(25e3,bo(e.map(s=>s.output),.75)),a=e.filter(s=>s.output>=n||s.tags.some(i=>["file-heavy","subagent","large"].includes(i)));if(!a.length)return null;const r=Ae(a,bt);return{rank:0,title:"Tool Output Volume",severity:a.some(s=>s.output>5e4)?"High":"Medium",credits:Math.round(r),share:r/t*100,summary:`${a.length.toLocaleString()} loaded calls have high output volume or file-heavy/subagent tags.`}}function bt(e){return e.credits>0?e.credits:e.cost*25}function Ae(e,t){return e.reduce((n,a)=>n+t(a),0)}function bo(e,t){const n=e.filter(a=>Number.isFinite(a)).sort((a,r)=>a-r);return n.length?n[Math.min(n.length-1,Math.max(0,Math.floor((n.length-1)*t)))]??0:0}function dr(e){const t=new Map;for(const a of e){if(!Number.isFinite(a.timestamp))continue;const r=hr(a.timestamp),s=t.get(r)??{label:fr(a.timestamp),timestamp:wo(a.timestamp),cached:0,cost:0,input:0,output:0};s.cached+=a.cached,s.cost+=a.cost,s.input+=a.input,s.output+=a.output,t.set(r,s)}const n=vo(t);return n.length?{tokenSeries:[{id:"input",label:"Input",color:"#2563eb",points:n.map(a=>({label:a.label,value:a.input}))},{id:"output",label:"Output",color:"#059669",points:n.map(a=>({label:a.label,value:a.output}))},{id:"cached",label:"Cached",color:"#7c3aed",dashed:!0,points:n.map(a=>({label:a.label,value:a.cached}))}],costSeries:[{id:"cost",label:"Estimated Cost",color:"#f59e0b",points:n.map(a=>({label:a.label,value:a.cost}))}],cacheSeries:[{id:"cache",label:"Cache hit %",color:"#2563eb",points:n.map(a=>({label:a.label,value:a.input>0?a.cached/a.input*100:0}))}]}:{tokenSeries:[],costSeries:[],cacheSeries:[]}}function vo(e){const t=[...e.values()].sort((s,i)=>s.timestamp-i.timestamp),n=t.at(0),a=t.at(-1);if(!n||!a)return[];const r=[];for(const s=new Date(n.timestamp);s.getTime()<=a.timestamp;s.setDate(s.getDate()+1)){const i=s.getTime(),o=hr(i);r.push(e.get(o)??{label:fr(i),timestamp:i,cached:0,cost:0,input:0,output:0})}return r}function hr(e){const t=new Date(e),n=String(t.getMonth()+1).padStart(2,"0"),a=String(t.getDate()).padStart(2,"0");return`${t.getFullYear()}-${n}-${a}`}function wo(e){const t=new Date(e);return new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime()}function fr(e){return new Intl.DateTimeFormat("en-US",{month:"short",day:"numeric"}).format(new Date(e))}function B(e,t){var a;const n=Number(((a=e.summary)==null?void 0:a[t])??0);return Number.isFinite(n)?n:0}function yo(e){if(!(!e.summary||e.load_window==="rows"))return{visibleCalls:B(e,"visible_calls"),inputTokens:B(e,"input_tokens"),cachedInputTokens:B(e,"cached_input_tokens"),uncachedInputTokens:B(e,"uncached_input_tokens"),outputTokens:B(e,"output_tokens"),reasoningOutputTokens:B(e,"reasoning_output_tokens"),totalTokens:B(e,"total_tokens"),estimatedCostUsd:B(e,"estimated_cost_usd"),usageCredits:B(e,"usage_credits")}}function _o(e){return{cost:Number(e.estimated_cost_usd??0),standardCost:aa(e.standard_cost_usd),priorityCost:aa(e.priority_cost_usd),pricingServiceTier:String(e.pricing_service_tier??""),billingBasis:String(e.billing_basis??"unknown"),costSemantics:String(e.cost_semantics??"api_token_estimate")}}function aa(e){if(e==null||e==="")return null;const t=Number(e);return Number.isFinite(t)?t:null}function So(e,t,n){const a=e.fast,r=a===!0||a===1?!0:a===!1||a===0?!1:null;return{credits:Number(e.usage_credits??0),serviceTier:String(e.service_tier??""),fast:r,serviceTierSource:String(e.service_tier_source??""),serviceTierConfidence:String(e.service_tier_confidence??""),fastProxyCandidate:t>0&&n/Math.max(t,1)>4e3,standardUsageCredits:Number(e.standard_usage_credits??e.usage_credits??0),fastUsageCredits:Co(e.fast_usage_credits),usageCreditMultiplier:Number(e.usage_credit_multiplier??1),usageCreditMultiplierSource:String(e.usage_credit_multiplier_source??""),usageCreditMultiplierSourceUrl:String(e.usage_credit_multiplier_source_url??""),usageCreditMultiplierFetchedAt:String(e.usage_credit_multiplier_fetched_at??""),usageCreditMultiplierConfidence:String(e.usage_credit_multiplier_confidence??"")}}function Co(e){if(e==null||e==="")return null;const t=Number(e);return Number.isFinite(t)?t:null}const xo=1e4,ko=500;async function ra(e,t,n){var s,i;const a=t.limit&&t.limit>0?t.limit:ko,r=await n(e,{...t,limit:a});return(i=t.onProgress)==null||i.call(t,{status:"completed",phase:"loading_rows",message:`Loaded ${t.loadWindow==="all"?"all-history":t.loadWindow} evidence window`,completed:Number(r.loaded_row_count??((s=r.rows)==null?void 0:s.length)??0),total:Number(r.total_available_rows??r.loaded_row_count??0),percent:100}),r}async function sa(e,t,n){var o,d;const a=[];let r=0,s=null,i=Number((e==null?void 0:e.total_available_rows)??0);for(let f=0;f<1e3;f+=1){(o=t.signal)==null||o.throwIfAborted();const h=await n(e,{...t,refresh:f===0?t.refresh:!1,limit:xo,offset:r}),v=h.rows??[];s=h,a.push(...v),i=Number(h.total_available_rows??i??a.length);const u=a.length>=i||!h.has_more;if((d=t.onProgress)==null||d.call(t,{status:u?"completed":"running",phase:"loading_rows",message:"Loading all rows",completed:a.length,total:i,percent:u?100:i>0?Math.min(99,Math.floor(a.length/i*100)):0}),r+=v.length,!h.has_more||v.length===0||a.length>=i)break}return{...s??e??{},rows:a,loaded_row_count:a.length,limit:null,limit_label:"All",has_more:!1,total_available_rows:i||a.length}}function To(){if(window.__CODEX_USAGE_BOOT__)return window.__CODEX_USAGE_BOOT__;const e=document.getElementById("usage-data");if(!(e!=null&&e.textContent))return null;try{return JSON.parse(e.textContent)}catch{return null}}async function Lo(e,t={}){if(t.refresh&&(e!=null&&e.refresh_jobs_available)){const n=await Ro(e,t),a={...t,refresh:!n};return a.loadWindow&&a.loadWindow!=="rows"?ra(e,a,He):a.limit===0?sa(e,a,He):He(e,a)}return t.loadWindow&&t.loadWindow!=="rows"?ra(e,t,He):t.limit===0?sa(e,t,He):He(e,t)}async function Ro(e,t){try{return await Mo(e,t),!0}catch(n){if(or(n)||n instanceof mr)throw n;return!1}}async function He(e,t={}){ir(e);const n=new URLSearchParams({refresh:t.refresh?"1":"0",limit:String(t.limit??(e==null?void 0:e.limit)??(e==null?void 0:e.loaded_row_count)??500),_:String(Date.now())});t.loadWindow&&n.set("load_window",t.loadWindow),t.since&&n.set("since",t.since),t.offset&&t.offset>0&&n.set("offset",String(t.offset)),(t.includeArchived??_r(e))&&n.set("include_archived","1");const r=await fetch(`/api/usage?${n.toString()}`,{headers:pn(e),cache:"no-store",signal:t.signal});return await bn(r,"Usage refresh")}async function Mo(e,t){var o;ir(e);const n=new URLSearchParams({_:String(Date.now())});(t.includeArchived??_r(e))&&n.set("include_archived","1");const r=await fetch(`/api/refresh/start?${n.toString()}`,{headers:pn(e),cache:"no-store",signal:t.signal}),s=await bn(r,"Usage refresh start");(o=t.onProgress)==null||o.call(t,s);const i=typeof s.job_id=="string"?s.job_id:"";if(!i)throw new Error("Usage refresh start did not return a job id.");return Po(e,i,t.onProgress,t.signal)}async function Po(e,t,n,a){for(let r=0;r<600;r+=1){a==null||a.throwIfAborted();const s=new URLSearchParams({job_id:t,_:String(Date.now())}),i=await fetch(`/api/refresh/status?${s.toString()}`,{headers:pn(e),cache:"no-store",signal:a}),o=await bn(i,"Usage refresh status");if(n==null||n(o),o.status==="completed")return o;if(o.status==="failed")throw new mr(o.error||o.message||"Usage refresh failed.");await uo(Math.min(1e3,150+r*50),a)}throw new Error("Usage refresh did not complete before the polling timeout.")}class mr extends Error{}function ia(e){if(!e)return{...sr,contextRuntime:sn(e)};const t=e.rows??[],n=t.map(pr),a=t.length?Te(t,b=>Number(b.total_tokens??0)):B(e,"total_tokens"),r=t.length?Te(t,b=>Number(b.estimated_cost_usd??0)):B(e,"estimated_cost_usd"),s=t.length?Te(t,b=>Number(b.cached_input_tokens??0)):B(e,"cached_input_tokens"),i=t.length?Te(t,b=>Number(b.input_tokens??0)):B(e,"input_tokens"),o=t.length?Te(t,b=>{const w=Number(b.input_tokens??0),T=Number(b.cached_input_tokens??0);return Number(b.uncached_input_tokens??Math.max(w-T,0))}):Math.max(i-s,0),d=t.length?Te(t,b=>Number(b.output_tokens??0)):B(e,"output_tokens"),f=t.length?Te(t,b=>Number(b.reasoning_output_tokens??0)):B(e,"reasoning_output_tokens"),h=i>0?s/i*100:0,v=n.length||Math.max(0,Number(e.loaded_row_count??0)),u=Ao(t),p=No(t),g=$o({cachePct:h,cachedTokens:s,estimatedCost:r,historyScope:e.history_scope??"active",totalCalls:v,totalTokens:a,tokenBreakdown:{cachedInput:s,uncachedInput:o,output:d,reasoningOutput:f},usageRemainingCard:Io(e)});return{...jo(e),contextRuntime:sn(e),scopeSummary:yo(e),cards:g,...u,...p,calls:n,threads:Sr(n),findings:lr(n),modelCosts:cr(n),reports:ur(n),cacheSegments:[{label:"Cache read",value:h,color:"#2563eb"},{label:"Uncached input",value:Math.max(100-h,0),color:"#7c3aed"}]}}function jo(e){return{...sr,contextRuntime:sn(e),tokenSeries:[],costSeries:[],cacheSeries:[],weeklyCreditSeries:[],usageRemainingSeries:[],actualVsPredictedSeries:[],calls:[],threads:[],findings:[],weeklyWindows:[],modelCosts:[],commandActions:[],cacheSegments:[],cacheHeatmap:[],diagnostics:[],reports:[]}}function Ao(e){return dr(e.map(t=>({timestamp:wn(t),cached:Number(t.cached_input_tokens??0),cost:Number(t.estimated_cost_usd??0),input:Number(t.input_tokens??0),output:Number(t.output_tokens??0)})))}function No(e){const t=[...e].map(f=>({row:f,timestamp:wn(f)})).filter(f=>Number.isFinite(f.timestamp)).sort((f,h)=>f.timestamp-h.timestamp),n=new Map;for(const{row:f,timestamp:h}of t){const v=rn(h),u=n.get(v)??{timestamp:h,credits:0,weeklyUsedPercent:null};u.credits+=Math.max(0,Number(f.usage_credits??0));const p=Oe(f.rate_limit_secondary_used_percent);p!==null&&(u.weeklyUsedPercent=p),n.set(v,u)}const a=[...n.entries()].map(([f,h])=>({label:f,...h}));let r=0;const s=a.map(f=>(r+=f.credits,{label:f.label,value:r})),i=s.map((f,h)=>{var v;return{label:f.label,value:s.length>1?(((v=s.at(-1))==null?void 0:v.value)??0)*((h+1)/s.length):f.value}}),o=a.filter(f=>f.weeklyUsedPercent!==null).map(f=>({label:f.label,value:Pt(100-(f.weeklyUsedPercent??0))})),d=Eo(e);return{weeklyCreditSeries:Fo(d),usageRemainingSeries:o.length?[{id:"weekly-remaining",label:"Weekly remaining",color:"#059669",points:o}]:[],actualVsPredictedSeries:s.length?[{id:"observed-drain",label:"Observed drain",color:"#2563eb",points:s},{id:"loaded-baseline",label:"Loaded-row baseline",color:"#1d4ed8",dashed:!0,points:i}]:[],weeklyWindows:d}}function Eo(e){const t=new Map;for(const n of e){const a=wn(n);if(!Number.isFinite(a))continue;const r=Do(n,a),s=t.get(r)??{rows:[],latestTimestamp:0,latestUsedPercent:null};s.rows.push(n),a>=s.latestTimestamp&&(s.latestTimestamp=a,s.latestUsedPercent=Oe(n.rate_limit_secondary_used_percent)),t.set(r,s)}return[...t.entries()].map(([n,a])=>{var i;const r=a.rows.reduce((o,d)=>o+Math.max(0,Number(d.usage_credits??0)),0),s=a.latestUsedPercent??0;return{week:n,plan:String(((i=a.rows[0])==null?void 0:i.rate_limit_plan_type)??"unknown"),observedPct:s,credits:r,projected:s>0?r/(s/100):r,ciLow:s>0?r/(s/100)*.85:r,ciHigh:s>0?r/(s/100)*1.15:r,confidence:a.rows.length>=20?"Medium":"Low",note:`Loaded ${vn(a.rows.length)} rows`}}).sort((n,a)=>n.week.localeCompare(a.week))}function Fo(e){return e.length?[{id:"weekly-projected",label:"Projected weekly credits",color:"#2563eb",points:e.map(t=>({label:t.week,value:t.projected,low:t.ciLow,high:t.ciHigh}))},{id:"loaded-credits",label:"Loaded-row credits",color:"#0f766e",dashed:!0,points:e.map(t=>({label:t.week,value:t.credits}))}]:[]}function Do(e,t){const n=Be(e.rate_limit_secondary_resets_at);return n!==null&&n>0?rn(n*1e3):rn(t)}function rn(e){return new Intl.DateTimeFormat("en-US",{month:"short",day:"numeric"}).format(new Date(e))}function sn(e){return{apiToken:String((e==null?void 0:e.api_token)??""),contextApiEnabled:!!(e!=null&&e.context_api_enabled),fileMode:window.location.protocol==="file:"}}function $o(e){return[{label:"Total Tokens",value:Me(e.totalTokens),detail:`${e.historyScope} history scope`,trend:"loaded aggregate rows",tone:"blue",breakdown:[{label:"Cached",value:Me(e.tokenBreakdown.cachedInput)},{label:"Uncached",value:Me(e.tokenBreakdown.uncachedInput)},{label:"Output",value:Me(e.tokenBreakdown.output)},{label:"Reasoning",value:Me(e.tokenBreakdown.reasoningOutput)}]},{label:"Estimated Cost",value:Wo(e.estimatedCost),detail:"local pricing config",trend:"privacy-safe estimate",tone:"green"},{label:"Cache Hit Rate",value:`${e.cachePct.toFixed(1)}%`,detail:`${Me(e.cachedTokens)} cached input`,trend:e.cachePct>=40?"healthy cache reuse":"cache risk",tone:e.cachePct>=40?"purple":"orange"},{label:"Total Calls",value:vn(e.totalCalls),detail:"loaded calls in this dashboard",trend:"privacy-safe",tone:"blue"},e.usageRemainingCard]}function Io(e){const t=Oo(e.observed_usage);if(t)return t;const n=Uo(e.allowance_windows);return n||{label:"Usage Remaining",value:"Unknown",detail:e.allowance_configured?"allowance configured; no current window":"no observed usage or allowance window",trend:e.allowance_error?`config issue: ${e.allowance_error}`:"not available in payload",tone:e.allowance_error?"red":"orange"}}function Oo(e){const t=Array.isArray(e==null?void 0:e.windows)?e.windows:[];if(!(e!=null&&e.available)||!t.length)return null;const n=gr(t,s=>Oe(s.used_percent)!==null);if(!n)return null;const a=Oe(n.used_percent)??0,r=Pt(100-a);return{label:"Usage Remaining",value:br(r),detail:`${wr(n.label||n.key,"Observed usage")} observed usage`,trend:vr(n.resets_at)||e.source||"observed locally",tone:yr(r)}}function Uo(e){if(!Array.isArray(e)||!e.length)return null;const t=gr(e,o=>{const d=Oe(o.remaining_percent),f=Be(o.remaining_credits),h=Be(o.total_credits);return d!==null||f!==null||h!==null&&h>0});if(!t)return null;const n=Be(t.remaining_credits),a=Be(t.total_credits),r=n!==null&&a!==null&&a>0?n/a*100:null,s=Oe(t.remaining_percent)??r;return{label:"Usage Remaining",value:s!==null?br(Pt(s)):`${oa(n??0)} left`,detail:`${wr(t.label||t.key,"Allowance")} allowance window`,trend:[n===null?"":`${oa(n)} left`,vr(t.reset_at)].filter(Boolean).join(" · ")||"configured allowance",tone:s===null?"green":yr(Pt(s))}}function gr(e,t){const n=e.filter(t);return n.find(Vo)??n[0]??null}function Vo(e){const t=Be(e.window_minutes),n=`${e.key??""} ${e.label??""}`.toLowerCase();return t===10080||/\b(weekly|week|7d|7-day|7 day)\b/.test(n)}function pr(e,t=0){const n=String(e.event_timestamp??e.time??e.turn_timestamp??e.started_at??e.call_started_at??""),a=n,r=String(e.call_started_at??e.started_at??n),s=Number(e.input_tokens??0),i=Number(e.output_tokens??0),o=Number(e.reasoning_output_tokens??0),d=Number(e.cached_input_tokens??0),f=Number(e.cache_hit_ratio??e.cache_ratio??0),h=s>0?d/s*100:f*100,v=Number(e.total_tokens??s+i),u=Number(e.duration_seconds??e.call_duration_seconds??0),p=String(e.previous_call_event_timestamp??""),g=Number(e.previous_call_delta_seconds??0),b=Number(e.uncached_input_tokens??Math.max(s-d,0)),w=String(e.record_id??e.id??`${a||"row"}-${t}`),T=String(e.primary_signal??"").trim(),N=Number(e.line_number),R=Oe(e.context_window_percent),_=Number(e.model_context_window),L=Number(e.cumulative_total_tokens);return{id:w,threadKey:String(e.thread_key??""),rawTime:a,eventTimestamp:n,callStartedAt:r,time:Cr(a),thread:qo(e),model:String(e.model??"unknown"),effort:String(e.effort??"blank"),input:s,output:i,reasoningOutput:o,totalTokens:v,cachedInput:d,uncachedInput:b,cachedPct:h,..._o(e),duration:jt(u),durationSeconds:u,previousCallGap:jt(g),previousCallEventTimestamp:p,previousCallGapSeconds:Number.isFinite(g)?g:0,initiator:String(e.call_initiator??"unknown"),initiatorReason:String(e.call_initiator_reason??""),initiatorConfidence:String(e.call_initiator_confidence??""),...So(e,u,v),usageCreditConfidence:String(e.usage_credit_confidence??"unknown"),usageCreditModel:String(e.usage_credit_model??""),usageCreditSource:String(e.usage_credit_source??""),usageCreditFetchedAt:String(e.usage_credit_fetched_at??""),usageCreditTier:String(e.usage_credit_tier??""),usageCreditNote:String(e.usage_credit_note??""),pricingModel:String(e.pricing_model??""),pricingEstimated:!!e.pricing_estimated,signal:T||(h<25?"cache-risk":"aggregate"),recommendation:String(e.recommended_action??""),tags:h<25?["uncached"]:h>60?["healthy-cache"]:[],sessionId:String(e.session_id??""),turnId:String(e.turn_id??""),parentSessionId:String(e.parent_session_id??""),parentSessionUpdatedAt:String(e.resolved_parent_session_updated_at??e.parent_session_updated_at??""),parentThread:String(e.resolved_parent_thread_name??e.parent_thread_name??""),threadAttachmentLabel:String(e.thread_attachment_label??""),threadSource:String(e.thread_source??""),subagentType:String(e.subagent_type??""),agentRole:String(e.agent_role??""),agentNickname:String(e.agent_nickname??""),project:String(e.project_name??""),projectRelativeCwd:String(e.project_relative_cwd??""),projectTags:Array.isArray(e.project_tags)?e.project_tags.map(y=>String(y)):[],cwd:String(e.cwd??""),sourceFile:String(e.source_file??""),lineNumber:Number.isFinite(N)&&N>0?N:null,gitBranch:String(e.git_branch??""),gitRemoteLabel:String(e.git_remote_label??""),gitRemoteHash:String(e.git_remote_hash??""),contextWindowPct:R,modelContextWindow:Number.isFinite(_)&&_>0?_:null,cumulativeTotalTokens:Number.isFinite(L)&&L>0?L:null,estimatedCacheSavings:Number(e.estimated_cache_savings_usd??0),efficiencyFlags:Array.isArray(e.efficiency_flags)?e.efficiency_flags.map(y=>String(y)):[]}}function Oe(e){const t=Number(e);return Number.isFinite(t)?t<=1?t*100:t:null}function Be(e){const t=Number(e);return Number.isFinite(t)?t:null}function Pt(e){return Math.max(0,Math.min(100,e))}function br(e){const t=Math.abs(e)>=10?0:1;return`${e.toFixed(t)}%`}function oa(e){return`${Me(e)} cr`}function vr(e){if(e==null||e==="")return"";const t=typeof e=="number"?e*1e3:Date.parse(e);return Number.isFinite(t)?`resets ${Ko(t)}`:""}function Ko(e){const t=new Date(e);return Number.isNaN(t.getTime())?String(e):`${t.toISOString().slice(0,16).replace("T"," ")} UTC`}function wr(e,t){return(e==null?void 0:e.trim())||t}function yr(e){return e<20?"red":e<40?"orange":"green"}function _r(e){return typeof e.include_archived=="boolean"?e.include_archived:e.history_scope==="all-history"||e.history_scope==="all"}function qo(e){const t=e.thread_name??e.thread??e.resolved_parent_thread_name??e.parent_thread_name;if(t&&String(t).trim())return String(t);const n=e.project_name??e.project_relative_cwd,a=e.session_id?e.session_id.slice(-6):"";return n&&a?`${n} ${a}`:e.thread_attachment_label?String(e.thread_attachment_label):"Untitled thread"}function Sr(e){const t=new Map;for(const n of e)t.set(n.thread,[...t.get(n.thread)??[],n]);return[...t.entries()].map(([n,a])=>{const r=a.length,i=[...a].sort((y,k)=>ca(k)-ca(y))[0]??null,o=(i==null?void 0:i.rawTime)||(i==null?void 0:i.time)||"",d=a.reduce((y,k)=>y+k.totalTokens,0),f=a.reduce((y,k)=>y+k.cachedInput,0),h=a.reduce((y,k)=>y+k.uncachedInput,0),v=a.reduce((y,k)=>y+k.output,0),u=a.reduce((y,k)=>y+k.reasoningOutput,0),p=a.reduce((y,k)=>y+k.cost,0),g=a.reduce((y,k)=>y+k.credits,0),b=a.reduce((y,k)=>y+k.durationSeconds,0),w=a.filter(y=>y.previousCallGapSeconds>0),T=w.reduce((y,k)=>y+k.previousCallGapSeconds,0)/Math.max(w.length,1),N=f+h,R=N>0?f/N*100:a.reduce((y,k)=>y+k.cachedPct,0)/Math.max(r,1),_=a.map(y=>y.contextWindowPct).filter(y=>typeof y=="number"&&Number.isFinite(y)),L=R<25?"High":R<45?"Medium":"Low";return{name:n,latestCallId:(i==null?void 0:i.id)??"",latestActivity:Cr(o),latestActivityRaw:o,turns:r,totalDurationSeconds:b,totalDuration:jt(b),averageGapSeconds:T,averageGap:jt(T),initiatorSummary:Qt(a.map(y=>y.initiator)),modelSummary:Qt(a.map(y=>y.model)),effortSummary:Qt(a.map(y=>y.effort)),totalTokens:d,cachedInput:f,uncachedInput:h,outputTokens:v,reasoningOutput:u,cost:p,credits:g,cachePct:R,contextPct:_.length?Math.max(..._):null,costPerCall:p/Math.max(r,1),coldResumeRisk:L,productivity:Math.max(20,Math.round(R-p/Math.max(r,1)*4))}}).sort((n,a)=>a.cost-n.cost).slice(0,20)}function ca(e){const t=Date.parse(e.rawTime||e.time);return Number.isFinite(t)?t:0}function Qt(e,t=3){const n=new Map;for(const a of e){const r=a.trim()||"unknown";n.set(r,(n.get(r)??0)+1)}return[...n.entries()].sort((a,r)=>r[1]-a[1]||a[0].localeCompare(r[0])).slice(0,t).map(([a,r])=>`${a} x${vn(r)}`).join(", ")||"unknown"}function Te(e,t){return e.reduce((n,a)=>n+t(a),0)}async function bn(e,t){let n;try{n=await e.json()}catch(a){if(e.ok)throw new Error(`${t} response could not be read as JSON: ${Ho(a)}`);n={}}if(!e.ok){const a=typeof n.error=="string"?n.error:`${t} request failed (${e.status})`;throw new Error(a)}return n}function Ho(e){return e instanceof Error?e.message:String(e)}function vn(e){return new Intl.NumberFormat("en-US").format(Math.round(e))}function Me(e){return new Intl.NumberFormat("en-US",{notation:"compact",maximumFractionDigits:2}).format(e)}function Wo(e){return new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",minimumFractionDigits:2,maximumFractionDigits:2}).format(e)}function jt(e){if(!Number.isFinite(e)||e<=0)return"-";if(e<60)return`${e.toFixed(0)}s`;const t=Math.floor(e/60),n=Math.round(e%60);return`${t}m ${n}s`}function Cr(e){const t=new Date(e);return Number.isNaN(t.getTime())?e||"-":new Intl.DateTimeFormat("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit"}).format(t)}function wn(e){return Date.parse(String(e.started_at??e.call_started_at??e.time??e.event_timestamp??e.turn_timestamp??""))}const Pe=1440*60*1e3;function Bo(e,t,n=window.location.search){const a=xr(n);if(!a.active)return e;const r=e.calls.filter(s=>Qo(s,a));return r.length===e.calls.length?e:Yo(e,r,`${t} filtered`)}function xr(e){var d,f,h,v,u,p;const t=new URLSearchParams(e),n=((d=t.get("model"))==null?void 0:d.trim())??"",a=((f=t.get("effort"))==null?void 0:f.trim())??"",r=((h=t.get("confidence")||t.get("pricing"))==null?void 0:h.trim())??"",s=((v=t.get("date")||t.get("time"))==null?void 0:v.trim())??"",i=((u=t.get("from"))==null?void 0:u.trim())??"",o=((p=t.get("to"))==null?void 0:p.trim())??"";return{model:n,effort:a,confidence:r,datePreset:s,dateStart:i,dateEnd:o,active:!!(n||a||r||s||i||o)}}function Qo(e,t){return!(t.model&&e.model!==t.model||t.effort&&e.effort!==t.effort||t.confidence&&!zo(e,t.confidence)||!Go(e,t))}function zo(e,t){return t==="official"||t==="cost-exact"?!!(e.pricingModel&&!e.pricingEstimated):t==="estimated"||t==="cost-estimated"?e.pricingEstimated:t==="unpriced"||t==="cost-unpriced"?!e.pricingModel:t==="credit-exact"?e.usageCreditConfidence==="exact":t==="credit-estimated"?e.usageCreditConfidence==="estimated":t==="credit-override"?e.usageCreditConfidence==="user_override":t==="credit-missing"?e.usageCreditConfidence==="missing"||e.usageCreditConfidence==="unknown":!0}function Go(e,t){const n=Jo(t);if(!n.active)return!0;if(n.invalid)return!1;const a=Date.parse(e.rawTime||e.time);return!(!Number.isFinite(a)||n.start!==null&&a=n.endExclusive)}function Jo(e){if(e.datePreset&&e.datePreset!=="all"&&e.datePreset!=="custom"){const a=Zo(e.datePreset);return a?{active:!0,invalid:!1,...a}:{active:!1,invalid:!1,start:null,endExclusive:null}}const t=la(e.dateStart),n=la(e.dateEnd);return t!==null&&n!==null&&t>n?{active:!0,invalid:!0,start:t,endExclusive:n+Pe}:{active:t!==null||n!==null,invalid:!1,start:t,endExclusive:n===null?null:n+Pe}}function Zo(e){const t=Xo(new Date);if(e==="today")return{start:t,endExclusive:t+Pe};if(e==="last-7-days")return{start:t-6*Pe,endExclusive:t+Pe};if(e==="this-week"){const a=new Date(t).getDay(),r=a===0?-6:1-a,s=t+r*Pe;return{start:s,endExclusive:s+7*Pe}}if(e==="this-month"){const n=new Date(t),a=new Date(n.getFullYear(),n.getMonth(),1).getTime(),r=new Date(n.getFullYear(),n.getMonth()+1,1).getTime();return{start:a,endExclusive:r}}return null}function la(e){if(!/^\d{4}-\d{2}-\d{2}$/.test(e))return null;const[t,n,a]=e.split("-").map(Number),r=new Date(t,n-1,a);return r.getFullYear()!==t||r.getMonth()!==n-1||r.getDate()!==a?null:r.getTime()}function Xo(e){return new Date(e.getFullYear(),e.getMonth(),e.getDate()).getTime()}function Yo(e,t,n="filtered"){const a=t.reduce((u,p)=>u+p.totalTokens,0),r=t.reduce((u,p)=>u+p.cost,0),s=t.reduce((u,p)=>u+p.cachedInput,0),i=t.reduce((u,p)=>u+p.uncachedInput,0),o=t.reduce((u,p)=>u+p.output,0),d=t.reduce((u,p)=>u+p.reasoningOutput,0),f=t.reduce((u,p)=>u+p.input,0),h=f>0?s/f*100:0,v=e.cards.find(u=>u.label==="Usage Remaining")??{label:"Usage Remaining",value:"Unknown",detail:"not available in filtered view",trend:"not available in payload",tone:"orange"};return{...e,cards:ec({cachePct:h,cachedTokens:s,estimatedCost:r,historyScope:n,totalCalls:t.length,totalTokens:a,tokenBreakdown:{cachedInput:s,uncachedInput:i,output:o,reasoningOutput:d},usageRemainingCard:v}),...tc(t),calls:t,threads:Sr(t),findings:lr(t),modelCosts:cr(t),reports:ur(t),cacheSegments:[{label:"Cache read",value:h,color:"#2563eb"},{label:"Uncached input",value:Math.max(100-h,0),color:"#7c3aed"}]}}function ec({cachePct:e,cachedTokens:t,estimatedCost:n,historyScope:a,totalCalls:r,totalTokens:s,tokenBreakdown:i,usageRemainingCard:o}){return[{label:"Total Tokens",value:We(s),detail:`${a} history scope`,trend:"filtered aggregate rows",tone:"blue",breakdown:[{label:"Cached",value:We(i.cachedInput)},{label:"Uncached",value:We(i.uncachedInput)},{label:"Output",value:We(i.output)},{label:"Reasoning",value:We(i.reasoningOutput)}]},{label:"Estimated Cost",value:ac(n),detail:"local pricing config",trend:"privacy-safe estimate",tone:"green"},{label:"Cache Hit Rate",value:`${e.toFixed(1)}%`,detail:`${We(t)} cached input`,trend:e>=40?"healthy cache reuse":"cache risk",tone:e>=40?"purple":"orange"},{label:"Total Calls",value:nc(r),detail:"loaded calls matching filters",trend:"legacy shell filters",tone:"blue"},o]}function tc(e){return dr(e.map(t=>({timestamp:Date.parse(t.rawTime||t.time),cached:t.cachedInput,cost:t.cost,input:t.input,output:t.output})))}function We(e){return new Intl.NumberFormat("en-US",{notation:"compact",maximumFractionDigits:2}).format(e)}function nc(e){return new Intl.NumberFormat("en-US").format(Math.round(e))}function ac(e){return new Intl.NumberFormat("en-US",{currency:"USD",maximumFractionDigits:2,minimumFractionDigits:2,style:"currency"}).format(e)}const kr=[{id:"overview",label:"Overview",description:"High-level telemetry",icon:$s},{id:"investigator",label:"Investigate",description:"Root-cause evidence",icon:Ds},{id:"compression-lab",label:"Compression Lab",description:"Context savings",icon:Ms},{id:"calls",label:"Calls",description:"Model-call table",icon:Vs},{id:"threads",label:"Threads",description:"Thread efficiency",icon:Hs},{id:"usage-drain",label:"Limits",description:"Allowance intelligence",icon:qs},{id:"cache-context",label:"Cache And Context",description:"Cache and cold resumes",icon:As},{id:"diagnostics",label:"Diagnostics Notebook",description:"Technical report",icon:Ls},{id:"reports",label:"Reports",description:"Generated analyses",icon:Ps},{id:"settings",label:"Settings",description:"Local configuration",icon:Os}],Tr=[{label:"Files",icon:Es,target:"settings"},{label:"Commands",icon:ks,target:"investigator"},{label:"Models",icon:Rs,target:"calls"}];function Lr(e){return _s(e)}const rc=[{value:"day",label:"24 hours",ariaLabel:"Last 24h"},{value:"week",label:"7 days",ariaLabel:"Last 7 days"},{value:"rows",label:"Recent",ariaLabel:"Recent rows"},{value:"all",label:"All time",ariaLabel:"All time"}];function sc(e){const t=e.refreshing||!e.canUseLiveApi,n=e.loadWindow==="rows",a=`${e.loadedRowCount.toLocaleString()} detail row${e.loadedRowCount===1?"":"s"} cached`,r=n?`${e.loadedRowCount.toLocaleString()} loaded / ${e.totalAvailableRows.toLocaleString()} total`:`${e.totalAvailableRows.toLocaleString()} calls analyzed · ${a}`,s=n?`${e.loadedRowCount.toLocaleString()} of ${e.totalAvailableRows.toLocaleString()} evidence rows loaded`:`Focused pages analyze all ${e.totalAvailableRows.toLocaleString()} calls in scope; ${e.loadedRowCount.toLocaleString()} call rows are cached for immediate detail views.`,i=n?e.rowLoadStatus:`${e.rowLoadModeLabel} analysis uses ${e.totalAvailableRows.toLocaleString()} calls; ${a}`;return c.jsxs("section",{className:"data-window-control","aria-label":"Analysis scope",children:[c.jsxs("div",{className:"data-window-summary",children:[c.jsx("span",{children:"Analysis scope"}),c.jsx("strong",{children:e.rowLoadModeLabel}),c.jsx("small",{title:s,children:r})]}),c.jsx("div",{className:"data-window-options",role:"group","aria-label":"Choose loaded call window",children:rc.map(o=>c.jsx("button",{type:"button","aria-label":o.ariaLabel,"aria-pressed":e.loadWindow===o.value,disabled:t,onClick:()=>e.onWindowChange(o.value),children:o.label},o.value))}),e.loadWindow==="rows"?c.jsxs("div",{className:"row-window-editor",children:[c.jsx("input",{"aria-label":"Rows to load slider","aria-valuetext":`${e.finitePendingLoadLimit.toLocaleString()} recent rows`,type:"range",min:1,max:e.rowLimitSliderMax,step:100,value:e.rowLimitSliderValue,onChange:o=>e.onSliderChange(o.target.value),disabled:t}),c.jsx("input",{"aria-label":"Rows to load",type:"number",min:1,step:100,value:e.pendingLoadLimit,onChange:o=>e.onDraftChange(o.target.value),disabled:t}),c.jsx("button",{type:"button","data-primary":"true",onClick:e.onApply,disabled:t||!e.rowLimitChanged,children:e.loadLabel}),c.jsx("button",{type:"button",onClick:e.onLoadMore,disabled:t||!e.hasMoreRows,children:e.loadMoreLabel})]}):null,e.refreshing?c.jsxs("div",{className:"row-load-progress","aria-label":"Row loading progress",children:[c.jsx("div",{className:"row-load-progress-track",role:"progressbar","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":e.refreshProgressPercent??void 0,children:c.jsx("span",{style:{width:`${e.refreshProgressPercent??8}%`}})}),c.jsxs("span",{children:[e.refreshProgressPercent===null?e.refreshProgressText:`${Math.round(e.refreshProgressPercent)}% loaded`,c.jsx("button",{className:"icon-button",type:"button",onClick:()=>void e.onCancel(),"aria-label":"Cancel refresh",title:"Cancel refresh",children:c.jsx(dn,{size:13})})]})]}):null,c.jsx("span",{className:"sr-only",role:"status","aria-live":"polite",children:i})]})}const yn=[{value:"all",label:"All time"},{value:"today",label:"Today"},{value:"this-week",label:"This week"},{value:"last-7-days",label:"Last 7 days"},{value:"this-month",label:"This month"},{value:"custom",label:"Custom"}],Rr=[{value:"all",label:"All confidence"},{value:"cost-exact",label:"Exact cost"},{value:"cost-estimated",label:"Estimated cost"},{value:"cost-unpriced",label:"Unpriced cost"},{value:"credit-exact",label:"Exact credit"},{value:"credit-estimated",label:"Estimated credit"},{value:"credit-override",label:"Credit override"},{value:"credit-missing",label:"Missing credit"}];function ic({activeView:e,locationSearch:t,model:n,onUrlChange:a}){const r=xr(t),s=C.useMemo(()=>ua(n.calls.map(g=>g.model)),[n.calls]),i=C.useMemo(()=>ua(n.calls.map(g=>g.effort)),[n.calls]),o=r.dateStart||r.dateEnd?"custom":oc(r.datePreset),d=lc(r,o);if(e==="calls"||e==="call"||!n.calls.length)return null;function f(g){const b=new URL(window.location.href);g(b),a(b)}function h(g,b){f(w=>{g==="confidence"&&w.searchParams.delete("pricing"),!b||b==="all"?w.searchParams.delete(g):w.searchParams.set(g,b)})}function v(g){f(b=>{if(!g||g==="all"){b.searchParams.delete("date"),b.searchParams.delete("time"),b.searchParams.delete("from"),b.searchParams.delete("to");return}b.searchParams.set("date",g),b.searchParams.set("time",g),g!=="custom"&&(b.searchParams.delete("from"),b.searchParams.delete("to"))})}function u(g,b){f(w=>{b?(w.searchParams.set(g,b),w.searchParams.set("date","custom"),w.searchParams.set("time","custom")):(w.searchParams.delete(g),!w.searchParams.get("from")&&!w.searchParams.get("to")&&(w.searchParams.delete("date"),w.searchParams.delete("time")))})}function p(){f(g=>{for(const b of["model","effort","confidence","pricing","date","time","from","to"])g.searchParams.delete(b)})}return c.jsxs("section",{className:"global-filter-strip span-all","aria-label":"Dashboard filters",children:[c.jsxs("strong",{children:[c.jsx(Fs,{size:15}),"Filters"]}),c.jsxs("label",{children:[c.jsx("span",{children:"Model"}),c.jsxs("select",{"aria-label":"Global model filter",value:r.model||"all",onChange:g=>h("model",g.target.value),children:[c.jsx("option",{value:"all",children:"All models"}),s.map(g=>c.jsx("option",{value:g,children:g},g))]})]}),c.jsxs("label",{children:[c.jsx("span",{children:"Effort"}),c.jsxs("select",{"aria-label":"Global effort filter",value:r.effort||"all",onChange:g=>h("effort",g.target.value),children:[c.jsx("option",{value:"all",children:"All effort"}),i.map(g=>c.jsx("option",{value:g,children:g},g))]})]}),c.jsxs("label",{children:[c.jsx("span",{children:"Confidence"}),c.jsx("select",{"aria-label":"Global confidence filter",value:cc(r.confidence),onChange:g=>h("confidence",g.target.value),children:Rr.map(g=>c.jsx("option",{value:g.value,children:g.label},g.value))})]}),c.jsxs("label",{children:[c.jsx("span",{children:"Time"}),c.jsx("select",{"aria-label":"Global time filter",value:o,onChange:g=>v(g.target.value),children:yn.map(g=>c.jsx("option",{value:g.value,children:g.label},g.value))}),d?c.jsx("span",{className:"filter-status","data-state":d.state,"aria-live":"polite",children:d.label}):null]}),c.jsxs("label",{children:[c.jsx("span",{children:"Start"}),c.jsx("input",{"aria-label":"Global start date",type:"date",value:r.dateStart,onChange:g=>u("from",g.target.value)})]}),c.jsxs("label",{children:[c.jsx("span",{children:"End"}),c.jsx("input",{"aria-label":"Global end date",type:"date",value:r.dateEnd,onChange:g=>u("to",g.target.value)})]}),c.jsxs("button",{className:"toolbar-button",type:"button",disabled:!r.active,onClick:p,children:[c.jsx(dn,{size:15}),"Clear filters"]})]})}function ua(e){return Array.from(new Set(e.map(t=>t.trim()).filter(Boolean))).sort((t,n)=>t.localeCompare(n))}function oc(e){return yn.some(t=>t.value===e)?e:"all"}function cc(e){return e==="official"?"cost-exact":e==="estimated"?"cost-estimated":e==="unpriced"?"cost-unpriced":Rr.some(t=>t.value===e)?e:"all"}function lc(e,t){var r;const n=ha(e.dateStart),a=ha(e.dateEnd);if(n!==null&&a!==null&&n>a)return{label:"Invalid date range",state:"error"};if(t==="custom"&&(e.dateStart||e.dateEnd))return{label:uc(e.dateStart,e.dateEnd),state:"active"};if(t!=="all"){const s=((r=yn.find(o=>o.value===t))==null?void 0:r.label)??t,i=dc(t);return{label:i?`${s}: ${da(i.start)} to ${da(Qe(i.endExclusive,-1))}`:s,state:"active"}}return null}function uc(e,t){return e&&t?`Custom: ${e} to ${t}`:e?`Custom: from ${e}`:t?`Custom: through ${t}`:"Custom range"}function dc(e){const t=hc();if(e==="today")return{start:t,endExclusive:Qe(t,1)};if(e==="this-week"){const n=fc(t);return{start:n,endExclusive:Qe(n,7)}}return e==="last-7-days"?{start:Qe(t,-6),endExclusive:Qe(t,1)}:e==="this-month"?{start:new Date(t.getFullYear(),t.getMonth(),1),endExclusive:new Date(t.getFullYear(),t.getMonth()+1,1)}:null}function hc(e=new Date){return new Date(e.getFullYear(),e.getMonth(),e.getDate())}function fc(e){const t=e.getDay();return Qe(e,t===0?-6:1-t)}function Qe(e,t){return new Date(e.getFullYear(),e.getMonth(),e.getDate()+t)}function da(e){const t=String(e.getMonth()+1).padStart(2,"0"),n=String(e.getDate()).padStart(2,"0");return`${e.getFullYear()}-${t}-${n}`}function ha(e){if(!/^\d{4}-\d{2}-\d{2}$/.test(e))return null;const[t,n,a]=e.split("-").map(Number),r=new Date(t,n-1,a);return r.getFullYear()!==t||r.getMonth()!==n-1||r.getDate()!==a?null:r.getTime()}function mc(e){return Lr(e)&&e!=="call"}function on(e,t="overview"){return e==="insights"?"overview":Lr(e)?e:t}function fa(e=window.location.search,t="calls"){const n=new URLSearchParams(e).get("return"),a=on(n,t);return mc(a)?a:t}function ma(e=window.location.search){return new URLSearchParams(e).has("return")}function gc(e){var t,n;return((t=kr.find(a=>a.id===e))==null?void 0:t.label)??((n=Tr.find(a=>a.target===e))==null?void 0:n.label)??"Calls"}function ga(e,t=window.location.search){return new URLSearchParams(t).get("history")==="all"?"all":e}function pa(e,t=window.location.href){const n=new URL(t);return e==="all"?n.searchParams.set("history","all"):n.searchParams.delete("history"),n}const pc={investigator:["finding"],threads:["thread","expand","threads","thread_q","risk","thread_call_sort","thread_call_page"],"cache-context":["cache_thread"],reports:["report"],"usage-drain":["usage_plan","usage_effort","usage_subagents","usage_sample","usage_confidence","limit_window","limit_hypothesis"],diagnostics:["diagnostic_source","diagnostic_fact"],calls:["explore","detail","call_q","source","sort","direction","density","page"],call:["record","return","mode","max_entries","max_chars","include_tool_output","include_compaction_history"]};function kt(e,t,n=[]){const a=new Set([t,...Array.isArray(n)?n:[n]]);for(const[r,s]of Object.entries(pc))if(!a.has(r))for(const i of s)e.searchParams.delete(i)}function ba(e){let t=!1;return e.searchParams.get("view")==="insights"&&(e.searchParams.set("view","overview"),t=!0),e.searchParams.get("return")==="insights"&&(e.searchParams.set("return","overview"),t=!0),t}const Mr=[{key:"overview",title:"Overview",path:"/api/diagnostics/overview",refreshPath:"/api/diagnostics/overview/refresh"},{key:"toolOutput",title:"Tool Output",path:"/api/diagnostics/tool-output",refreshPath:"/api/diagnostics/tool-output/refresh"},{key:"commands",title:"Commands",path:"/api/diagnostics/commands",refreshPath:"/api/diagnostics/commands/refresh"},{key:"gitInteractions",title:"Git Interactions",path:"/api/diagnostics/git-interactions",refreshPath:"/api/diagnostics/git-interactions/refresh"},{key:"fileReads",title:"File Reads",path:"/api/diagnostics/file-reads",refreshPath:"/api/diagnostics/file-reads/refresh"},{key:"fileModifications",title:"File Modifications",path:"/api/diagnostics/file-modifications",refreshPath:"/api/diagnostics/file-modifications/refresh"},{key:"readProductivity",title:"Read Productivity",path:"/api/diagnostics/read-productivity",refreshPath:"/api/diagnostics/read-productivity/refresh"},{key:"concentration",title:"Concentration",path:"/api/diagnostics/concentration",refreshPath:"/api/diagnostics/concentration/refresh"},{key:"guidedSummary",title:"What Is Driving Usage?",path:"/api/diagnostics/guided-summary",refreshPath:"/api/diagnostics/guided-summary/refresh"},{key:"usageDrain",title:"Usage Drain",path:"/api/diagnostics/usage-drain",refreshPath:"/api/diagnostics/usage-drain/refresh"}],At=new Map,Ft=new Map;function bc(){At.clear(),Ft.clear()}async function eu(e,t,n={}){Cn(t);const a=Sc(e);return n.signal?Nt(a,t,n.signal):_c(Ft,Sn(e,t,n.cacheKey),()=>Nt(a,t))}async function tu(e,t={}){Cn(e);const n=await fetch(`/api/diagnostics/refresh?_=${Date.now()}`,{method:"POST",headers:{Accept:"application/json","X-Codex-Usage-Token":e.apiToken},cache:"no-store",signal:t.signal}),a=await Dt(n,"Diagnostic snapshot refresh"),r=a.sections??(jr(a)?await vc(e,await Pr(a,e,t),t.signal):{});At.set(_n(e),r);for(const[s,i]of Object.entries(r))Ft.set(Sn(s,e),i);return r}async function nu(e,t,n={}){Cn(t);const a=await fetch(`${e.refreshPath}?_=${Date.now()}`,{method:"POST",headers:{Accept:"application/json","X-Codex-Usage-Token":t.apiToken},cache:"no-store",signal:n.signal}),r=await Dt(a,e.title),s=jr(r)?await wc(t,e,await Pr(r,t,n),n.signal):r,i=_n(t),o=At.get(i),d=o?await Promise.resolve(o):{};return At.set(i,{...d,[e.key]:s}),Ft.set(Sn(e.key,t),s),s}async function Pr(e,t,n){var r,s,i,o;let a=e;for((r=n.onProgress)==null||r.call(n,a);a.status==="pending"||a.status==="running";){const d=n.pollIntervalMs??((s=a.next)==null?void 0:s.poll_after_ms)??250;await yc(d,n.signal);const f=new URLSearchParams({job_id:a.job_id,_:String(Date.now())}),h=await fetch(`/api/diagnostics/refresh/status?${f.toString()}`,{headers:{Accept:"application/json","X-Codex-Usage-Token":t.apiToken},cache:"no-store",signal:n.signal});a=await Dt(h,"Diagnostic refresh status"),(i=n.onProgress)==null||i.call(n,a)}if(a.status!=="completed")throw new Error(`Diagnostic refresh failed: ${((o=a.error)==null?void 0:o.code)??a.status}`);return a}async function vc(e,t,n){const a=await Promise.all(Mr.map(async r=>[r.key,await Nt(r,e,n)]));return Object.fromEntries(a)}async function wc(e,t,n,a){return Nt(t,e,a)}function jr(e){if(!e||typeof e!="object")return!1;const t=e;return t.schema==="codex-usage-tracker-analysis-job-v1"&&t.job_kind==="diagnostic-refresh"&&typeof t.job_id=="string"}function yc(e,t){return t!=null&&t.aborted?Promise.reject(t.reason??new DOMException("Aborted","AbortError")):e<=0?Promise.resolve():new Promise((n,a)=>{const r=()=>{window.clearTimeout(s),t==null||t.removeEventListener("abort",r),a((t==null?void 0:t.reason)??new DOMException("Aborted","AbortError"))},s=window.setTimeout(()=>{t==null||t.removeEventListener("abort",r),n()},e);t==null||t.addEventListener("abort",r,{once:!0})})}function _c(e,t,n){const a=e.get(t);if(a!==void 0)return Promise.resolve(a);const r=n().then(s=>(e.set(t,s),s)).catch(s=>{throw e.delete(t),s});return e.set(t,r),r}function _n(e){return`${e.fileMode?"file":"live"}:${e.apiToken}`}function Sn(e,t,n=""){return`snapshot:${_n(t)}:${n}:${e}`}function Sc(e){const t=Mr.find(n=>n.key===e);if(!t)throw new Error(`Unknown diagnostic snapshot: ${e}`);return t}async function Nt(e,t,n){const a=await fetch(`${e.path}?_=${Date.now()}`,{headers:{Accept:"application/json","X-Codex-Usage-Token":t.apiToken},cache:"no-store",signal:n});return await Dt(a,e.title)}function Cn(e){if(e.fileMode)throw new Error("Diagnostic facts require localhost dashboard server.");if(!e.apiToken)throw new Error("Diagnostic facts require localhost dashboard API token.")}async function Dt(e,t){if(!e.ok)throw new Error(`${t} request failed with HTTP ${e.status}`);const n=await e.json();if(typeof n.error=="string"&&n.error)throw new Error(n.error);return n}const va=[{key:"facts",label:"Top Facts",title:"Top Diagnostic Facts",path:"/api/diagnostics/facts",limit:50},{key:"tools",label:"Tools",title:"Tool and Function Activity",path:"/api/diagnostics/tools",limit:25},{key:"compactions",label:"Compactions",title:"Compaction Activity",path:"/api/diagnostics/compactions",limit:25}],Ar=new Map,Nr=new Map;function Cc(){bc(),Ar.clear(),Nr.clear()}async function au(e,t,n={}){$r(t);const a=va.find(f=>f.key===e)??va[0],r=kc(n.sort??"uncached"),s=n.direction??"desc",i=Math.max(1,Math.round(n.limit??a.limit)),o=Math.max(0,Math.round(n.offset??0)),d=async()=>{const f=new URLSearchParams({limit:String(i),offset:String(o),sort:r,direction:s});Dr(f,"include_archived",n.includeArchived);const h=await fetch(`${a.path}?${f.toString()}`,{headers:{Accept:"application/json","X-Codex-Usage-Token":t.apiToken},cache:"no-store",signal:n.signal});return await Ir(h,a.title)};return n.signal?d():Er(Ar,xc(a.key,t,i,o,r,s,n.cacheKey,n.includeArchived),d)}async function ru(e,t,n={}){$r(t);const a=Math.max(1,Math.round(n.limit??8)),r=Math.max(0,Math.round(n.offset??0)),s=n.sort??"tokens",i=n.direction??"desc",o=async()=>{const d=String(e.fact_type??""),f=String(e.fact_name??"");if(!d||!f)throw new Error("Diagnostic fact type and name are required.");const h=new URLSearchParams({fact_type:d,fact_name:f,limit:String(a),offset:String(r),sort:s,direction:i});Dr(h,"include_archived",n.includeArchived);const v=await fetch(`/api/diagnostics/fact-calls?${h.toString()}`,{headers:{Accept:"application/json","X-Codex-Usage-Token":t.apiToken},cache:"no-store",signal:n.signal}),u=await Ir(v,"Diagnostic fact calls");return{calls:(u.rows??[]).map((p,g)=>pr(p,g)),rawPayload:u}};return n.signal?o():Er(Nr,Tc(e,t,a,r,s,i,n.cacheKey,n.includeArchived),o)}function Er(e,t,n){const a=e.get(t);if(a!==void 0)return Promise.resolve(a);const r=n().then(s=>(e.set(t,s),s)).catch(s=>{throw e.delete(t),s});return e.set(t,r),r}function Fr(e){return`${e.fileMode?"file":"live"}:${e.apiToken}`}function xc(e,t,n,a,r,s,i="",o){return["facts",e,Fr(t),i,o??"default",n,a,r,s].join(":")}function kc(e){return e==="total"?"tokens":e==="latest"?"time":e}function Tc(e,t,n,a,r,s,i="",o){return["fact-calls",Fr(t),i,o??"default",String(e.fact_type??""),String(e.fact_name??""),n,a,r,s].join(":")}function Dr(e,t,n){n!==void 0&&e.set(t,String(n))}function $r(e){if(e.fileMode)throw new Error("Diagnostic facts require localhost dashboard server.");if(!e.apiToken)throw new Error("Diagnostic facts require localhost dashboard API token.")}async function Ir(e,t){if(!e.ok)throw new Error(`${t} request failed with HTTP ${e.status}`);const n=await e.json();if(n.error)throw new Error(n.error);return n}const Lc=[{id:"usage-snapshot",endpoint:"/api/usage",dataClass:"snapshot",schema:"codex-usage-tracker-dashboard-v1"},{id:"overview-summary",endpoint:"/api/summary",dataClass:"aggregate",schema:"codex-usage-tracker-summary-v1"},{id:"overview-recommendations",endpoint:"/api/recommendations",dataClass:"aggregate",schema:"codex-usage-tracker-recommendations-v1"},{id:"calls",endpoint:"/api/calls",dataClass:"aggregate",schema:"codex-usage-tracker-calls-v1"},{id:"threads",endpoint:"/api/threads",dataClass:"aggregate",schema:"codex-usage-tracker-threads-v1"},{id:"thread-calls",endpoint:"/api/thread-calls",dataClass:"detail",schema:"codex-usage-tracker-thread-calls-v1"},{id:"investigator-agentic",endpoint:"/api/investigations/agentic",dataClass:"aggregate",schema:"codex-usage-tracker-agentic-investigation-v1"},{id:"investigator-walk",endpoint:"/api/investigations/walk",dataClass:"userAction",schema:"codex-usage-tracker-investigation-walk-v1"},{id:"diagnostics-facts",endpoint:"/api/diagnostics/facts",dataClass:"aggregate",schema:null},{id:"diagnostics-fact-calls",endpoint:"/api/diagnostics/fact-calls",dataClass:"detail",schema:null},{id:"diagnostics-dedupe",endpoint:"/api/diagnostics/dedupe",dataClass:"aggregate",schema:"codex-usage-tracker-dedupe-diagnostics-v1"},{id:"diagnostics-snapshot",endpoint:"/api/diagnostics/{snapshot}",dataClass:"aggregate",schema:null},{id:"compression-profile",endpoint:"/api/compression/profile",dataClass:"aggregate",schema:"codex-usage-tracker-compression-api-v1"},{id:"reports",endpoint:"/api/reports/pack",dataClass:"aggregate",schema:"codex-usage-tracker-reports-pack-v1"}],Rc=Lc,Mc=new Map(Rc.map(e=>[e.id,e])),Pc=15*6e4,jc=3e4,Or={snapshot:ot({persistedCache:"aggregate-only"}),aggregate:ot({persistedCache:"aggregate-only"}),detail:ot(),heavyJob:ot({cancellation:"shared-job",staleTime:1e3}),userAction:ot({retry:0,staleTime:0})};function Ac({sourceKey:e,sourceRevision:t}){return{sourceKey:wa(e,"local-api"),sourceRevision:wa(t,"unversioned")}}function Nc(e,t,n={},...a){return["dashboard",e.id,t.sourceKey,t.sourceRevision,Dc(n),...a]}function Ec(e){return["dashboard",e.id]}function Fc(e){const t=Mc.get(e);if(!t)throw new Error(`Unknown dashboard query definition: ${e}`);return t}function Ur(e){const t=Or[e];return{gcTime:t.gcTime,refetchOnReconnect:t.refetchOnReconnect,refetchOnWindowFocus:t.refetchOnWindowFocus,retry:t.retry,staleTime:t.staleTime}}function su({enabled:e,hasData:t,isError:n,isFetching:a,isPending:r}){return e?t?a?"updating":"ready":n?"error":a||r?"loading":"waiting":"waiting"}function iu(e){const t=e.filter(a=>a==="ready"||a==="updating").length,n=e.length;return{ready:t,total:n,percent:n===0?100:Math.round(t/n*100),loading:e.filter(a=>a==="loading").length,errors:e.filter(a=>a==="error").length}}function Dc(e){return{historyScope:e.historyScope??"active",loadWindow:e.loadWindow??"all",limit:e.limit??null,since:e.since??""}}function wa(e,t){return(e==null?void 0:e.trim())||t}function ot(e={}){return{staleTime:jc,gcTime:Pc,retry:1,refetchOnReconnect:!1,refetchOnWindowFocus:!1,cancellation:"observer",persistedCache:"none",...e}}const $c=new Set(["command_text","content_fragment","content_fragments","excerpt","indexed_content","indexed_fragment","indexed_fragments","prompt","raw_context","raw_output","snippet","tool_output"]),Ic=new Set(["includes_indexed_content","includes_raw_fragment","includes_raw_fragments","indexed_content_included","raw_content_included","raw_context_included"]),Oc=new Set(["codex-usage-tracker-context-v1"]);function ya(e){return cn(e,new WeakSet)}function cn(e,t){if(!e||typeof e!="object")return!0;if(t.has(e))return!1;t.add(e);const n=Array.isArray(e)?e.every(a=>cn(a,t)):Object.entries(e).every(([a,r])=>{const s=a.toLowerCase();return $c.has(s)||Ic.has(s)&&r===!0||s==="schema"&&typeof r=="string"&&Oc.has(r)?!1:cn(r,t)});return t.delete(e),n}const Uc="codexUsageDashboard",Vc=1,Y="usageSnapshots",Kc=6,qc=10080*60*1e3,Hc={async read(e){if(!e.sourceRevision)return null;try{const t=await Sa();if(!t)return null;const n=await Vr(t.transaction(Y,"readonly").objectStore(Y).get(_a(e)));return!n||n.sourceRevision!==e.sourceRevision||Date.now()-n.storedAt>qc?null:ya(n.payload)?n.payload:(await Wc(t,n.cacheKey),null)}catch{return null}},async write(e,t){if(!(!e.sourceRevision||!ya(t)))try{const n=await Sa();if(!n)return;const a=n.transaction(Y,"readwrite");a.objectStore(Y).put({...e,cacheKey:_a(e),payload:t,storedAt:Date.now()}),await xn(a),await Bc(n)}catch{}}};async function Wc(e,t){const n=e.transaction(Y,"readwrite");n.objectStore(Y).delete(t),await xn(n)}function _a(e){const{historyScope:t,loadWindow:n,limit:a,since:r}=e.scope;return JSON.stringify([e.sourceKey,t,n,a,r])}function Sa(){return typeof indexedDB>"u"?Promise.resolve(null):new Promise(e=>{const t=indexedDB.open(Uc,Vc);t.onupgradeneeded=()=>{t.result.objectStoreNames.contains(Y)||t.result.createObjectStore(Y,{keyPath:"cacheKey"})},t.onsuccess=()=>e(t.result),t.onerror=()=>e(null),t.onblocked=()=>e(null)})}function Vr(e){return new Promise((t,n)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>n(e.error??new Error("IndexedDB request failed"))})}function xn(e){return new Promise((t,n)=>{e.oncomplete=()=>t(),e.onerror=()=>n(e.error??new Error("IndexedDB transaction failed")),e.onabort=()=>n(e.error??new Error("IndexedDB transaction aborted"))})}async function Bc(e){const t=e.transaction(Y,"readonly"),a=(await Vr(t.objectStore(Y).getAll())).sort((i,o)=>o.storedAt-i.storedAt).slice(Kc);if(!a.length)return;const r=e.transaction(Y,"readwrite"),s=r.objectStore(Y);a.forEach(i=>s.delete(i.cacheKey)),await xn(r)}const Qc={kind:"production",load:Lo},zc="codexUsageDashboardRuntimeMetadata",Gc=2048,ln=Fc("usage-snapshot"),Kr={all:Ec(ln),snapshot:(e,t,n)=>Nc(ln,Ac({sourceKey:e,sourceRevision:t}),n)};function Jc(){return new Ri({defaultOptions:{queries:{...Ur("aggregate")}}})}const kn=Jc();async function Zc({currentPayload:e,historyScope:t,loadWindow:n,loadLimit:a,since:r=null,onProgress:s,queryClient:i=kn,refresh:o=!1,snapshotStore:d=Hc,transport:f=Qc}){const h=Hi(a,t,r,n),v=Tn(e),u=Kr.snapshot(v.sourceKey,v.sourceRevision,h);!i.getQueryData(u)&&nl(e,h)&&i.setQueryData(u,e),o&&await i.invalidateQueries({queryKey:u,exact:!0});const p=Ca(e,h),g=await i.fetchQuery({queryKey:u,...Ur(ln.dataClass),queryFn:async({signal:b})=>{var R;const w=!o&&p?await d.read(p):null;if(w)return s==null||s({status:"completed",phase:"loading_rows",message:"Loaded cached dashboard snapshot",completed:Number(w.loaded_row_count??((R=w.rows)==null?void 0:R.length)??0),total:Number(w.total_available_rows??w.loaded_row_count??0),percent:100}),w;const T=await f.load(e,{refresh:o,limit:Wi(h),includeArchived:h.historyScope==="all",loadWindow:n,since:h.since,onProgress:s,signal:b}),N=p?{...p,sourceRevision:String(T.latest_refresh_at??p.sourceRevision)}:Ca(T,h);return N&&await d.write(N,T),T},staleTime:o?0:Or.snapshot.staleTime});return tl(el(g,h)),g}function Ca(e,t){if(!(e!=null&&e.api_token))return null;const n=String(e.latest_refresh_at??"");return n?{sourceKey:qr(e),sourceRevision:n,scope:t}:null}async function Xc(e=kn){await e.cancelQueries({queryKey:Kr.all})}function Yc(e){return yi(e)||or(e)}function el(e,t){return{schema:"codex-usage-dashboard-runtime-v1",...Tn(e),scope:t,updatedAt:Date.now()}}function Tn(e){return{sourceKey:qr(e),sourceRevision:al(e)}}function tl(e,t=rl()){if(t)try{const n=JSON.stringify(e);n.length<=Gc&&t.setItem(zc,n)}catch{}}function nl(e,t){var o;if(!e||!(((o=e.rows)==null?void 0:o.length)??0))return!1;const n=ct(e),a=t.limit===null?n===0:n===t.limit,r=(e.since??null)===t.since,s=mn(e)===t.loadWindow,i=e.include_archived||e.history_scope==="all-history"?"all":"active";return a&&r&&s&&i===t.historyScope}function qr(e){const t=e,n=Number((t==null?void 0:t.payload_cache_version)??0),a=String((t==null?void 0:t.payload_cache_key)??(e!=null&&e.api_token?"live":"static"));return`${n}:${a}`}function al(e){return String((e==null?void 0:e.latest_refresh_at)??"unversioned")}function rl(){try{return typeof window>"u"?null:window.sessionStorage}catch{return null}}const sl=new Set(["duplicate_cumulative_total"]);function il({canUseLiveApi:e,payload:t,shellI18n:n}){const a=ol(t,e,n);return c.jsxs("section",{className:"environment-status","aria-label":n.t("aria.dashboard_status","Dashboard status"),children:[c.jsx("span",{className:"environment-status-title",children:n.t("aria.dashboard_status","Dashboard status")}),c.jsx("div",{className:"environment-status-grid",children:a.map(r=>c.jsx("span",{className:"environment-chip","data-state":r.state,title:r.title,children:r.label},r.label))})]})}function ol(e,t,n){const a=hl(e==null?void 0:e.parser_diagnostics,n);return[{label:n.t("badge.unofficial_project","Unofficial project"),state:"neutral",title:n.t("badge.unofficial_project_title","Codex Usage Tracker is independent and is not made by, affiliated with, endorsed by, sponsored by, or supported by OpenAI. OpenAI and Codex are trademarks of OpenAI.")},{label:t?`${n.t("badge.live","Live")} API`:n.t("badge.static","Static"),state:t?"ready":"warn",title:t?"Local API token present for refresh actions.":"Static embedded snapshot; live refresh is unavailable."},ll(e,n),ul(e,n),dl(e,n),cl(e),...a?[a]:[]]}function cl(e){const t=e==null?void 0:e.dedupe,n=Number((t==null?void 0:t.excluded_copied_rows)||0),a=Number((t==null?void 0:t.canonical_rows)||0),r=Number((t==null?void 0:t.physical_rows)||0);return{label:`Deduped · ${n.toLocaleString()} copied excluded`,state:"ready",title:`Billable totals use ${a.toLocaleString()} canonical rows while preserving ${r.toLocaleString()} physical source rows.`}}function ll(e,t){var s;const n=!!(e!=null&&e.pricing_configured),a=((s=e==null?void 0:e.pricing_snapshot_warning)==null?void 0:s.trim())??"",r=Hr(e==null?void 0:e.pricing_source);return{label:n?t.t("badge.costs","Costs"):t.t("badge.no_costs","No costs"),state:n?a?"warn":"ready":"missing",title:n?[r||"Pricing configured",a].filter(Boolean).join(" - "):t.t("pricing.configure_hint","Run codex-usage-tracker update-pricing to configure estimated costs.")}}function ul(e,t){var s,i;const n=((s=e==null?void 0:e.allowance_error)==null?void 0:s.trim())??"",a=((i=e==null?void 0:e.rate_card_error)==null?void 0:i.trim())??"",r=Hr(e==null?void 0:e.allowance_source)||"Codex credit rates";return n?{label:t.t("state.allowance_config_error","Allowance config error"),state:"missing",title:`Config error: ${n}`}:a?{label:"Rate-card error",state:"missing",title:`Rate-card error: ${a}`}:e!=null&&e.allowance_configured?{label:t.t("state.allowance_configured","Allowance configured"),state:"ready",title:r}:e!=null&&e.rate_card_configured?{label:"Credit rates loaded",state:"ready",title:r}:{label:t.t("action.set_limits","Set limits"),state:"warn",title:"No local allowance windows are configured."}}function dl(e,t){const n=e==null?void 0:e.project_metadata_privacy,a=(n==null?void 0:n.mode)||(e==null?void 0:e.privacy_mode)||"normal",r=a==="normal",s=[n!=null&&n.cwd_redacted?"cwd redacted":"",n!=null&&n.project_names_redacted?"project names redacted":"",n!=null&&n.git_remote_label_hidden?"git remote hidden":"",n!=null&&n.relative_cwd_hidden?"relative cwd hidden":"",n!=null&&n.git_branch_hidden?"git branch hidden":"",n!=null&&n.tags_hidden?"tags hidden":""].filter(Boolean);return{label:r?t.t("badge.metadata_normal","Metadata normal"):Wr(t.t("badge.metadata_mode","Metadata {mode}"),{mode:a}),state:r?"ready":"warn",title:r?"Project metadata is shown normally.":[`Metadata mode: ${a}`,...s].join(" - ")}}function hl(e,t){const n=Object.entries(e??{}).filter(([s,i])=>Number(i||0)>0&&!sl.has(s));if(!n.length)return null;const a=n.reduce((s,[,i])=>s+Number(i||0),0),r=n.map(([s,i])=>`${s}=${Number(i||0)}`).join(", ");return{label:t.t("badge.parser_warnings","Parser warnings"),state:"missing",title:Wr(t.t("parser.warnings_title","Latest refresh reported {count} parser diagnostics: {entries}. Run codex-usage-tracker inspect-log to investigate schema drift."),{count:a.toLocaleString(),entries:r})}}function Hr(e){if(!e)return"";if(typeof e=="string")return e;const t=e.label??e.name??e.type??e.path??"";return typeof t=="string"?t:""}function Wr(e,t){return e.replace(/\{([a-zA-Z0-9_]+)\}/g,(n,a)=>t[a]??n)}async function xa(e){var n;if((n=navigator.clipboard)!=null&&n.writeText)return await navigator.clipboard.writeText(e),!0;const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly","readonly"),t.style.position="fixed",t.style.left="-9999px",t.style.top="0",document.body.appendChild(t),t.select();try{return document.execCommand("copy")}finally{t.remove()}}const fl={"highest-cost":"Highest Cost Threads","context-bloat":"Context Bloat","cache-misses":"Cache Misses","pricing-gaps":"Pricing Gaps","codex-credits":"Codex Credits","reasoning-spike":"Reasoning Spike"};function ou(e,t){return t==="context-bloat"?Number(e.contextWindowPct??0)>=60||e.totalTokens>=2e5:t==="cache-misses"?e.signal==="cache-risk"||e.cachedPct<30||e.uncachedInput>=5e4:t==="pricing-gaps"?e.pricingEstimated||!Number.isFinite(e.cost):t==="codex-credits"?e.credits>0:t==="reasoning-spike"?e.reasoningOutput>0:!0}function ml(e){return fl[e]??e}const gl=se(()=>O(()=>import("./OverviewPage.js"),__vite__mapDeps([49,1,2,34,4,20,21,9,10,31,32,6,16,17,18,45,22,11,12,13,14,35,23,24,25,26,50])),"OverviewPage"),pl=se(()=>O(()=>import("./InvestigatorPage.js"),__vite__mapDeps([51,1,2,34,4,6,41,7,20,21,9,10,16,17,18,45,22,11,12,13,23,24,38,25,26,52])),"InvestigatorPage"),bl=se(()=>O(()=>import("./CompressionLabPage.js"),__vite__mapDeps([53,1,2,34,4,6,9,10,17,25,26,12,54])),"CompressionLabPage"),vl=se(()=>O(()=>import("./ExploreRoutePage.js"),__vite__mapDeps([55,1,2,44,3,4,5,6,7,14,29,33,19,45,22,11,12,13,35,23,24,34,46,47,38,25,26,48,8,9,10,56])),"ExploreRoutePage"),wl=se(()=>O(()=>import("./CallInvestigatorPage.js"),__vite__mapDeps([57,1,2,46,33,19,37,38,47,25,26,12])),"CallInvestigatorPage"),yl=se(()=>O(()=>import("./ThreadsPage.js"),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27])),"ThreadsPage"),_l=se(()=>O(()=>import("./UsageDrainPage.js"),__vite__mapDeps([36,1,2,34,4,20,21,9,10,16,17,18,24,37,38,25,26,12,39])),"UsageDrainPage"),Sl=se(()=>O(()=>import("./CacheContextPage.js"),__vite__mapDeps([28,1,2,29,30,22,31,32,6,33,19,20,21,9,10,23,34,4,3,5,7,15,35,24,25,26,12])),"CacheContextPage"),Cl=se(()=>O(()=>import("./DiagnosticsPage.js"),__vite__mapDeps([40,1,2,29,33,19,20,21,9,10,23,24,41,4,6,7,34,3,25,26,12])),"DiagnosticsPage"),xl=se(()=>O(()=>import("./ReportsPage.js"),__vite__mapDeps([42,1,2,34,4,6,33,19,20,21,9,10,16,17,18,30,22,35,23,24,25,26,12,43])),"ReportsPage"),kl=se(()=>O(()=>import("./SettingsPage.js"),__vite__mapDeps([58,1,2,33,19,32,38,47,17,25,26,12,59])),"SettingsPage");function Tl(e){return c.jsx(C.Suspense,{fallback:c.jsx(Rl,{activeView:e.activeView}),children:Ll(e)})}function Ll(e){const{activePreset:t,activeRecordId:n,activeView:a,autoRefreshEnabled:r,applicationI18n:s,backFromCallInvestigator:i,callBackLabel:o,canLoadAllRows:d,canUseLiveApi:f,contextRuntime:h,copyCallInvestigatorLink:v,dashboardPayload:u,globalFilters:p,globalQuery:g,hasMoreRows:b,historyScope:w,loadWindow:T,loadAllRows:N,loadedRowCount:R,loadLimit:_,loadMoreRows:L,scopeSince:y,model:k,navigateView:j,onRefresh:E,openCallInvestigator:F,refreshing:me,refreshState:vt,setContextApiEnabled:wt,sourceIdentity:I,totalAvailableRows:at}=e;switch(a){case"overview":return c.jsx(gl,{model:k,contextRuntime:h,sourceKey:I.sourceKey,sourceRevision:I.sourceRevision,onRefresh:E,globalQuery:g,runtime:{historyScope:w,loadLimit:_,loadWindow:T,loadedRowCount:R,scopeSince:y,totalAvailableRows:at},refreshing:me,canLoadMoreRows:f&&b,onLoadMoreRows:L,onOpenInvestigator:F,onCopyCallLink:v,onNavigateView:j,globalFilters:p});case"investigator":return c.jsx(pl,{model:k,contextRuntime:h,includeArchived:w==="all",sourceKey:I.sourceKey,sourceRevision:I.sourceRevision,onOpenInvestigator:F,onCopyCallLink:v,onNavigateView:j});case"compression-lab":return c.jsx(bl,{contextRuntime:h,includeArchived:w==="all",since:y,sourceKey:I.sourceKey,sourceRevision:I.sourceRevision});case"calls":return c.jsx(vl,{model:k,globalQuery:g,activePreset:t,onRefresh:E,contextRuntime:h,includeArchived:w==="all",scopeSince:y,sourceKey:I.sourceKey,sourceRevision:I.sourceRevision,onContextApiEnabledChange:wt,onOpenInvestigator:F,onCopyCallLink:v,onNavigateView:j});case"call":return c.jsx(wl,{model:k,recordId:n,contextRuntime:h,onContextApiEnabledChange:wt,onNavigateRecord:F,onCopyCallLink:v,onBackToCalls:i,backLabel:o});case"threads":return c.jsx(yl,{model:k,globalQuery:g,onOpenInvestigator:F,onCopyCallLink:v,globalFilters:p,contextRuntime:h,includeArchived:w==="all",sourceKey:I.sourceKey,sourceRevision:I.sourceRevision,onNavigateView:j});case"usage-drain":return c.jsx(_l,{model:k,contextRuntime:h,includeArchived:w==="all",sourceRevision:I.sourceRevision,onOpenInvestigator:F,onCopyCallLink:v});case"cache-context":return c.jsx(Sl,{model:k,contextRuntime:h,includeArchived:w==="all",scopeSince:y,sourceKey:I.sourceKey,sourceRevision:I.sourceRevision,onOpenInvestigator:F,onCopyCallLink:v});case"diagnostics":return c.jsx(Cl,{model:k,contextRuntime:h,includeArchived:w==="all",sourceKey:I.sourceKey,sourceRevision:I.sourceRevision,rowLoadControls:{loadedRowCount:R,totalAvailableRows:at,canLoadMoreRows:f&&b,canLoadAllRows:d,refreshing:me,onLoadMoreRows:L,onLoadAllRows:N},onOpenInvestigator:F,onCopyCallLink:v,globalFilters:p});case"reports":return c.jsx(xl,{model:k,refreshState:vt,includeArchived:w==="all",loadWindow:T,loadLimit:_,sourceKey:I.sourceKey,sourceRevision:I.sourceRevision,onOpenInvestigator:F,onCopyCallLink:v});case"settings":return c.jsx(kl,{model:k,payload:u,historyScope:w,loadWindow:T,loadLimit:_,scopeSince:y,loadedRowCount:R,totalAvailableRows:at,canUseLiveApi:f,autoRefreshEnabled:r,refreshState:vt,applicationI18n:s})}}function Rl({activeView:e}){return c.jsxs("section",{"aria-busy":"true","aria-live":"polite",className:"route-state",role:"status",children:["Loading ",e.replace("-"," "),"..."]})}const ka=1e4,Ml={1:"overview",2:"calls",3:"threads",4:"diagnostics"},Pl=new Set(["call","compression-lab","diagnostics","investigator"]);function Br(e){return!Pl.has(e)}function jl(e){return e instanceof Element?!!e.closest('input, select, textarea, button, [contenteditable="true"]'):!1}function Qr(){var Vn;const e=C.useRef(null),t=C.useMemo(()=>To(),[]),[n,a]=C.useState(t),[r,s]=C.useState(()=>ia(t)),[i,o]=C.useState(()=>{const m=new URLSearchParams(window.location.search);return on(m.get("view"))}),[d,f]=C.useState(()=>new URLSearchParams(window.location.search).get("record")??""),[h,v]=C.useState(()=>fa()),[u,p]=C.useState(()=>ma()),[g,b]=C.useState(()=>new URLSearchParams(window.location.search).get("q")??""),[w,T]=C.useState(()=>new URLSearchParams(window.location.search).get("preset")??""),[N,R]=C.useState(()=>window.location.search),[_,L]=C.useState("Stored snapshot loaded just now"),[y,k]=C.useState(null),[j,E]=C.useState(!1),[F,me]=C.useState(!1),[vt,wt]=C.useState(!1),[I,at]=C.useState(()=>Gs(t)),$t=C.useRef(!1),[G,Ln]=C.useState(()=>je(ct(t),500)),[Ue,yt]=C.useState(()=>je(ct(t),500)),[q,Rn]=C.useState(()=>Bi(t)),[J,_t]=C.useState(()=>ga(Wt(t))),[Mn,Pn]=C.useState(r.contextRuntime.contextApiEnabled),H=!!(n!=null&&n.api_token),zr=C.useMemo(()=>Tn(n),[n]),A=C.useMemo(()=>$a(n,I),[n,I]),jn=C.useMemo(()=>({...r.contextRuntime,contextApiEnabled:Mn}),[Mn,r.contextRuntime]),Gr=C.useMemo(()=>Bo(r,J,N),[J,N,r]),An=i==="calls"||i==="call"?r:Gr,rt=Br(i),It=je(Ue,G,500),ge=Math.max(0,Number((n==null?void 0:n.loaded_row_count)??((Vn=n==null?void 0:n.rows)==null?void 0:Vn.length)??r.calls.length??0)),Ve=Math.max(0,Number((n==null?void 0:n.total_available_rows)??ge)),Jr=Ue!==G,Nn=Gi({currentLimit:G,loadedRows:ge,pendingLimit:Ue}),Zr=Math.min(It,Nn),En=!!(n!=null&&n.has_more)||Ve>0&&ge{const m=new URL(window.location.href);ba(m)&&Ke(m)},[]),C.useEffect(()=>{function m(){const S=window.location.search,M=new URLSearchParams(S);o(on(M.get("view"))),f(M.get("record")??""),v(fa(S)),p(ma(S)),b(M.get("q")??""),T(M.get("preset")??""),_t(ga(Wt(n),S)),R(S)}return window.addEventListener("popstate",m),()=>window.removeEventListener("popstate",m)},[n]),C.useEffect(()=>{function m(S){var oe;if(jl(S.target))return;if(S.key==="/"){S.preventDefault(),(oe=e.current)==null||oe.focus();return}const M=Ml[S.key];M&&(S.preventDefault(),st(M))}return window.addEventListener("keydown",m),()=>window.removeEventListener("keydown",m)},[]),C.useEffect(()=>{function m(){wt(window.scrollY>320)}return m(),window.addEventListener("scroll",m,{passive:!0}),()=>window.removeEventListener("scroll",m)},[]);function rs(m){const S=i==="call"?h:i;v(S),p(i==="call"?u:!0),o("call"),f(m);const M=new URL(window.location.href);kt(M,i,i==="call"?h:[]),M.searchParams.set("view","call"),M.searchParams.set("record",m),M.searchParams.set("return",S),$n(M)}function ss(){st(h)}function is(m){b(m),T("");const S=new URL(window.location.href);S.searchParams.delete("preset"),m?S.searchParams.set("q",m):S.searchParams.delete("q"),Ke(S)}async function ie(m={}){var St;if(j)return;$t.current=!0;const S=m.loadLimit??G,M=m.loadWindow??q,oe=Ht(M),pe=m.historyScope??J,ee=m.refresh??!0,Ot=!!(n!=null&&n.refresh_jobs_available);E(!0),k(Ot?{status:"running",phase:ee?"refreshing_index":"loading_rows",message:`${ee?"Refreshing":"Loading"} ${Re(M,S)}`}:null),L(`${ee?"Refreshing index for":"Loading"} ${Re(M,S)}...`);let qe=!1;try{const z=await Zc({currentPayload:n,refresh:ee,loadLimit:S,loadWindow:M,since:oe,historyScope:pe,onProgress:Vt=>{qe||(qe=Vt.message==="Loaded cached dashboard snapshot"),k(Vt),L(Yn(Vt,pe))}});Cc(),a(z),s(ia(z)),Pn(!!z.context_api_enabled);const ke=M==="rows"?je(ct(z,S),S):S;Ln(ke),yt(ke),Rn(M);const Kn=Wt(z,pe);_t(Kn),zi(ke,Kn,M);const Ut=z.loaded_row_count??((St=z.rows)==null?void 0:St.length)??0,qn=z.total_available_rows??Ut;L(M==="rows"?`${qe?"Cache hit; l":ee?"Refreshed index; l":"L"}oaded ${Ut.toLocaleString()} of ${qn.toLocaleString()} calls from ${Re(M,ke)}`:`${qe?"Cache hit; ":ee?"Refreshed index; ":""}${Re(M,ke)} analysis ready across ${qn.toLocaleString()} calls; ${Ut.toLocaleString()} detail rows cached`)}catch(z){if(k(null),Yc(z)){L("Refresh cancelled; stored snapshot remains visible");return}const ke=new Date().toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});L(`${to(z)} Stored snapshot kept at ${ke}`)}finally{k(null),E(!1)}}async function os(){await Xc(),k(null),E(!1),L("Refresh cancelled; stored snapshot remains visible")}C.useEffect(()=>{document.documentElement.lang=A.language,document.documentElement.dir=A.direction},[A.direction,A.language]),C.useEffect(()=>{var z;const m=Number((n==null?void 0:n.total_available_rows)??0),S=Number((n==null?void 0:n.loaded_row_count)??((z=n==null?void 0:n.rows)==null?void 0:z.length)??0),M=Qi(),oe=(M==null?void 0:M.historyScope)??J,pe=(M==null?void 0:M.loadLimit)??G,ee=(M==null?void 0:M.loadWindow)??q,Ot=mn(n),qe=oe!==J||ee!==Ot||ee==="rows"&&pe!==ct(n),St=S>0;!H||!qe&&St||m<=0||j||$t.current||($t.current=!0,qe&&(_t(oe),Ln(pe),yt(pe),Rn(ee),Ke(pa(oe))),ie({refresh:!1,historyScope:oe,loadLimit:pe,loadWindow:ee}))},[H,n,J,G,q,j]),C.useEffect(()=>{!H&&F&&me(!1)},[F,H]),C.useEffect(()=>{if(!F||!H||!rt)return;const m=window.setInterval(()=>{ie()},ka);return()=>window.clearInterval(m)},[F,rt,H,n,J,G,q,j]),C.useEffect(()=>{if(!F||!H||!rt)return;function m(){document.visibilityState==="visible"&&ie()}return document.addEventListener("visibilitychange",m),()=>document.removeEventListener("visibilitychange",m)},[F,rt,H,n,J,G,q,j]);function In(m){const S=m.trim();yt(Math.max(1,pt(S===""?Number.NaN:Number(S))))}function cs(m){In(m)}function ls(){ie({refresh:!1,loadLimit:Ue,loadWindow:"rows"})}function us(){ie({refresh:!1,loadWindow:"all"})}function On(){yt(Fn),ie({refresh:!1,loadLimit:Fn,loadWindow:q})}function ds(m){m!==q&&ie({refresh:!1,loadWindow:m})}function hs(m){const S=m==="all"?"all":"active";_t(S),Ke(pa(S)),ie({refresh:!1,historyScope:S})}function fs(m){if(me(m),m){if(!rt){L("Auto refresh pauses on this evidence-heavy view");return}L(`Auto refresh every ${ka/1e3}s`),ie()}else L("Auto refresh paused")}function ms(m){at(m),Js(m)}async function gs(){try{const m=new URL(window.location.href);if(ba(m),kt(m,i,i==="call"?h:[]),!await xa(m.toString()))throw new Error("Clipboard unavailable");L("Copied current view link")}catch{L("Copy unavailable in browser")}}async function ps(m){try{const S=new URL(window.location.href);kt(S,i,i==="call"?h:[]);const M=i==="call"?h:i;if(S.searchParams.set("view","call"),S.searchParams.set("record",m),S.searchParams.set("return",M),!await xa(S.toString()))throw new Error("Clipboard unavailable");L("Copied call investigator link")}catch{L("Copy unavailable in browser")}}async function bs(){const m=await Xi(i,An,{contextRuntime:jn,historyScope:J,loadWindow:q,loadLimit:G,scopeSince:(n==null?void 0:n.since)??Ht(q),loadedRowCount:ge,totalAvailableRows:Ve,canUseLiveApi:H,autoRefreshEnabled:F,refreshState:_},g,w);if(!m.rowCount){L(`No ${m.label} to export`);return}Fi(m.filename,m.csv),L(`Exported ${m.rowCount} ${m.label}`)}function vs(){T("");const m=new URL(window.location.href);m.searchParams.delete("preset"),Ke(m),L("Investigation preset cleared")}function ws(){window.scrollTo({top:0,behavior:"smooth"})}return c.jsx(io,{value:A,children:c.jsxs("div",{className:"app-shell","data-dashboard-localization-root":!0,children:[c.jsxs("aside",{className:"sidebar",children:[c.jsxs("div",{className:"brand",children:[c.jsx("div",{className:"brand-mark",children:c.jsx(Ks,{size:22})}),c.jsxs("div",{children:[c.jsx("strong",{children:"Codex Usage Tracker"}),c.jsx("span",{children:A.t("dashboard.eyebrow","Local telemetry console")})]})]}),c.jsxs("div",{className:"local-pill",children:[c.jsx("span",{"aria-hidden":"true"}),"Local data only"]}),c.jsx(il,{payload:n,canUseLiveApi:H,shellI18n:A}),c.jsx("nav",{className:"primary-nav","aria-label":"Primary",children:kr.map(m=>{const S=m.icon,M=i===m.id||i==="call"&&m.id==="calls";return c.jsxs("button",{type:"button","aria-pressed":M,className:M?"active":"",onClick:()=>st(m.id),children:[c.jsx(S,{size:18}),c.jsx("span",{children:A.navLabel(m.id,m.label)})]},m.id)})}),c.jsxs("div",{className:"secondary-block",role:"group","aria-label":"Quick Links",children:[c.jsx("span",{children:"Quick Links"}),Tr.map(m=>{const S=m.icon;return c.jsxs("button",{type:"button",onClick:()=>st(m.target),children:[c.jsx(S,{size:16}),m.label]},m.label)})]})]}),c.jsxs("main",{className:"workspace",children:[c.jsxs("div",{className:"unofficial-banner",role:"note","aria-label":"Unofficial project notice",children:[c.jsx(Us,{size:16}),c.jsxs("span",{children:[c.jsx("strong",{children:"Unofficial project."})," Not made by, affiliated with, endorsed by, sponsored by, or supported by OpenAI."]})]}),c.jsxs("header",{className:"topbar","aria-label":"Dashboard toolbar",children:[c.jsxs("label",{className:"global-search",children:[c.jsx("span",{className:"sr-only",children:A.t("filter.search","Search dashboard")}),c.jsx("input",{ref:e,"aria-label":A.t("filter.search","Search dashboard"),value:g,onChange:m=>is(m.target.value),placeholder:A.t("filter.search_placeholder","Search calls, threads, models, diagnostics...")})]}),c.jsxs("div",{className:"topbar-actions",children:[c.jsxs("div",{className:"topbar-scope-controls",children:[A.languages.length>1?c.jsxs("label",{className:"topbar-select",children:[c.jsx("span",{children:A.t("language.label","Language")}),c.jsx("select",{"data-localization-skip":"true","aria-label":A.t("language.label","Language"),value:A.language,onChange:m=>ms(m.target.value),children:A.languages.map(m=>c.jsx("option",{value:m.code,children:m.native_name||m.english_name||m.code},m.code))})]}):null,c.jsxs("label",{className:"topbar-select",children:[c.jsx("span",{children:A.t("nav.history","History")}),c.jsxs("select",{"aria-label":"History scope",title:Dn,value:J,onChange:m=>hs(m.target.value),disabled:j||!H,children:[c.jsx("option",{value:"active",children:A.t("option.active_sessions_only","Active")}),c.jsx("option",{value:"all",children:A.t("option.all_history","All history")})]}),c.jsx("small",{className:"sr-only",children:Dn})]})]}),c.jsx(sc,{canUseLiveApi:H,finitePendingLoadLimit:It,hasMoreRows:En,loadLabel:A.t("nav.load","Load"),loadMoreLabel:A.t("button.load_more","Load more"),loadWindow:q,loadedRowCount:ge,pendingLoadLimit:Ue,refreshProgressPercent:ts,refreshProgressText:ns,refreshing:j,rowLimitChanged:Jr,rowLimitSliderMax:Nn,rowLimitSliderValue:Zr,rowLoadModeLabel:Yr,rowLoadStatus:Xr,totalAvailableRows:Ve,onApply:ls,onCancel:os,onDraftChange:In,onLoadMore:On,onSliderChange:cs,onWindowChange:ds}),c.jsxs("div",{className:"topbar-meta",children:[c.jsxs("div",{className:"topbar-statuses",children:[w?c.jsxs("button",{className:"toolbar-button",type:"button",onClick:vs,children:[c.jsx(dn,{size:15}),A.t("button.clear","Clear")," ",ml(w)]}):null,c.jsxs("label",{className:"topbar-toggle",children:[c.jsx("input",{"aria-label":"Auto refresh",type:"checkbox",checked:F,onChange:m=>fs(m.target.checked),disabled:j||!H}),c.jsx("span",{children:"Auto"})]})]}),c.jsxs("div",{className:"topbar-icon-actions",children:[c.jsx("button",{className:"icon-button",type:"button",onClick:gs,"aria-label":A.t("button.copy_link","Copy link"),title:A.t("button.copy_link","Copy link"),children:c.jsx(js,{size:17})}),c.jsx("button",{className:"icon-button",type:"button",onClick:bs,"aria-label":A.t("button.export_csv","Export CSV"),title:A.t("button.export_csv","Export CSV"),children:c.jsx(Ns,{size:17})}),c.jsx("button",{className:"icon-button",type:"button",onClick:Un,"aria-label":A.t("button.refresh","Refresh"),title:A.t("button.refresh","Refresh"),disabled:j,children:c.jsx(Is,{size:17})})]})]})]})]}),c.jsx("p",{className:"sr-only",role:"status","aria-live":"polite",children:_}),c.jsx(Tl,{activeView:i,model:An,navigateView:st,onRefresh:Un,refreshState:_,globalQuery:g,activePreset:w,activeRecordId:d,contextRuntime:jn,setContextApiEnabled:Pn,openCallInvestigator:rs,copyCallInvestigatorLink:ps,callBackLabel:u?`Back to ${gc(h)}`:A.t("button.back_to_dashboard","Back to dashboard"),backFromCallInvestigator:ss,dashboardPayload:n,sourceIdentity:zr,historyScope:J,loadWindow:q,scopeSince:(n==null?void 0:n.since)??Ht(q),loadLimit:G,loadedRowCount:ge,totalAvailableRows:Ve,canUseLiveApi:H,autoRefreshEnabled:F,applicationI18n:A,refreshing:j,hasMoreRows:En,canLoadAllRows:as,loadMoreRows:On,loadAllRows:us,globalFilters:c.jsx(ic,{activeView:i,locationSearch:N,model:r,onUrlChange:Ke})})]}),vt?c.jsxs("button",{className:"to-top-button",type:"button",onClick:ws,"aria-label":"Back to top",children:[c.jsx(Ts,{size:18}),A.t("button.top","Top")]}):null]})});function Un(){ie()}}function Al(){return c.jsx(Mi,{client:kn,children:c.jsx(Qr,{})})}const cu=Object.freeze(Object.defineProperty({__proto__:null,App:Qr,RoutedApp:Al,shouldAutoRefreshUsageView:Br},Symbol.toStringTag,{value:"Module"}));export{Jl as $,Ts as A,Il as B,Oi as C,Ns as D,Zl as E,Ds as F,ks as G,As as H,ya as I,Et as J,Q as K,Ol as L,qa as M,ne as N,au as O,kc as P,eu as Q,Is as R,Os as S,ru as T,Ps as U,Vs as V,ou as W,xa as X,Ql as Y,Bl as Z,Wl as _,js as a,Fs as a0,dn as a1,ml as a2,Kl as a3,Vl as a4,gi as a5,ci as a6,Gt as a7,Wa as a8,ii as a9,oi as aa,zt as ab,Ka as ac,Ci as ad,di as ae,Ul as af,Hl as ag,Gl as ah,kt as ai,fa as aj,zl as ak,Re as al,cu as am,Xl as b,$ as c,Fi as d,ce as e,ve as f,Di as g,iu as h,su as i,Le as j,dr as k,ql as l,Xt as m,Mr as n,nu as o,dt as p,tu as q,Ei as r,va as s,Yl as t,Va as u,pr as v,Ur as w,Nc as x,Ac as y,Fc as z}; diff --git a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/CallInvestigatorPage.js b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/CallInvestigatorPage.js index 28188d15..a5bfb058 100644 --- a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/CallInvestigatorPage.js +++ b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/CallInvestigatorPage.js @@ -1,6 +1,6 @@ -import{r as C,j as e}from"./dashboard-react.js";import{c as Z,v as R,p as U,j as h,u as A,ac as ee,a as te,C as ae,f as F,m as Q,ad as ne,ae as se,X as le}from"./App.js";import{f as q,t as oe,u as ie,T as ce,w as re,C as de,z as ue,v as he,x as O,y as xe,A as me,B as pe,c as be,d as K,a as B,r as $,l as ge,g as je,D as V,b as ve,e as fe,h as ye,i as J,j as W,k as Ce,m as we,n as ke,o as _e,p as Ne,q as Ee,s as Se}from"./contextEvidenceState.js";import{P as T}from"./Panel.js";import{S as z}from"./StatusBadge.js";import{C as Te,a as Ie}from"./chevron-right.js";import{S as Ae}from"./shield-check.js";import{L as Le}from"./lock-keyhole.js";import"./locale-zh-Hans.js";import"./dashboardRouter.js";import"./router.js";/** +import{r as C,j as e}from"./dashboard-react.js";import{c as G,v as R,p as U,j as h,u as A,ag as ee,a as te,C as ae,f as F,Y as ne,Z as se,m as Q,_ as le,ah as oe,ai as ie,aj as ce,X as re,$ as O,ak as de}from"./App.js";import{f as q,t as ue,T as he,v as xe,C as me,u as pe,w as be,x as ge,c as je,d as K,a as B,r as $,l as ve,g as fe,y as V,b as ye,e as Ce,h as we,i as J,j as W,k as ke,m as _e,n as Ne,o as Ee,p as Se,q as Te,s as Ie}from"./contextEvidenceState.js";import{P as T}from"./Panel.js";import{S as z}from"./StatusBadge.js";import{C as Ae,a as Le}from"./chevron-right.js";import{S as Pe}from"./shield-check.js";import{L as Re}from"./lock-keyhole.js";import"./locale-zh-Hans.js";import"./dashboardRouter.js";import"./router.js";/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const X=Z("ArrowLeft",[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]]),D=new Map;function Pe(t,n){ze(t,n);const s=`${n.apiToken}:${t}`,i=D.get(s);if(i)return i;const l=Re(t,n).finally(()=>{D.delete(s)});return D.set(s,l),l}async function Re(t,n){var o;const s=new URLSearchParams({record_id:t,_:String(Date.now())}),i=await fetch(`/api/call?${s.toString()}`,{headers:{Accept:"application/json","X-Codex-Usage-Token":n.apiToken},cache:"no-store"}),l=await Me(i,"Call detail");if(!((o=l.record)!=null&&o.record_id))throw new Error("Call detail response did not include the requested aggregate record.");const u=Array.isArray(l.adjacent_records)&&l.adjacent_records.length?l.adjacent_records:[l.previous_record,l.record,l.next_record].filter(p=>!!p);return{record:R(l.record,0),previousRecord:l.previous_record?R(l.previous_record,-1):null,nextRecord:l.next_record?R(l.next_record,1):null,adjacentRecords:u.map((p,c)=>R(p,c)),rawPayload:l}}function ze(t,n){if(!t)throw new Error("record_id is required for call detail loading.");if(n.fileMode)throw new Error("Call detail hydration requires the localhost dashboard server.");if(!n.apiToken)throw new Error("Call detail hydration requires a localhost dashboard API token.")}async function Me(t,n){let s={};try{s=await t.json()}catch{s={}}if(!t.ok){const i=typeof s.error=="string"?s.error:`${n} request failed (${t.status})`;throw new Error(i)}return s}function Oe(t,n){return H(n.t("call.readout.exact_body","{input} input tokens = {cached} cached + {uncached} uncached; {output} output tokens; {cache} cache reuse."),{input:h(t.input),cached:h(t.cachedInput),uncached:h(t.uncachedInput),output:h(t.output),cache:U(t.cachedPct)})}function $e(t){return t.t("call.readout.previous_unavailable","No previous call is loaded in resolved thread, call-to-call deltas unavailable.")}function De(t,n){const s=t.uncachedInput-n.uncachedInput,i=t.cachedInput-n.cachedInput;return s>0&&i<0?`Fresh input rose by ${h(s)} while cached input fell by ${h(Math.abs(i))}; classic cache-drop profile.`:s>0?`Fresh input increased by ${h(s)} from previous call; inspect evidence new files, tool results, or rewritten context.`:s<0&&i>=0?`Fresh input fell by ${h(Math.abs(s))} while cached input increased, so this call reused context more efficiently previous one.`:"Token accounting broadly stable compared previous call in resolved thread."}function qe(t,n){var s;if(t.status==="loaded"){const i=((s=t.payload.entries)==null?void 0:s.length)??0,l=Number(t.payload.visible_char_count??0),u=Number(t.payload.visible_token_estimate??0),o=Ue(t.payload,n);return H(n.t("call.readout.evidence_analyzed","Evidence analyzed: {totalEntries} selected-turn entries, {visibleChars} visible redacted chars, {visibleTokens} visible tokens.{serializedDetail}"),{totalEntries:h(i),visibleChars:h(l),visibleTokens:h(u),estimator:t.payload.visible_token_estimator??"visible-token estimate",serializedDetail:o,renderedEntries:h(i)})}return t.status==="loading"?n.t("call.readout.evidence_loading",t.message):t.status==="error"?`Evidence request failed: ${t.message}`:"Evidence is not loaded yet. Aggregate token counts are exact, but visible-context attribution needs runtime evidence."}function Ue(t,n){const s=t.serialized_evidence??{};if(s.deferred||s.deferred_buckets)return n.t("call.readout.evidence_serialized_deferred","Fast serialized estimate only; full serialized grouping deferred.");const i=Ke(t);return i<=0?"":H(n.t("call.readout.evidence_serialized_bound"," Serialized local upper bound: {tokens} tokens."),{tokens:h(i),chars:h(Number(s.raw_json_char_count??s.total_chars??0))})}function Fe(t,n){const s=t.input>0?t.cachedInput/t.input:t.cachedPct/100,i=n&&n.input>0?n.cachedInput/n.input:n?n.cachedPct/100:null,l=(n==null?void 0:n.uncachedInput)??0;return n&&i!==null&&i>=.8&&s<=.05&&t.input>=1e3?"Compare previous call, then inspect loaded evidence see fresh context was sent after cache miss.":n&&t.uncachedInput>Math.max(l*2,1e3)?"Inspect most recent evidence entries first; spike is in fresh uncached input, not cached history.":s>=.85?`Cache reuse is healthy; focus on ${h(t.uncachedInput)} uncached tokens were still billed as fresh input.`:n?"Use delta cards to locate whether change came from cached input, uncached input, or output/reasoning.":"Use loaded evidence if aggregate totals are not enough understand this isolated call."}function He(t){return t==="Hydrated from /api/call"?"Position: hydrated live record outside loaded snapshot":`Position: ${t}`}function H(t,n){return t.replace(/\{(\w+)\}/g,(s,i)=>n[i]??s)}function Ke(t){const n=t.serialized_evidence??{};return Number(n.raw_json_token_estimate??n.token_estimate??0)}function ct({model:t,recordId:n,contextRuntime:s,onContextApiEnabledChange:i,onNavigateRecord:l,onCopyCallLink:u,onBackToCalls:o,backLabel:p}){const c=A(),[j,b]=C.useState(""),[x,k]=C.useState({status:"idle"}),[d,w]=C.useState({status:"idle"}),[S,L]=C.useState({recordId:"",nonce:0}),I=x.status==="loaded"&&x.recordId===n?x.detail:null,{modelIndex:N,hydratedDetail:E,call:a,previous:r,next:g,threadCalls:m,positionLabel:v}=C.useMemo(()=>ee({calls:t.calls,recordId:n,detail:I}),[I,t.calls,n]),G=d.status==="loaded"?d.payload:null;if(C.useEffect(()=>{w({status:"idle"})},[n]),C.useEffect(()=>{if(!n||N>=0){k({status:"idle"});return}if(s.fileMode||!s.apiToken){k({status:"error",recordId:n,message:"This call is outside the loaded snapshot. Serve the dashboard with its localhost API token to hydrate it."});return}let _=!1;return k({status:"loading",recordId:n,message:"Loading call detail from localhost..."}),Pe(n,s).then(P=>{_||k({status:"loaded",recordId:n,detail:P})}).catch(P=>{_||k({status:"error",recordId:n,message:q(P)})}),()=>{_=!0}},[s,N,n]),!a){const _=x.status==="loading"||x.status==="error"?x.message:n?"Selected call is not present in the loaded dashboard rows.":"No aggregate call rows are loaded.";return e.jsx("div",{className:"page-grid",children:e.jsxs("div",{className:"page-title-row",children:[e.jsxs("div",{children:[e.jsx("h1",{children:"Call Investigator"}),e.jsx("p",{children:_})]}),e.jsxs("button",{className:"toolbar-button",type:"button",onClick:o,children:[e.jsx(X,{size:16})," ",p]})]})})}async function Y(){try{const _=new URL(window.location.href);if(ne(_,"call",se(_.search)),!await le(_.toString()))throw new Error("Clipboard unavailable");b("Copied investigator link")}catch{b("Copy unavailable in this browser")}}return e.jsxs("div",{className:"call-investigator-layout",children:[e.jsxs("div",{className:"page-title-row span-all",children:[e.jsxs("div",{children:[e.jsx("h1",{children:"Call Investigator"}),e.jsxs("p",{children:[a.thread," / ",a.model]})]}),e.jsxs("div",{className:"toolbar",children:[e.jsxs("button",{className:"toolbar-button",type:"button",onClick:o,children:[e.jsx(X,{size:16})," ",p]}),e.jsxs("button",{className:"toolbar-button",type:"button",onClick:()=>r&&l(r.id),disabled:!r,children:[e.jsx(Te,{size:16})," ",c.t("button.previous_call","Previous call")]}),e.jsxs("button",{className:"toolbar-button",type:"button",onClick:()=>g&&l(g.id),disabled:!g,children:[c.t("button.next_call","Next call")," ",e.jsx(Ie,{size:16})]}),e.jsxs("button",{className:"toolbar-button",type:"button",onClick:Y,"aria-label":c.t("button.copy_investigator_link","Copy investigator link"),children:[e.jsx(te,{size:16})," ",c.t("button.copy_link","Copy link")]})]})]}),e.jsxs(T,{title:c.t("call.readout.title","Investigation Readout"),subtitle:j||v,className:"span-all",action:e.jsx(z,{label:c.t("call.readout.badge","Aggregate + on-demand evidence"),tone:"blue"}),children:[e.jsxs("div",{className:"call-summary",children:[e.jsx(z,{label:"Aggregate only",tone:"green"}),E?e.jsx(z,{label:"Hydrated live",tone:"blue"}):null,e.jsx(ae,{call:a}),e.jsx(z,{label:"Raw context gated",tone:"blue"}),e.jsx("span",{className:"call-id",children:a.id.slice(0,16)})]}),e.jsx(Be,{call:a,previous:r,evidenceState:d,positionLabel:v}),e.jsxs("div",{className:"drilldown-metric-grid wide",children:[e.jsx(f,{label:"Total tokens",value:h(a.totalTokens),detail:`${F(a.input)} input`}),e.jsx(f,{label:"Uncached input",value:h(a.uncachedInput),detail:"fresh billed input"}),e.jsx(f,{label:"Cache hit rate",value:U(a.cachedPct),detail:oe(a)}),e.jsx(f,{label:"Estimated cost",value:Q(a.cost),detail:a.pricingEstimated?"estimated pricing":"configured pricing"}),e.jsx(f,{label:"Duration",value:a.duration,detail:a.fast?"fast candidate":"normal throughput"}),e.jsx(f,{label:"Usage credits",value:a.credits?a.credits.toFixed(3):"-",detail:a.usageCreditConfidence})]}),e.jsx(ie,{call:a})]}),e.jsxs(T,{title:"Token Accounting",subtitle:"Exact aggregate row fields",children:[e.jsx(Qe,{call:a}),e.jsx(ce,{call:a})]}),e.jsx(T,{title:"Cache Accounting",subtitle:"Derived from adjacent aggregate call",children:e.jsx(re,{call:a,calls:m})}),e.jsx(T,{title:"Context Attribution",subtitle:"Estimated from visible log volume",className:"span-all",children:e.jsx(de,{call:a,payload:G,onRunFullAnalysis:()=>L(_=>({recordId:a.id,nonce:_.nonce+1})),showHeading:!1})}),e.jsxs(T,{title:"Aggregate Identity",subtitle:"Local metadata only",children:[e.jsxs("dl",{className:"detail-list",children:[e.jsx(y,{label:"Record id",value:a.id}),e.jsx(y,{label:"Time",value:a.time}),e.jsx(y,{label:"Thread",value:a.thread}),e.jsx(y,{label:"Model",value:a.model}),e.jsx(y,{label:"Effort",value:a.effort}),e.jsx(y,{label:"Project",value:a.project||"Unknown"}),e.jsx(y,{label:"Context window",value:ue(a)}),e.jsx(y,{label:"Recommendation",value:a.recommendation||"No aggregate recommendation"})]}),e.jsx(he,{call:a}),a.recommendation?e.jsxs("div",{className:"recommendation-box",children:[e.jsx(Ae,{size:16}),e.jsx("p",{children:a.recommendation})]}):null]}),e.jsx(T,{title:"Thread Context",subtitle:`${m.length} loaded related calls`,className:"span-all",children:e.jsx(Xe,{call:a,calls:m,onNavigateRecord:l,onCopyCallLink:u})}),e.jsx(T,{title:"Raw Evidence",subtitle:"Explicit localhost request only",className:"span-all",children:e.jsx(Ve,{call:a,contextRuntime:s,onContextApiEnabledChange:i,onEvidenceStateChange:w,fullAnalysisRequestNonce:S.recordId===a.id?S.nonce:0},a.id)})]})}function Be({call:t,previous:n,evidenceState:s,positionLabel:i}){const l=A();return e.jsxs("div",{className:"investigation-readout-grid",children:[e.jsx(M,{label:l.t("call.readout.exact_label","Exact callback accounting"),body:Oe(t,l)}),e.jsx(M,{label:l.t("call.readout.previous_label","Compared previous call"),body:n?De(t,n):$e(l)}),e.jsx(M,{label:l.t("call.readout.evidence_label","Evidence state"),body:qe(s,l)}),e.jsx(M,{label:l.t("call.readout.next_label","Next diagnostic move"),body:Fe(t,n),detail:He(i)})]})}function M({label:t,body:n,detail:s}){return e.jsxs("div",{className:"investigation-readout-card",children:[e.jsx("span",{children:t}),e.jsx("p",{children:n}),s?e.jsx("small",{children:s}):null]})}function Ve({call:t,contextRuntime:n,onContextApiEnabledChange:s,onEvidenceStateChange:i,fullAnalysisRequestNonce:l}){const u=A(),[o,p]=C.useState(()=>pe(window.location.search,be(t.id)??K)),[c,j]=C.useState({status:"idle"}),b=!!n.apiToken&&!n.fileMode,x=b&&n.contextApiEnabled;C.useEffect(()=>{const a=B(t.id,o);j(a?{status:"loaded",payload:a}:{status:"idle"})},[t.id,o.includeToolOutput,o.includeCompactionHistory,o.maxChars,o.maxEntries,o.mode]),C.useEffect(()=>{i(c)},[c,i]),C.useEffect(()=>{if(l<=0||!x||c.status==="loading")return;const a={...o,mode:"full"};N(a),d(a,"Loading full turn analysis...")},[l]);async function k(){j({status:"loading",message:"Enabling localhost context API..."});try{const a=await fe(n);s(a),j({status:"idle",message:a?"Context API enabled. Load this call when ready.":"Context API did not enable."})}catch(a){j({status:"error",message:q(a)})}}async function d(a=o,r="Loading selected-turn evidence..."){$(t.id,a);const g=B(t.id,a);if(g){j({status:"loaded",payload:g});return}j({status:"loading",message:r});try{const m=await ge(t.id,n,a);je(t.id,a,m),j({status:"loaded",payload:m})}catch(m){j({status:"error",message:q(m)})}}function w(){if(!x||c.status==="loading")return;const a={...o,mode:"full"};N(a),d(a,"Loading full turn analysis...")}function S(a){const r=Ne(a,o);N(r),d(r,"Loading older context...")}function L(){const a={...o,includeToolOutput:!0};N(a),d(a,"Loading omitted tool output...")}function I(){const a={...o,includeCompactionHistory:!0};N(a),d(a,"Loading compacted replacement...")}function N(a){p(a),$(t.id,a);const r=new URL(window.location.href);V(r,a),window.history.replaceState(null,"",r)}function E(a,r){p(g=>{const m={...g,[a]:r};$(t.id,m);const v=new URL(window.location.href);return V(v,m),window.history.replaceState(null,"",v),m})}return e.jsxs("div",{className:"investigator-evidence",children:[e.jsxs("div",{className:"locked-context-card",children:[e.jsx(Le,{size:20}),e.jsxs("div",{children:[e.jsx("strong",{children:"Raw context is gated"}),e.jsx("p",{children:ve(n)})]})]}),e.jsxs("div",{className:"context-action-grid",children:[e.jsx("button",{className:"toolbar-button",type:"button",onClick:k,disabled:!b||n.contextApiEnabled||c.status==="loading",children:u.t("button.enable_context_loading","Enable context loading")}),e.jsx("button",{className:"primary-button",type:"button",onClick:()=>d(),disabled:!x||c.status==="loading",children:u.t("button.show_turn_evidence","Show turn log evidence")}),e.jsx("button",{className:"toolbar-button",type:"button",onClick:w,disabled:!x||c.status==="loading",children:u.t("button.full_serialized_analysis","Run full serialized analysis")}),e.jsxs("label",{className:"context-field",children:[e.jsx("span",{children:"Mode"}),e.jsxs("select",{"aria-label":"Context mode",value:o.mode,disabled:!x||c.status==="loading",onChange:a=>E("mode",a.target.value==="full"?"full":"quick"),children:[e.jsx("option",{value:"quick",children:"Quick"}),e.jsx("option",{value:"full",children:"Full"})]})]}),e.jsxs("label",{className:"context-field",children:[e.jsx("span",{children:"Entries"}),e.jsxs("select",{"aria-label":"Context entries",value:String(o.maxEntries),disabled:!x||c.status==="loading",onChange:a=>E("maxEntries",Number(a.target.value)),children:[e.jsx("option",{value:"20",children:"20"}),e.jsx("option",{value:"50",children:"50"}),e.jsx("option",{value:"100",children:"100"}),e.jsx("option",{value:"0",children:"All"})]})]}),e.jsxs("label",{className:"toggle-row",children:[e.jsx("input",{type:"checkbox",checked:o.includeToolOutput,disabled:!x||c.status==="loading",onChange:a=>E("includeToolOutput",a.target.checked)}),u.t("button.include_tool_output","Include tool output")]}),e.jsxs("label",{className:"toggle-row",children:[e.jsx("input",{type:"checkbox",checked:o.includeCompactionHistory,disabled:!x||c.status==="loading",onChange:a=>E("includeCompactionHistory",a.target.checked)}),"Include compaction history"]}),e.jsxs("label",{className:"toggle-row",children:[e.jsx("input",{type:"checkbox",checked:o.maxChars===0,disabled:!x||c.status==="loading",onChange:a=>E("maxChars",a.target.checked?0:K.maxChars)}),u.t("button.no_char_limit","No char limit")]})]}),c.status==="idle"&&c.message?e.jsx("p",{className:"context-state-note",children:c.message}):null,c.status==="loading"?e.jsx("p",{className:"context-state-note",children:c.message}):null,c.status==="error"?e.jsx("p",{className:"context-state-note error",children:c.message}):null,c.status==="loaded"?e.jsx(Je,{payload:c.payload,onLoadOlder:S,onLoadCompactionHistory:I,onLoadToolOutput:L}):null,e.jsx("p",{className:"privacy-note",children:"Raw context is read from the local JSONL source only after this explicit action and is not embedded in static dashboard HTML."})]})}function Je({payload:t,onLoadOlder:n,onLoadCompactionHistory:s,onLoadToolOutput:i}){var E,a;const l=A(),u=t.entries??[],o=t.omitted??{},p=Number(o.older_entries??0),c=ye(t),j=10,b=String(t.record_id??""),[x,k]=C.useState(()=>J(b)),[d,w]=C.useState(()=>W(b,u));C.useEffect(()=>{k(J(b)),w(W(b,u))},[u.length,t.context_mode,t.include_compaction_history,t.include_tool_output,(E=t.omitted)==null?void 0:E.max_chars,(a=t.omitted)==null?void 0:a.max_entries,t.record_id,b]);const S=x?u:u.slice(0,j),L=Math.max(u.length-S.length,0);function I(){k(r=>{const g=!r;return Se(b,g),g})}function N(r,g){Ee(b,r,g),w(m=>{const v=new Set(m);return g?v.add(r):v.delete(r),v})}return e.jsxs("div",{className:"context-evidence",children:[e.jsxs("div",{className:"context-evidence-summary",children:[e.jsx(f,{label:"Entries",value:h(u.length),detail:String(t.context_mode??"quick")}),e.jsx(f,{label:"Visible chars",value:h(Number(t.visible_char_count??0)),detail:"redacted local text"}),e.jsx(f,{label:"Visible tokens",value:h(Number(t.visible_token_estimate??0)),detail:"estimator"}),e.jsx(f,{label:"Older omitted",value:h(p),detail:"entry budget"})]}),c.length?e.jsx("p",{className:"context-note",children:c.join(" ")}):null,p>0?e.jsx("div",{className:"context-followup-actions",children:e.jsx("button",{className:"toolbar-button",type:"button",onClick:()=>n(t),children:l.t("button.load_older_context","Load older entries")})}):null,e.jsxs("div",{className:"context-entry-list",children:[S.map((r,g)=>{const m=Ce(r,g);return e.jsxs("details",{className:"context-entry",open:d.has(m),onToggle:v=>N(m,v.currentTarget.open),children:[e.jsx("summary",{className:"context-entry-summary",children:e.jsxs("div",{className:"context-entry-meta",children:[e.jsx("strong",{children:r.label||r.role||r.type||`Entry ${g+1}`}),e.jsx("span",{children:r.line_number?`line ${r.line_number}`:r.timestamp||"local evidence"})]})}),e.jsx(we,{entry:r}),r.tool_output_omitted?e.jsx("div",{className:"context-entry-actions",children:e.jsx("button",{className:"toolbar-button",type:"button",onClick:i,children:l.t("button.show_tool_output","Show tool output")})}):null,e.jsx(We,{entry:r,onLoadCompactionHistory:s}),e.jsx("pre",{ref:v=>{v&&(v.scrollTop=_e(b,m))},onScroll:v=>ke(b,m,v.currentTarget.scrollTop),children:r.text||"[no visible text]"})]},m)}),u.length?null:e.jsx("p",{className:"empty-state",children:"No visible evidence entries returned for this call."})]}),u.length>j?e.jsxs("div",{className:"context-followup-actions",children:[e.jsx("button",{className:"toolbar-button",type:"button",onClick:I,children:x?`Show first ${h(j)} entries`:`Show all ${h(u.length)} returned entries`}),x?null:e.jsxs("span",{className:"context-entry-count-note",children:[h(L)," entries hidden in compact view"]})]}):null]})}function We({entry:t,onLoadCompactionHistory:n}){const s=A(),i=t.compaction;if(!(i!=null&&i.replacement_history_available))return null;const l=i.replacement_history??[],u=Number(i.replacement_entry_count??l.length);return e.jsxs("div",{className:"context-entry-compaction",children:[e.jsx("strong",{children:"Compaction detected"}),e.jsxs("span",{children:[h(u)," replacement history entries available."]}),l.length?e.jsx("div",{className:"context-replacement-history","aria-label":"Compacted replacement context",children:l.map((o,p)=>e.jsxs("div",{className:"context-replacement-entry",children:[e.jsx("strong",{children:o.label||`Replacement item ${p+1}`}),e.jsx("pre",{children:o.text||"[no visible replacement text]"})]},`${o.label??"replacement"}-${p}`))}):e.jsx("div",{className:"context-entry-actions",children:e.jsx("button",{className:"toolbar-button",type:"button",onClick:n,children:s.t("button.show_compaction_history","Show compacted replacement")})})]})}function Xe({call:t,calls:n,onNavigateRecord:s,onCopyCallLink:i}){const u=A().t("button.copy_link","Copy link"),o=n.length?n:[t],p=Math.max(o.findIndex(d=>d.id===t.id),0),c=o.reduce((d,w)=>d+w.totalTokens,0),j=o.reduce((d,w)=>d+w.cost,0),b=o.reduce((d,w)=>d+w.cachedPct,0)/Math.max(o.length,1),x=o.filter(d=>Number(d.contextWindowPct??0)>=60).length,k=o.filter(d=>d.cachedPct<25||d.signal==="cache-risk").length;return e.jsxs("div",{className:"thread-context-module",children:[e.jsxs("div",{className:"drilldown-metric-grid wide",children:[e.jsx(f,{label:"Thread calls",value:h(o.length),detail:`selected ${p+1} of ${o.length}`}),e.jsx(f,{label:"Thread tokens",value:F(c),detail:"loaded aggregate rows"}),e.jsx(f,{label:"Thread cost",value:Q(j),detail:"estimated aggregate"}),e.jsx(f,{label:"Avg cache",value:U(b),detail:O(o.map(d=>d.model))}),e.jsx(f,{label:"Cache risks",value:h(k),detail:"weak reuse or flagged"}),e.jsx(f,{label:"Context pressure",value:h(x),detail:">=60% context window"})]}),e.jsxs("div",{className:"thread-context-grid",children:[e.jsxs("div",{children:[e.jsx("h3",{children:"Thread timeline"}),e.jsx(xe,{selectedCall:t,calls:o,onOpenInvestigator:s,onCopyCallLink:i,className:"investigator-thread-timeline",copyAriaContext:"thread context call",copyLabel:u})]}),e.jsxs("dl",{className:"detail-list compact",children:[e.jsx(y,{label:"Project",value:t.project||"Unknown"}),e.jsx(y,{label:"Project path",value:t.projectRelativeCwd||t.cwd||"."}),e.jsx(y,{label:"Source line",value:me(t)}),e.jsx(y,{label:"Session",value:t.sessionId||"Not available"}),e.jsx(y,{label:"Parent thread",value:t.parentThread||"None"}),e.jsx(y,{label:"Models in thread",value:O(o.map(d=>d.model),{limit:3})}),e.jsx(y,{label:"Effort mix",value:O(o.map(d=>d.effort),{limit:3})})]})]})]})}function f({label:t,value:n,detail:s}){return e.jsxs("span",{className:"drilldown-metric",children:[e.jsx("small",{children:t}),e.jsx("strong",{children:n}),e.jsx("em",{children:s})]})}function y({label:t,value:n}){return e.jsxs("div",{children:[e.jsx("dt",{children:t}),e.jsx("dd",{children:n})]})}function Qe({call:t}){const s=[{label:"Cached input",value:Math.max(t.input-t.uncachedInput,0),color:"#2563eb"},{label:"Uncached input",value:t.uncachedInput,color:"#f59e0b"},{label:"Output",value:t.output,color:"#059669"},{label:"Reasoning",value:t.reasoningOutput,color:"#7c3aed"}].filter(l=>l.value>0),i=Math.max(s.reduce((l,u)=>l+u.value,0),1);return e.jsxs("div",{className:"composition-card",children:[e.jsxs("div",{className:"composition-head",children:[e.jsx("strong",{children:"Token composition"}),e.jsxs("span",{children:[F(i)," visible tokens"]})]}),e.jsx("div",{className:"composition-bar",role:"img","aria-label":"Token composition",children:s.map(l=>e.jsx("i",{style:{width:`${Math.max(l.value/i*100,3)}%`,background:l.color}},l.label))}),e.jsx("div",{className:"composition-legend",children:s.map(l=>e.jsxs("span",{children:[e.jsx("i",{style:{background:l.color}}),l.label]},l.label))})]})}export{ct as CallInvestigatorPage}; + */const X=G("ArrowLeft",[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]]),D=new Map;function ze(t,n){Oe(t,n);const s=`${n.apiToken}:${t}`,i=D.get(s);if(i)return i;const l=Me(t,n).finally(()=>{D.delete(s)});return D.set(s,l),l}async function Me(t,n){var o;const s=new URLSearchParams({record_id:t,_:String(Date.now())}),i=await fetch(`/api/call?${s.toString()}`,{headers:{Accept:"application/json","X-Codex-Usage-Token":n.apiToken},cache:"no-store"}),l=await $e(i,"Call detail");if(!((o=l.record)!=null&&o.record_id))throw new Error("Call detail response did not include the requested aggregate record.");const u=Array.isArray(l.adjacent_records)&&l.adjacent_records.length?l.adjacent_records:[l.previous_record,l.record,l.next_record].filter(p=>!!p);return{record:R(l.record,0),previousRecord:l.previous_record?R(l.previous_record,-1):null,nextRecord:l.next_record?R(l.next_record,1):null,adjacentRecords:u.map((p,c)=>R(p,c)),rawPayload:l}}function Oe(t,n){if(!t)throw new Error("record_id is required for call detail loading.");if(n.fileMode)throw new Error("Call detail hydration requires the localhost dashboard server.");if(!n.apiToken)throw new Error("Call detail hydration requires a localhost dashboard API token.")}async function $e(t,n){let s={};try{s=await t.json()}catch{s={}}if(!t.ok){const i=typeof s.error=="string"?s.error:`${n} request failed (${t.status})`;throw new Error(i)}return s}function De(t,n){return H(n.t("call.readout.exact_body","{input} input tokens = {cached} cached + {uncached} uncached; {output} output tokens; {cache} cache reuse."),{input:h(t.input),cached:h(t.cachedInput),uncached:h(t.uncachedInput),output:h(t.output),cache:U(t.cachedPct)})}function qe(t){return t.t("call.readout.previous_unavailable","No previous call is loaded in resolved thread, call-to-call deltas unavailable.")}function Ue(t,n){const s=t.uncachedInput-n.uncachedInput,i=t.cachedInput-n.cachedInput;return s>0&&i<0?`Fresh input rose by ${h(s)} while cached input fell by ${h(Math.abs(i))}; classic cache-drop profile.`:s>0?`Fresh input increased by ${h(s)} from previous call; inspect evidence new files, tool results, or rewritten context.`:s<0&&i>=0?`Fresh input fell by ${h(Math.abs(s))} while cached input increased, so this call reused context more efficiently previous one.`:"Token accounting broadly stable compared previous call in resolved thread."}function Fe(t,n){var s;if(t.status==="loaded"){const i=((s=t.payload.entries)==null?void 0:s.length)??0,l=Number(t.payload.visible_char_count??0),u=Number(t.payload.visible_token_estimate??0),o=He(t.payload,n);return H(n.t("call.readout.evidence_analyzed","Evidence analyzed: {totalEntries} selected-turn entries, {visibleChars} visible redacted chars, {visibleTokens} visible tokens.{serializedDetail}"),{totalEntries:h(i),visibleChars:h(l),visibleTokens:h(u),estimator:t.payload.visible_token_estimator??"visible-token estimate",serializedDetail:o,renderedEntries:h(i)})}return t.status==="loading"?n.t("call.readout.evidence_loading",t.message):t.status==="error"?`Evidence request failed: ${t.message}`:"Evidence is not loaded yet. Aggregate token counts are exact, but visible-context attribution needs runtime evidence."}function He(t,n){const s=t.serialized_evidence??{};if(s.deferred||s.deferred_buckets)return n.t("call.readout.evidence_serialized_deferred","Fast serialized estimate only; full serialized grouping deferred.");const i=Ve(t);return i<=0?"":H(n.t("call.readout.evidence_serialized_bound"," Serialized local upper bound: {tokens} tokens."),{tokens:h(i),chars:h(Number(s.raw_json_char_count??s.total_chars??0))})}function Ke(t,n){const s=t.input>0?t.cachedInput/t.input:t.cachedPct/100,i=n&&n.input>0?n.cachedInput/n.input:n?n.cachedPct/100:null,l=(n==null?void 0:n.uncachedInput)??0;return n&&i!==null&&i>=.8&&s<=.05&&t.input>=1e3?"Compare previous call, then inspect loaded evidence see fresh context was sent after cache miss.":n&&t.uncachedInput>Math.max(l*2,1e3)?"Inspect most recent evidence entries first; spike is in fresh uncached input, not cached history.":s>=.85?`Cache reuse is healthy; focus on ${h(t.uncachedInput)} uncached tokens were still billed as fresh input.`:n?"Use delta cards to locate whether change came from cached input, uncached input, or output/reasoning.":"Use loaded evidence if aggregate totals are not enough understand this isolated call."}function Be(t){return t==="Hydrated from /api/call"?"Position: hydrated live record outside loaded snapshot":`Position: ${t}`}function H(t,n){return t.replace(/\{(\w+)\}/g,(s,i)=>n[i]??s)}function Ve(t){const n=t.serialized_evidence??{};return Number(n.raw_json_token_estimate??n.token_estimate??0)}function dt({model:t,recordId:n,contextRuntime:s,onContextApiEnabledChange:i,onNavigateRecord:l,onCopyCallLink:u,onBackToCalls:o,backLabel:p}){const c=A(),[j,b]=C.useState(""),[x,k]=C.useState({status:"idle"}),[d,w]=C.useState({status:"idle"}),[S,L]=C.useState({recordId:"",nonce:0}),I=x.status==="loaded"&&x.recordId===n?x.detail:null,{modelIndex:N,hydratedDetail:E,call:a,previous:r,next:g,threadCalls:m,positionLabel:v}=C.useMemo(()=>ee({calls:t.calls,recordId:n,detail:I}),[I,t.calls,n]),Y=d.status==="loaded"?d.payload:null;if(C.useEffect(()=>{w({status:"idle"})},[n]),C.useEffect(()=>{if(!n||N>=0){k({status:"idle"});return}if(s.fileMode||!s.apiToken){k({status:"error",recordId:n,message:"This call is outside the loaded snapshot. Serve the dashboard with its localhost API token to hydrate it."});return}let _=!1;return k({status:"loading",recordId:n,message:"Loading call detail from localhost..."}),ze(n,s).then(P=>{_||k({status:"loaded",recordId:n,detail:P})}).catch(P=>{_||k({status:"error",recordId:n,message:q(P)})}),()=>{_=!0}},[s,N,n]),!a){const _=x.status==="loading"||x.status==="error"?x.message:n?"Selected call is not present in the loaded dashboard rows.":"No aggregate call rows are loaded.";return e.jsx("div",{className:"page-grid",children:e.jsxs("div",{className:"page-title-row",children:[e.jsxs("div",{children:[e.jsx("h1",{children:"Call Investigator"}),e.jsx("p",{children:_})]}),e.jsxs("button",{className:"toolbar-button",type:"button",onClick:o,children:[e.jsx(X,{size:16})," ",p]})]})})}async function Z(){try{const _=new URL(window.location.href);if(ie(_,"call",ce(_.search)),!await re(_.toString()))throw new Error("Clipboard unavailable");b("Copied investigator link")}catch{b("Copy unavailable in this browser")}}return e.jsxs("div",{className:"call-investigator-layout",children:[e.jsxs("div",{className:"page-title-row span-all",children:[e.jsxs("div",{children:[e.jsx("h1",{children:"Call Investigator"}),e.jsxs("p",{children:[a.thread," / ",a.model]})]}),e.jsxs("div",{className:"toolbar",children:[e.jsxs("button",{className:"toolbar-button",type:"button",onClick:o,children:[e.jsx(X,{size:16})," ",p]}),e.jsxs("button",{className:"toolbar-button",type:"button",onClick:()=>r&&l(r.id),disabled:!r,children:[e.jsx(Ae,{size:16})," ",c.t("button.previous_call","Previous call")]}),e.jsxs("button",{className:"toolbar-button",type:"button",onClick:()=>g&&l(g.id),disabled:!g,children:[c.t("button.next_call","Next call")," ",e.jsx(Le,{size:16})]}),e.jsxs("button",{className:"toolbar-button",type:"button",onClick:Z,"aria-label":c.t("button.copy_investigator_link","Copy investigator link"),children:[e.jsx(te,{size:16})," ",c.t("button.copy_link","Copy link")]})]})]}),e.jsxs(T,{title:c.t("call.readout.title","Investigation Readout"),subtitle:j||v,className:"span-all",action:e.jsx(z,{label:c.t("call.readout.badge","Aggregate + on-demand evidence"),tone:"blue"}),children:[e.jsxs("div",{className:"call-summary",children:[e.jsx(z,{label:"Aggregate only",tone:"green"}),E?e.jsx(z,{label:"Hydrated live",tone:"blue"}):null,e.jsx(ae,{call:a}),e.jsx(z,{label:"Raw context gated",tone:"blue"}),e.jsx("span",{className:"call-id",children:a.id.slice(0,16)})]}),e.jsx(Je,{call:a,previous:r,evidenceState:d,positionLabel:v}),e.jsxs("div",{className:"drilldown-metric-grid wide",children:[e.jsx(f,{label:"Total tokens",value:h(a.totalTokens),detail:`${F(a.input)} input`}),e.jsx(f,{label:"Uncached input",value:h(a.uncachedInput),detail:"fresh billed input"}),e.jsx(f,{label:"Cache hit rate",value:U(a.cachedPct),detail:ne(a)}),e.jsx(f,{label:"Estimated cost",value:Q(a.cost),detail:se(a)}),e.jsx(f,{label:"Duration",value:a.duration,detail:le(a)}),e.jsx(f,{label:"Usage credits",value:a.credits?a.credits.toFixed(3):"-",detail:a.usageCreditConfidence})]}),e.jsx(ue,{call:a})]}),e.jsxs(T,{title:"Token Accounting",subtitle:"Exact aggregate row fields",children:[e.jsx(Ze,{call:a}),e.jsx(he,{call:a})]}),e.jsx(T,{title:"Cache Accounting",subtitle:"Derived from adjacent aggregate call",children:e.jsx(xe,{call:a,calls:m})}),e.jsx(T,{title:"Context Attribution",subtitle:"Estimated from visible log volume",className:"span-all",children:e.jsx(me,{call:a,payload:Y,onRunFullAnalysis:()=>L(_=>({recordId:a.id,nonce:_.nonce+1})),showHeading:!1})}),e.jsxs(T,{title:"Aggregate Identity",subtitle:"Local metadata only",children:[e.jsxs("dl",{className:"detail-list",children:[e.jsx(y,{label:"Record id",value:a.id}),e.jsx(y,{label:"Time",value:a.time}),e.jsx(y,{label:"Thread",value:a.thread}),e.jsx(y,{label:"Model",value:a.model}),e.jsx(y,{label:"Effort",value:a.effort}),e.jsx(y,{label:"Project",value:a.project||"Unknown"}),e.jsx(y,{label:"Context window",value:oe(a)}),e.jsx(y,{label:"Recommendation",value:a.recommendation||"No aggregate recommendation"})]}),e.jsx(pe,{call:a}),a.recommendation?e.jsxs("div",{className:"recommendation-box",children:[e.jsx(Pe,{size:16}),e.jsx("p",{children:a.recommendation})]}):null]}),e.jsx(T,{title:"Thread Context",subtitle:`${m.length} loaded related calls`,className:"span-all",children:e.jsx(Ye,{call:a,calls:m,onNavigateRecord:l,onCopyCallLink:u})}),e.jsx(T,{title:"Raw Evidence",subtitle:"Explicit localhost request only",className:"span-all",children:e.jsx(We,{call:a,contextRuntime:s,onContextApiEnabledChange:i,onEvidenceStateChange:w,fullAnalysisRequestNonce:S.recordId===a.id?S.nonce:0},a.id)})]})}function Je({call:t,previous:n,evidenceState:s,positionLabel:i}){const l=A();return e.jsxs("div",{className:"investigation-readout-grid",children:[e.jsx(M,{label:l.t("call.readout.exact_label","Exact callback accounting"),body:De(t,l)}),e.jsx(M,{label:l.t("call.readout.previous_label","Compared previous call"),body:n?Ue(t,n):qe(l)}),e.jsx(M,{label:l.t("call.readout.evidence_label","Evidence state"),body:Fe(s,l)}),e.jsx(M,{label:l.t("call.readout.next_label","Next diagnostic move"),body:Ke(t,n),detail:Be(i)})]})}function M({label:t,body:n,detail:s}){return e.jsxs("div",{className:"investigation-readout-card",children:[e.jsx("span",{children:t}),e.jsx("p",{children:n}),s?e.jsx("small",{children:s}):null]})}function We({call:t,contextRuntime:n,onContextApiEnabledChange:s,onEvidenceStateChange:i,fullAnalysisRequestNonce:l}){const u=A(),[o,p]=C.useState(()=>ge(window.location.search,je(t.id)??K)),[c,j]=C.useState({status:"idle"}),b=!!n.apiToken&&!n.fileMode,x=b&&n.contextApiEnabled;C.useEffect(()=>{const a=B(t.id,o);j(a?{status:"loaded",payload:a}:{status:"idle"})},[t.id,o.includeToolOutput,o.includeCompactionHistory,o.maxChars,o.maxEntries,o.mode]),C.useEffect(()=>{i(c)},[c,i]),C.useEffect(()=>{if(l<=0||!x||c.status==="loading")return;const a={...o,mode:"full"};N(a),d(a,"Loading full turn analysis...")},[l]);async function k(){j({status:"loading",message:"Enabling localhost context API..."});try{const a=await Ce(n);s(a),j({status:"idle",message:a?"Context API enabled. Load this call when ready.":"Context API did not enable."})}catch(a){j({status:"error",message:q(a)})}}async function d(a=o,r="Loading selected-turn evidence..."){$(t.id,a);const g=B(t.id,a);if(g){j({status:"loaded",payload:g});return}j({status:"loading",message:r});try{const m=await ve(t.id,n,a);fe(t.id,a,m),j({status:"loaded",payload:m})}catch(m){j({status:"error",message:q(m)})}}function w(){if(!x||c.status==="loading")return;const a={...o,mode:"full"};N(a),d(a,"Loading full turn analysis...")}function S(a){const r=Se(a,o);N(r),d(r,"Loading older context...")}function L(){const a={...o,includeToolOutput:!0};N(a),d(a,"Loading omitted tool output...")}function I(){const a={...o,includeCompactionHistory:!0};N(a),d(a,"Loading compacted replacement...")}function N(a){p(a),$(t.id,a);const r=new URL(window.location.href);V(r,a),window.history.replaceState(null,"",r)}function E(a,r){p(g=>{const m={...g,[a]:r};$(t.id,m);const v=new URL(window.location.href);return V(v,m),window.history.replaceState(null,"",v),m})}return e.jsxs("div",{className:"investigator-evidence",children:[e.jsxs("div",{className:"locked-context-card",children:[e.jsx(Re,{size:20}),e.jsxs("div",{children:[e.jsx("strong",{children:"Raw context is gated"}),e.jsx("p",{children:ye(n)})]})]}),e.jsxs("div",{className:"context-action-grid",children:[e.jsx("button",{className:"toolbar-button",type:"button",onClick:k,disabled:!b||n.contextApiEnabled||c.status==="loading",children:u.t("button.enable_context_loading","Enable context loading")}),e.jsx("button",{className:"primary-button",type:"button",onClick:()=>d(),disabled:!x||c.status==="loading",children:u.t("button.show_turn_evidence","Show turn log evidence")}),e.jsx("button",{className:"toolbar-button",type:"button",onClick:w,disabled:!x||c.status==="loading",children:u.t("button.full_serialized_analysis","Run full serialized analysis")}),e.jsxs("label",{className:"context-field",children:[e.jsx("span",{children:"Mode"}),e.jsxs("select",{"aria-label":"Context mode",value:o.mode,disabled:!x||c.status==="loading",onChange:a=>E("mode",a.target.value==="full"?"full":"quick"),children:[e.jsx("option",{value:"quick",children:"Quick"}),e.jsx("option",{value:"full",children:"Full"})]})]}),e.jsxs("label",{className:"context-field",children:[e.jsx("span",{children:"Entries"}),e.jsxs("select",{"aria-label":"Context entries",value:String(o.maxEntries),disabled:!x||c.status==="loading",onChange:a=>E("maxEntries",Number(a.target.value)),children:[e.jsx("option",{value:"20",children:"20"}),e.jsx("option",{value:"50",children:"50"}),e.jsx("option",{value:"100",children:"100"}),e.jsx("option",{value:"0",children:"All"})]})]}),e.jsxs("label",{className:"toggle-row",children:[e.jsx("input",{type:"checkbox",checked:o.includeToolOutput,disabled:!x||c.status==="loading",onChange:a=>E("includeToolOutput",a.target.checked)}),u.t("button.include_tool_output","Include tool output")]}),e.jsxs("label",{className:"toggle-row",children:[e.jsx("input",{type:"checkbox",checked:o.includeCompactionHistory,disabled:!x||c.status==="loading",onChange:a=>E("includeCompactionHistory",a.target.checked)}),"Include compaction history"]}),e.jsxs("label",{className:"toggle-row",children:[e.jsx("input",{type:"checkbox",checked:o.maxChars===0,disabled:!x||c.status==="loading",onChange:a=>E("maxChars",a.target.checked?0:K.maxChars)}),u.t("button.no_char_limit","No char limit")]})]}),c.status==="idle"&&c.message?e.jsx("p",{className:"context-state-note",children:c.message}):null,c.status==="loading"?e.jsx("p",{className:"context-state-note",children:c.message}):null,c.status==="error"?e.jsx("p",{className:"context-state-note error",children:c.message}):null,c.status==="loaded"?e.jsx(Xe,{payload:c.payload,onLoadOlder:S,onLoadCompactionHistory:I,onLoadToolOutput:L}):null,e.jsx("p",{className:"privacy-note",children:"Raw context is read from the local JSONL source only after this explicit action and is not embedded in static dashboard HTML."})]})}function Xe({payload:t,onLoadOlder:n,onLoadCompactionHistory:s,onLoadToolOutput:i}){var E,a;const l=A(),u=t.entries??[],o=t.omitted??{},p=Number(o.older_entries??0),c=we(t),j=10,b=String(t.record_id??""),[x,k]=C.useState(()=>J(b)),[d,w]=C.useState(()=>W(b,u));C.useEffect(()=>{k(J(b)),w(W(b,u))},[u.length,t.context_mode,t.include_compaction_history,t.include_tool_output,(E=t.omitted)==null?void 0:E.max_chars,(a=t.omitted)==null?void 0:a.max_entries,t.record_id,b]);const S=x?u:u.slice(0,j),L=Math.max(u.length-S.length,0);function I(){k(r=>{const g=!r;return Ie(b,g),g})}function N(r,g){Te(b,r,g),w(m=>{const v=new Set(m);return g?v.add(r):v.delete(r),v})}return e.jsxs("div",{className:"context-evidence",children:[e.jsxs("div",{className:"context-evidence-summary",children:[e.jsx(f,{label:"Entries",value:h(u.length),detail:String(t.context_mode??"quick")}),e.jsx(f,{label:"Visible chars",value:h(Number(t.visible_char_count??0)),detail:"redacted local text"}),e.jsx(f,{label:"Visible tokens",value:h(Number(t.visible_token_estimate??0)),detail:"estimator"}),e.jsx(f,{label:"Older omitted",value:h(p),detail:"entry budget"})]}),c.length?e.jsx("p",{className:"context-note",children:c.join(" ")}):null,p>0?e.jsx("div",{className:"context-followup-actions",children:e.jsx("button",{className:"toolbar-button",type:"button",onClick:()=>n(t),children:l.t("button.load_older_context","Load older entries")})}):null,e.jsxs("div",{className:"context-entry-list",children:[S.map((r,g)=>{const m=ke(r,g);return e.jsxs("details",{className:"context-entry",open:d.has(m),onToggle:v=>N(m,v.currentTarget.open),children:[e.jsx("summary",{className:"context-entry-summary",children:e.jsxs("div",{className:"context-entry-meta",children:[e.jsx("strong",{children:r.label||r.role||r.type||`Entry ${g+1}`}),e.jsx("span",{children:r.line_number?`line ${r.line_number}`:r.timestamp||"local evidence"})]})}),e.jsx(_e,{entry:r}),r.tool_output_omitted?e.jsx("div",{className:"context-entry-actions",children:e.jsx("button",{className:"toolbar-button",type:"button",onClick:i,children:l.t("button.show_tool_output","Show tool output")})}):null,e.jsx(Qe,{entry:r,onLoadCompactionHistory:s}),e.jsx("pre",{ref:v=>{v&&(v.scrollTop=Ee(b,m))},onScroll:v=>Ne(b,m,v.currentTarget.scrollTop),children:r.text||"[no visible text]"})]},m)}),u.length?null:e.jsx("p",{className:"empty-state",children:"No visible evidence entries returned for this call."})]}),u.length>j?e.jsxs("div",{className:"context-followup-actions",children:[e.jsx("button",{className:"toolbar-button",type:"button",onClick:I,children:x?`Show first ${h(j)} entries`:`Show all ${h(u.length)} returned entries`}),x?null:e.jsxs("span",{className:"context-entry-count-note",children:[h(L)," entries hidden in compact view"]})]}):null]})}function Qe({entry:t,onLoadCompactionHistory:n}){const s=A(),i=t.compaction;if(!(i!=null&&i.replacement_history_available))return null;const l=i.replacement_history??[],u=Number(i.replacement_entry_count??l.length);return e.jsxs("div",{className:"context-entry-compaction",children:[e.jsx("strong",{children:"Compaction detected"}),e.jsxs("span",{children:[h(u)," replacement history entries available."]}),l.length?e.jsx("div",{className:"context-replacement-history","aria-label":"Compacted replacement context",children:l.map((o,p)=>e.jsxs("div",{className:"context-replacement-entry",children:[e.jsx("strong",{children:o.label||`Replacement item ${p+1}`}),e.jsx("pre",{children:o.text||"[no visible replacement text]"})]},`${o.label??"replacement"}-${p}`))}):e.jsx("div",{className:"context-entry-actions",children:e.jsx("button",{className:"toolbar-button",type:"button",onClick:n,children:s.t("button.show_compaction_history","Show compacted replacement")})})]})}function Ye({call:t,calls:n,onNavigateRecord:s,onCopyCallLink:i}){const u=A().t("button.copy_link","Copy link"),o=n.length?n:[t],p=Math.max(o.findIndex(d=>d.id===t.id),0),c=o.reduce((d,w)=>d+w.totalTokens,0),j=o.reduce((d,w)=>d+w.cost,0),b=o.reduce((d,w)=>d+w.cachedPct,0)/Math.max(o.length,1),x=o.filter(d=>Number(d.contextWindowPct??0)>=60).length,k=o.filter(d=>d.cachedPct<25||d.signal==="cache-risk").length;return e.jsxs("div",{className:"thread-context-module",children:[e.jsxs("div",{className:"drilldown-metric-grid wide",children:[e.jsx(f,{label:"Thread calls",value:h(o.length),detail:`selected ${p+1} of ${o.length}`}),e.jsx(f,{label:"Thread tokens",value:F(c),detail:"loaded aggregate rows"}),e.jsx(f,{label:"Thread cost",value:Q(j),detail:"estimated aggregate"}),e.jsx(f,{label:"Avg cache",value:U(b),detail:O(o.map(d=>d.model))}),e.jsx(f,{label:"Cache risks",value:h(k),detail:"weak reuse or flagged"}),e.jsx(f,{label:"Context pressure",value:h(x),detail:">=60% context window"})]}),e.jsxs("div",{className:"thread-context-grid",children:[e.jsxs("div",{children:[e.jsx("h3",{children:"Thread timeline"}),e.jsx(be,{selectedCall:t,calls:o,onOpenInvestigator:s,onCopyCallLink:i,className:"investigator-thread-timeline",copyAriaContext:"thread context call",copyLabel:u})]}),e.jsxs("dl",{className:"detail-list compact",children:[e.jsx(y,{label:"Project",value:t.project||"Unknown"}),e.jsx(y,{label:"Project path",value:t.projectRelativeCwd||t.cwd||"."}),e.jsx(y,{label:"Source line",value:de(t)}),e.jsx(y,{label:"Session",value:t.sessionId||"Not available"}),e.jsx(y,{label:"Parent thread",value:t.parentThread||"None"}),e.jsx(y,{label:"Models in thread",value:O(o.map(d=>d.model),{limit:3})}),e.jsx(y,{label:"Effort mix",value:O(o.map(d=>d.effort),{limit:3})})]})]})]})}function f({label:t,value:n,detail:s}){return e.jsxs("span",{className:"drilldown-metric",children:[e.jsx("small",{children:t}),e.jsx("strong",{children:n}),e.jsx("em",{children:s})]})}function y({label:t,value:n}){return e.jsxs("div",{children:[e.jsx("dt",{children:t}),e.jsx("dd",{children:n})]})}function Ze({call:t}){const s=[{label:"Cached input",value:Math.max(t.input-t.uncachedInput,0),color:"#2563eb"},{label:"Uncached input",value:t.uncachedInput,color:"#f59e0b"},{label:"Output",value:t.output,color:"#059669"},{label:"Reasoning",value:t.reasoningOutput,color:"#7c3aed"}].filter(l=>l.value>0),i=Math.max(s.reduce((l,u)=>l+u.value,0),1);return e.jsxs("div",{className:"composition-card",children:[e.jsxs("div",{className:"composition-head",children:[e.jsx("strong",{children:"Token composition"}),e.jsxs("span",{children:[F(i)," visible tokens"]})]}),e.jsx("div",{className:"composition-bar",role:"img","aria-label":"Token composition",children:s.map(l=>e.jsx("i",{style:{width:`${Math.max(l.value/i*100,3)}%`,background:l.color}},l.label))}),e.jsx("div",{className:"composition-legend",children:s.map(l=>e.jsxs("span",{children:[e.jsx("i",{style:{background:l.color}}),l.label]},l.label))})]})}export{dt as CallInvestigatorPage}; diff --git a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/CallsPage.js b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/CallsPage.js index 2c78fd7d..f5e4e4f7 100644 --- a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/CallsPage.js +++ b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/CallsPage.js @@ -1,16 +1,16 @@ -import{j as t,r as c}from"./dashboard-react.js";import{u as Lt}from"./useInfiniteQuery.js";import{c as kt,d as It}from"./exploreQueries.js";import{c as Re,W as $t,u as ce,j as O,C as Rt,a as dt,G as At,U as ut,H as Ot,X as ht,f as ee,p as q,m as Ae,Y as Bt,Z as Vt,D as Ht,R as Kt,_ as mt,E as zt,d as Ut,r as qt,e as Gt,g as Qt}from"./App.js";import{r as Wt,u as Ze}from"./filtering.js";import{L as et}from"./LineChart.js";import{P as Z}from"./Panel.js";import{S as re}from"./StatusBadge.js";import{E as Yt}from"./EvidenceGrid.js";import{b as Xt,c as Jt}from"./tableActions.js";import{u as Zt}from"./useQuery.js";import{d as tt,c as en,a as nt,b as tn,e as nn,f as st,r as at,l as sn,g as an,h as on,i as it,j as ot,C as rn,k as ln,m as cn,n as dn,o as un,p as hn,q as mn,s as xn,t as Oe,u as pn,v as fn,T as xt,w as gn,x as jn,y as vn}from"./contextEvidenceState.js";import{L as pt}from"./lock-keyhole.js";import{S as bn}from"./search.js";import{S as Cn}from"./shield-check.js";import{a as yn}from"./EvidenceGridControls.js";import"./useBaseQuery.js";import"./queryOptions.js";import"./infiniteQueryOptions.js";import"./locale-zh-Hans.js";import"./dashboardRouter.js";import"./router.js";import"./index2.js";import"./rowActionEvents.js";/** +import{j as t,r as c}from"./dashboard-react.js";import{u as Lt}from"./useInfiniteQuery.js";import{c as kt,d as It}from"./exploreQueries.js";import{c as Re,W as $t,u as ce,j as O,C as Rt,a as dt,G as At,U as ut,H as Ot,X as ht,f as ee,Y as Ae,p as q,Z as Bt,m as Oe,_ as Vt,$ as Ht,a0 as Kt,a1 as zt,D as Ut,R as qt,a2 as mt,E as Gt,d as Qt,r as Wt,e as Yt,g as Xt}from"./App.js";import{r as Jt,u as Ze}from"./filtering.js";import{L as et}from"./LineChart.js";import{P as Z}from"./Panel.js";import{S as re}from"./StatusBadge.js";import{E as Zt}from"./EvidenceGrid.js";import{b as en,c as tn}from"./tableActions.js";import{u as nn}from"./useQuery.js";import{d as tt,c as sn,a as nt,b as an,e as on,f as st,r as at,l as rn,g as ln,h as cn,i as it,j as ot,C as dn,k as un,m as hn,n as mn,o as xn,p as pn,q as fn,s as gn,t as jn,u as vn,T as xt,v as bn,w as Cn}from"./contextEvidenceState.js";import{L as pt}from"./lock-keyhole.js";import{S as yn}from"./search.js";import{S as Sn}from"./shield-check.js";import{a as wn}from"./EvidenceGridControls.js";import"./useBaseQuery.js";import"./queryOptions.js";import"./infiniteQueryOptions.js";import"./locale-zh-Hans.js";import"./dashboardRouter.js";import"./router.js";import"./index2.js";import"./rowActionEvents.js";/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Sn=Re("GitBranch",[["line",{x1:"6",x2:"6",y1:"3",y2:"15",key:"17qcm7"}],["circle",{cx:"18",cy:"6",r:"3",key:"1h7g24"}],["circle",{cx:"6",cy:"18",r:"3",key:"fqmcym"}],["path",{d:"M18 9a9 9 0 0 1-9 9",key:"n2h4wq"}]]);/** + */const Fn=Re("GitBranch",[["line",{x1:"6",x2:"6",y1:"3",y2:"15",key:"17qcm7"}],["circle",{cx:"18",cy:"6",r:"3",key:"1h7g24"}],["circle",{cx:"6",cy:"18",r:"3",key:"fqmcym"}],["path",{d:"M18 9a9 9 0 0 1-9 9",key:"n2h4wq"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const wn=Re("PanelRightClose",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M15 3v18",key:"14nvp0"}],["path",{d:"m8 9 3 3-3 3",key:"12hl5m"}]]);/** + */const Nn=Re("PanelRightClose",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M15 3v18",key:"14nvp0"}],["path",{d:"m8 9 3 3-3 3",key:"12hl5m"}]]);/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Fn=Re("PanelRightOpen",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M15 3v18",key:"14nvp0"}],["path",{d:"m10 15-3-3 3-3",key:"1pgupc"}]]);function Nn(e){const n=[],s=[e.localQuery.trim(),e.globalQuery.trim()].filter(Boolean);s.length&&n.push(`Search ${s.map(i=>`"${i}"`).join(" + ")}`),e.modelFilter!=="all"&&n.push(`Model ${e.modelFilter}`),e.effortFilter!=="all"&&n.push(`Effort ${e.effortFilter}`),e.confidenceFilter!=="all"&&n.push(`Confidence ${Pn(e.confidenceFilter)}`),e.sourceFilter!=="all"&&n.push(`Source ${_n(e.sourceFilter)}`),e.dateRangeStatus.invalid?n.push("Date range invalid"):(e.dateRangeStatus.active||e.timeFilter!=="all")&&n.push(e.dateRangeStatus.label||Ln(e.timeFilter)),e.activePresetLabel&&n.push(`Preset ${e.activePresetLabel}`);const a=`Showing ${e.shownCount.toLocaleString()} of ${e.totalCount.toLocaleString()} aggregate rows`;return n.length?`${a} - Filters: ${n.join("; ")}`:a}function En(e,n){return n==="project"?`${e.project.toLocaleString()} project/cwd rows`:n==="session"?`${e.session.toLocaleString()} session-linked rows`:n==="git"?`${e.git.toLocaleString()} git rows`:n==="source-file"?`${e.sourceFile.toLocaleString()} source-file rows`:n==="missing"?`${e.missing.toLocaleString()} rows missing source`:`${e.project.toLocaleString()} project, ${e.session.toLocaleString()} session, ${e.git.toLocaleString()} git`}function Tn(e,n){return n==="all"?!0:n==="project"?Be(e):n==="session"?Ve(e):n==="git"?He(e):n==="source-file"?Ke(e):!ft(e)}function Mn(e,n){if(n==="all")return!0;if(n==="cost-exact")return!e.pricingEstimated&&Number.isFinite(e.cost)&&e.cost>0;if(n==="cost-estimated")return e.pricingEstimated;if(n==="cost-unpriced")return!e.pricingEstimated&&(!Number.isFinite(e.cost)||e.cost<=0);const s=e.usageCreditConfidence.toLowerCase();return n==="credit-exact"?s.includes("exact"):n==="credit-override"?s.includes("override"):n==="credit-estimated"?s.includes("estimated"):s.includes("missing")||s.includes("unpriced")}function Dn(e){return e.reduce((n,s)=>({project:n.project+(Be(s)?1:0),session:n.session+(Ve(s)?1:0),git:n.git+(He(s)?1:0),sourceFile:n.sourceFile+(Ke(s)?1:0),missing:n.missing+(ft(s)?0:1),total:n.total+1}),{project:0,session:0,git:0,sourceFile:0,missing:0,total:0})}function Be(e){return!!(e.project||e.projectRelativeCwd||e.cwd||e.projectTags.length)}function Ve(e){return!!(e.sessionId||e.turnId||e.parentSessionId)}function He(e){return!!(e.gitBranch||e.gitRemoteLabel||e.gitRemoteHash)}function Ke(e){return!!(e.sourceFile||e.lineNumber!==null)}function ft(e){return Be(e)||Ve(e)||He(e)||Ke(e)}function Pn(e){return e==="cost-exact"?"exact cost":e==="cost-estimated"?"estimated cost":e==="cost-unpriced"?"unpriced cost":e==="credit-exact"?"exact credit rate":e==="credit-estimated"?"estimated credit mapping":e==="credit-override"?"user credit override":"missing credit rate"}function _n(e){return e==="project"?"project/cwd":e==="session"?"session-linked":e==="git"?"git metadata":e==="source-file"?"source file":"missing source"}function Ln(e){return e==="today"?"Today":e==="this-week"?"This week":e==="last-7-days"?"Last 7 days":e==="this-month"?"This month":e==="custom"?"Custom date range":"All time"}const oe="__detail_first__",kn=new Set(["all","cost-exact","cost-estimated","cost-unpriced","credit-exact","credit-estimated","credit-override","credit-missing"]),In=new Set(["all","today","this-week","last-7-days","this-month","custom"]),$n=new Set(["all","project","session","git","source-file","missing"]),Rn=new Set(["time","duration","gap","attention","thread","initiator","model","effort","total","cached","uncached","output","reasoning","cost","usage","cache","context"]);function E(e,n=window.location.href){var s;return((s=new URL(n).searchParams.get(e))==null?void 0:s.trim())??""}function gt(e=window.location.href){const n=E("confidence",e)||E("pricing",e),s=Hn(n);return kn.has(s)?s:"all"}function jt(e=window.location.href){const n=E("source",e);return $n.has(n)?n:"all"}function vt(e=window.location.href){const n=E("date",e)||E("time",e);return In.has(n)?n:"all"}function le(e,n=window.location.href){return Le(E(e,n))}function An(e=window.location.href){const n=E("record",e);return n||(E("detail",e)==="first"?oe:null)}function Pe(e=window.location.href){const n=E("sort",e);return bt(n)}function bt(e){return Rn.has(e)?e:"time"}function Ct(e,n=window.location.href){const s=E("direction",n);return s==="asc"||s==="desc"?s:U(e)}function On(e=window.location.href){return E("density",e)==="roomy"?"roomy":"dense"}function Bn(e,n=window.location.href){const s=Number(E("page",n)||1);return Number.isFinite(s)&&s>1?Math.floor(s)*e:e}function _e(e,n=window.location.href){const s=new URL(n);return s.searchParams.set("view","calls"),s.searchParams.delete("detail"),s.searchParams.delete("pricing"),k(s,"record",e.selectedRecordId,""),k(s,"call_q",e.localQuery.trim(),""),k(s,"model",e.modelFilter,"all"),k(s,"effort",e.effortFilter,"all"),k(s,"confidence",e.confidenceFilter,"all"),k(s,"source",e.sourceFilter,"all"),k(s,"time",e.timeFilter,"all"),k(s,"date",e.timeFilter,"all"),k(s,"from",e.timeFilter==="custom"?e.dateStart:"",""),k(s,"to",e.timeFilter==="custom"?e.dateEnd:"",""),k(s,"sort",e.sortKey,"time"),k(s,"direction",e.sortDirection,U(e.sortKey)),k(s,"density",e.density,"dense"),k(s,"page",String(Vn(e.visibleRowCount,e.pageSize)),"1"),s}function U(e){return e==="cache"||e==="effort"||e==="initiator"||e==="model"||e==="thread"?"asc":"desc"}function Le(e){const n=Kn(e);return n?zn(n):""}function Vn(e,n){return Math.max(1,Math.ceil(Math.max(e,n)/n))}function k(e,n,s,a){if(!s||s===a){e.searchParams.delete(n);return}e.searchParams.set(n,s)}function Hn(e){return e==="official"?"cost-exact":e==="estimated"?"cost-estimated":e==="unpriced"?"cost-unpriced":e}function Kn(e){if(!/^\d{4}-\d{2}-\d{2}$/.test(e))return null;const[n,s,a]=e.split("-").map(Number),i=new Date(n,s-1,a);return i.getFullYear()===n&&i.getMonth()===s-1&&i.getDate()===a?i:null}function zn(e){return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`}function yt(e,n){return e.filter(s=>Un(s,n))}function ke(e,n,s){return[...e].sort((a,i)=>qn(a,i,n,s))}function ze(e,n,s,a){const i=Wn(a);if(e==="custom"){const o=lt(n),r=lt(s);return o&&r&&o>r?{active:!0,invalid:!0,start:o,endExclusive:K(r,1),label:"Invalid date range"}:{active:!!(o||r),invalid:!1,start:o,endExclusive:r?K(r,1):null,label:J("Custom",o,r)}}if(e==="today")return{active:!0,invalid:!1,start:i,endExclusive:K(i,1),label:J("Today",i,i)};if(e==="this-week"){const o=Yn(i);return{active:!0,invalid:!1,start:o,endExclusive:K(o,7),label:J("This week",o,K(o,6))}}if(e==="last-7-days"){const o=K(i,-6);return{active:!0,invalid:!1,start:o,endExclusive:K(i,1),label:J("Last 7 days",o,i)}}if(e==="this-month"){const o=new Date(i.getFullYear(),i.getMonth(),1),r=new Date(i.getFullYear(),i.getMonth()+1,1);return{active:!0,invalid:!1,start:o,endExclusive:r,label:J("This month",o,K(r,-1))}}return{active:!1,invalid:!1,start:null,endExclusive:null,label:"All time"}}function Un(e,n){if(n.modelFilter!=="all"&&e.model!==n.modelFilter||n.effortFilter!=="all"&&e.effort!==n.effortFilter||!Mn(e,n.confidenceFilter)||!Tn(e,n.sourceFilter)||!Xn(e,n.timeFilter,n.dateStart,n.dateEnd)||n.activePreset&&!$t(e,n.activePreset))return!1;const s=[e.thread,e.cwd,e.project,e.projectRelativeCwd,e.gitBranch,e.gitRemoteLabel,e.sessionId,e.model,e.effort,e.initiator,e.initiatorReason,e.signal,e.recommendation,e.rawTime,e.sourceFile,e.lineNumber,e.tags.join(" ")];return[n.globalQuery,n.localQuery].every(a=>Wt(s,a))}function qn(e,n,s,a){const i=rt(e,s),o=rt(n,s);if(i===null&&o!==null)return 1;if(o===null&&i!==null)return-1;const r=Qn(i,o);return r!==0?a==="asc"?r:-r:Ie(e,n)||e.id.localeCompare(n.id)}function rt(e,n){return n==="time"?$e(e):n==="duration"?e.durationSeconds:n==="gap"?e.previousCallGapSeconds:n==="attention"?Gn(e):n==="thread"?e.thread.toLowerCase():n==="initiator"?e.initiator.toLowerCase():n==="model"?e.model.toLowerCase():n==="effort"?e.effort.toLowerCase():n==="total"?e.totalTokens:n==="cached"?e.input*(e.cachedPct/100):n==="uncached"?e.uncachedInput:n==="output"?e.output:n==="reasoning"?e.reasoningOutput:n==="cost"?e.cost:n==="usage"?e.credits:n==="cache"?e.cachedPct:e.contextWindowPct}function Gn(e){const n=e.signal&&e.signal!=="aggregate"?1e3:0,s=e.recommendation?750:0,a=e.contextWindowPct??0,i=Math.min(e.uncachedInput/1e3,250),o=Math.min(e.cost*25,200),r=Math.min(e.credits,200),h=Math.min(e.durationSeconds/60,120);return n+s+a+i+o+r+h}function Qn(e,n){return e===null&&n===null?0:e===null?1:n===null?-1:typeof e=="string"||typeof n=="string"?String(e).localeCompare(String(n)):e-n}function Ie(e,n){return $e(n)-$e(e)}function $e(e){const n=Date.parse(e.rawTime);return Number.isFinite(n)?n:0}function lt(e){if(!/^\d{4}-\d{2}-\d{2}$/.test(e))return null;const[n,s,a]=e.split("-").map(Number),i=new Date(n,s-1,a);return i.getFullYear()===n&&i.getMonth()===s-1&&i.getDate()===a?i:null}function ct(e){return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`}function J(e,n,s){const a=n?ct(n):"",i=s?ct(s):"";return a&&i&&a===i?`${e}: ${a}`:a&&i?`${e}: ${a} to ${i}`:a?`${e}: from ${a}`:i?`${e}: through ${i}`:e}function Wn(e=new Date){return new Date(e.getFullYear(),e.getMonth(),e.getDate())}function K(e,n){return new Date(e.getFullYear(),e.getMonth(),e.getDate()+n)}function Yn(e){const n=e.getDay();return K(e,n===0?-6:1-n)}function Xn(e,n,s="",a="",i=new Date){if(n==="all")return!0;const o=ze(n,s,a,i);if(o.invalid)return!1;if(!o.active)return!0;const r=Date.parse(e.rawTime);return!(!Number.isFinite(r)||o.start&&r=o.endExclusive.getTime())}const Jn={time:"time",duration:"duration",gap:"gap",thread:"thread",initiator:"initiator",model:"model",effort:"effort",total:"tokens",cached:"cached",uncached:"uncached",output:"output",reasoning:"reasoning",cache:"cache"};function Zn(e){const n=Jn[e.sortKey],s=ze(e.timeFilter,e.dateStart,e.dateEnd,new Date),a=es(e,n,s.invalid);return{enabled:!a,reason:a,sort:n??"time",filters:{query:[e.globalQuery.trim(),e.localQuery.trim()].filter(Boolean).join(" "),model:e.modelFilter==="all"?void 0:e.modelFilter,effort:e.effortFilter==="all"?void 0:e.effortFilter,...ts(e.confidenceFilter),...ns(s.start,s.endExclusive,e.scopeSince)}}}function es(e,n,s){return!e.enabled||e.runtime.fileMode||!e.runtime.apiToken?"Stored snapshot":e.globalQuery.trim()&&e.localQuery.trim()?"Multiple searches use loaded snapshot":e.activePreset?"Preset uses loaded snapshot":e.sourceFilter!=="all"?"Source filter uses loaded snapshot":n?s?"Invalid date range":"":"This sort uses loaded snapshot"}function ts(e){return e==="cost-exact"?{pricingStatus:"priced"}:e==="cost-estimated"?{pricingStatus:"estimated"}:e==="cost-unpriced"?{pricingStatus:"unpriced"}:e==="credit-exact"?{creditConfidence:"exact"}:e==="credit-estimated"?{creditConfidence:"estimated"}:e==="credit-override"?{creditConfidence:"user_override"}:e==="credit-missing"?{creditConfidence:"unpriced"}:{}}function ns(e,n,s){return{since:(e==null?void 0:e.toISOString())??s??void 0,until:n?new Date(n.getTime()-1).toISOString():void 0}}function ss({data:e,valueLabel:n=s=>String(s)}){const s=Math.max(...e.map(a=>a.value),1);return t.jsx("div",{className:"bar-list",children:e.map(a=>t.jsxs("div",{className:"bar-row",children:[t.jsx("span",{children:a.label}),t.jsx("div",{className:"bar-track","aria-hidden":"true",children:t.jsx("i",{style:{width:`${a.value/s*100}%`,backgroundColor:a.color??"#2563eb"}})}),t.jsx("strong",{children:n(a.value)})]},a.label))})}function P({label:e,value:n,detail:s}){return t.jsxs("span",{className:"drilldown-metric",children:[t.jsx("small",{children:e}),t.jsx("strong",{children:n}),t.jsx("em",{children:s})]})}function S({label:e,value:n}){return t.jsxs("div",{children:[t.jsx("dt",{children:e}),t.jsx("dd",{children:n})]})}function as({call:e,contextRuntime:n,onContextApiEnabledChange:s}){const a=ce(),[i,o]=c.useState(()=>en(e.id)??tt),[r,h]=c.useState({status:"idle"}),F=!!n.apiToken&&!n.fileMode,d=F&&n.contextApiEnabled;c.useEffect(()=>{const l=nt(e.id,i);h(l?{status:"loaded",payload:l}:{status:"idle"})},[e.id,i.includeCompactionHistory,i.includeToolOutput,i.maxChars,i.maxEntries,i.mode]);async function b(){h({status:"loading",message:"Enabling localhost context API..."});try{const l=await nn(n);s(l),h({status:"idle",message:l?"Context API enabled. Load this call when ready.":"Context API did not enable."})}catch(l){h({status:"error",message:st(l)})}}async function j(l=i,p="Loading selected-turn evidence..."){at(e.id,l);const w=nt(e.id,l);if(w){h({status:"loaded",payload:w});return}h({status:"loading",message:p});try{const g=await sn(e.id,n,l);an(e.id,l,g),h({status:"loaded",payload:g})}catch(g){h({status:"error",message:st(g)})}}function C(){const l={...i,mode:"full"};o(l),j(l,"Loading full turn analysis...")}function D(l){const p=hn(l,i);o(p),j(p,"Loading older context...")}function N(){const l={...i,includeToolOutput:!0};o(l),j(l,"Loading omitted tool output...")}function y(){const l={...i,includeCompactionHistory:!0};o(l),j(l,"Loading compacted replacement...")}function m(l,p){o(w=>{const g={...w,[l]:p};return at(e.id,g),g})}return t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"locked-context-card",children:[t.jsx(pt,{size:20}),t.jsxs("div",{children:[t.jsx("strong",{children:"Raw context is gated"}),t.jsx("p",{children:tn(n)})]})]}),t.jsxs("dl",{className:"detail-list",children:[t.jsx(S,{label:"Record id",value:e.id}),t.jsx(S,{label:"Time",value:e.time}),t.jsx(S,{label:"Thread",value:e.thread}),t.jsx(S,{label:"Model",value:e.model}),t.jsx(S,{label:"Effort",value:e.effort})]}),t.jsxs("div",{className:"context-action-grid",children:[t.jsx("button",{className:"toolbar-button",type:"button",onClick:b,disabled:!F||n.contextApiEnabled||r.status==="loading",children:a.t("button.enable_context_loading","Enable context loading")}),t.jsx("button",{className:"primary-button",type:"button",onClick:()=>j(),disabled:!d||r.status==="loading",children:a.t("button.show_turn_evidence","Show turn log evidence")}),t.jsx("button",{className:"toolbar-button",type:"button",onClick:C,disabled:!d||r.status==="loading",children:a.t("button.full_serialized_analysis","Run full serialized analysis")}),t.jsxs("label",{className:"context-field",children:[t.jsx("span",{children:"Mode"}),t.jsxs("select",{"aria-label":"Side panel context mode",value:i.mode,disabled:!d||r.status==="loading",onChange:l=>m("mode",l.target.value==="full"?"full":"quick"),children:[t.jsx("option",{value:"quick",children:"Quick"}),t.jsx("option",{value:"full",children:"Full"})]})]}),t.jsxs("label",{className:"context-field",children:[t.jsx("span",{children:"Entries"}),t.jsxs("select",{"aria-label":"Side panel context entries",value:String(i.maxEntries),disabled:!d||r.status==="loading",onChange:l=>m("maxEntries",Number(l.target.value)),children:[t.jsx("option",{value:"20",children:"20"}),t.jsx("option",{value:"50",children:"50"}),t.jsx("option",{value:"100",children:"100"}),t.jsx("option",{value:"0",children:"All"})]})]}),t.jsxs("label",{className:"toggle-row",children:[t.jsx("input",{type:"checkbox",checked:i.includeToolOutput,disabled:!d||r.status==="loading",onChange:l=>m("includeToolOutput",l.target.checked)}),a.t("button.include_tool_output","Include tool output")]}),t.jsxs("label",{className:"toggle-row",children:[t.jsx("input",{type:"checkbox",checked:i.includeCompactionHistory,disabled:!d||r.status==="loading",onChange:l=>m("includeCompactionHistory",l.target.checked)}),"Include compaction history"]}),t.jsxs("label",{className:"toggle-row",children:[t.jsx("input",{type:"checkbox",checked:i.maxChars===0,disabled:!d||r.status==="loading",onChange:l=>m("maxChars",l.target.checked?0:tt.maxChars)}),a.t("button.no_char_limit","No char limit")]})]}),r.status==="idle"&&r.message?t.jsx("p",{className:"context-state-note",children:r.message}):null,r.status==="loading"?t.jsx("p",{className:"context-state-note",children:r.message}):null,r.status==="error"?t.jsx("p",{className:"context-state-note error",children:r.message}):null,r.status==="loaded"?t.jsx(is,{call:e,payload:r.payload,onLoadOlder:D,onRunFullAnalysis:C,onLoadCompactionHistory:y,onLoadToolOutput:N}):null,t.jsx("p",{className:"privacy-note",children:"Raw context is never embedded in the dashboard HTML. This view reads the selected local JSONL turn only after an explicit request."})]})}function is({call:e,payload:n,onLoadOlder:s,onRunFullAnalysis:a,onLoadCompactionHistory:i,onLoadToolOutput:o}){var _,T;const r=ce(),h=n.entries??[],F=n.omitted??{},d=Number(F.older_entries??0),b=on(n),j=8,C=e.id||String(n.record_id??""),[D,N]=c.useState(()=>it(C)),[y,m]=c.useState(()=>ot(C,h));c.useEffect(()=>{N(it(C)),m(ot(C,h))},[h.length,n.context_mode,n.include_compaction_history,n.include_tool_output,(_=n.omitted)==null?void 0:_.max_chars,(T=n.omitted)==null?void 0:T.max_entries,n.record_id,C]);const l=D?h:h.slice(0,j),p=Math.max(h.length-l.length,0);function w(){N(x=>{const L=!x;return xn(C,L),L})}function g(x,L){mn(C,x,L),m(v=>{const u=new Set(v);return L?u.add(x):u.delete(x),u})}return t.jsxs("div",{className:"context-evidence",children:[t.jsxs("div",{className:"context-evidence-summary",children:[t.jsx(P,{label:"Entries",value:O(h.length),detail:String(n.context_mode??"quick")}),t.jsx(P,{label:"Visible chars",value:O(Number(n.visible_char_count??0)),detail:"redacted local text"}),t.jsx(P,{label:"Visible tokens",value:O(Number(n.visible_token_estimate??0)),detail:"estimator"}),t.jsx(P,{label:"Older omitted",value:O(d),detail:"entry budget"})]}),b.length?t.jsx("p",{className:"context-note",children:b.join(" ")}):null,t.jsx(rn,{call:e,payload:n,onRunFullAnalysis:a}),d>0?t.jsx("div",{className:"context-followup-actions",children:t.jsx("button",{className:"toolbar-button",type:"button",onClick:()=>s(n),children:r.t("button.load_older_context","Load older entries")})}):null,t.jsxs("div",{className:"context-entry-list",children:[l.map((x,L)=>{const v=ln(x,L);return t.jsxs("details",{className:"context-entry",open:y.has(v),onToggle:u=>g(v,u.currentTarget.open),children:[t.jsx("summary",{className:"context-entry-summary",children:t.jsxs("div",{className:"context-entry-meta",children:[t.jsx("strong",{children:x.label||x.role||x.type||`Entry ${L+1}`}),t.jsx("span",{children:x.line_number?`line ${x.line_number}`:x.timestamp||"local evidence"})]})}),t.jsx(cn,{entry:x}),x.tool_output_omitted?t.jsx("div",{className:"context-entry-actions",children:t.jsx("button",{className:"toolbar-button",type:"button",onClick:o,children:r.t("button.show_tool_output","Show tool output")})}):null,t.jsx(os,{entry:x,onLoadCompactionHistory:i}),t.jsx("pre",{ref:u=>{u&&(u.scrollTop=un(C,v))},onScroll:u=>dn(C,v,u.currentTarget.scrollTop),children:x.text||"[no visible text]"})]},v)}),h.length?null:t.jsx("p",{className:"empty-state",children:"No visible evidence entries returned for this call."})]}),h.length>j?t.jsxs("div",{className:"context-followup-actions",children:[t.jsx("button",{className:"toolbar-button",type:"button",onClick:w,children:D?`Show first ${O(j)} entries`:`Show all ${O(h.length)} returned entries`}),D?null:t.jsxs("span",{className:"context-entry-count-note",children:[O(p)," entries hidden in compact view"]})]}):null]})}function os({entry:e,onLoadCompactionHistory:n}){const s=ce(),a=e.compaction;if(!(a!=null&&a.replacement_history_available))return null;const i=a.replacement_history??[],o=Number(a.replacement_entry_count??i.length);return t.jsxs("div",{className:"context-entry-compaction",children:[t.jsx("strong",{children:"Compaction detected"}),t.jsxs("span",{children:[O(o)," replacement history entries available."]}),i.length?t.jsx("div",{className:"context-replacement-history","aria-label":"Compacted replacement context",children:i.map((r,h)=>t.jsxs("div",{className:"context-replacement-entry",children:[t.jsx("strong",{children:r.label||`Replacement item ${h+1}`}),t.jsx("pre",{children:r.text||"[no visible replacement text]"})]},`${r.label??"replacement"}-${h}`))}):t.jsx("div",{className:"context-entry-actions",children:t.jsx("button",{className:"toolbar-button",type:"button",onClick:n,children:s.t("button.show_compaction_history","Show compacted replacement")})})]})}const rs=[{id:"summary",label:"Summary",icon:At},{id:"tokens",label:"Tokens",icon:ut},{id:"cache",label:"Cache",icon:Ot},{id:"thread",label:"Thread",icon:Sn},{id:"evidence",label:"Evidence",icon:pt}];function ls({call:e,calls:n,contextRuntime:s,includeArchived:a,sourceRevision:i,hydrateThreadCalls:o,onContextApiEnabledChange:r,onOpenInvestigator:h,onCopyCallLink:F}){const[d,b]=c.useState("summary"),[j,C]=c.useState(""),D=c.useMemo(()=>e?n.filter(m=>m.thread===e.thread).sort(Ie):[],[e,n]),N=Zt({...kt({runtime:s,includeArchived:a,sourceRevision:i,threadKey:(e==null?void 0:e.threadKey)||(e==null?void 0:e.thread)||"",selectedRecordId:(e==null?void 0:e.id)??"",selectedEventTimestamp:(e==null?void 0:e.eventTimestamp)??""}),enabled:o&&!s.fileMode&&!!s.apiToken&&!!e,placeholderData:m=>m}),y=c.useMemo(()=>{var l;const m=((l=N.data)==null?void 0:l.rows)??D;return!e||m.some(p=>p.id===e.id)?m:[...m,e].sort(Ie)},[e,D,N.data]);return e?t.jsx("aside",{className:"side-panel drilldown-panel",children:t.jsxs(Z,{title:"Call Drill-Down",subtitle:`${e.thread} / ${e.model}`,children:[t.jsxs("div",{className:"call-summary",children:[t.jsx(re,{label:"Aggregate only",tone:"green"}),t.jsx(Rt,{call:e}),t.jsx(re,{label:"Raw context gated",tone:"blue"}),t.jsx("span",{className:"call-id",children:e.id.slice(0,12)})]}),t.jsxs("div",{className:"action-row",children:[t.jsxs("button",{className:"toolbar-button",type:"button",onClick:()=>h(e.id),children:[t.jsx(bn,{size:16}),"Open investigator"]}),t.jsxs("button",{className:"toolbar-button",type:"button",onClick:()=>xs(e,C),children:[t.jsx(dt,{size:16}),"Copy link"]})]}),j?t.jsx("p",{className:"context-state-note",children:j}):null,t.jsx("div",{className:"drilldown-tabs",role:"tablist","aria-label":"Call drill-down sections",children:rs.map(m=>{const l=m.icon,p=d===m.id;return t.jsxs("button",{type:"button",role:"tab","aria-selected":p,className:p?"active":"",onClick:()=>b(m.id),children:[t.jsx(l,{size:14}),m.label]},m.id)})}),t.jsxs("div",{className:"drilldown-tab-panel",role:"tabpanel",children:[d==="summary"?t.jsx(cs,{call:e}):null,d==="tokens"?t.jsx(us,{call:e}):null,d==="cache"?t.jsx(hs,{call:e,calls:y}):null,d==="thread"?t.jsx(ms,{call:e,calls:y,onOpenInvestigator:h,onCopyCallLink:F}):null,d==="evidence"?t.jsx(as,{call:e,contextRuntime:s,onContextApiEnabledChange:r},e.id):null]})]})}):t.jsx("aside",{className:"side-panel drilldown-panel",children:t.jsx(Z,{title:"Call Drill-Down",subtitle:"No matching call",children:t.jsx("p",{className:"empty-state",children:"No aggregate row matches the active filters."})})})}function cs({call:e}){return t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"drilldown-metric-grid",children:[t.jsx(P,{label:"Total tokens",value:O(e.totalTokens),detail:`${ee(e.input)} input`}),t.jsx(P,{label:"Uncached input",value:O(e.uncachedInput),detail:"fresh billed input"}),t.jsx(P,{label:"Cache hit rate",value:q(e.cachedPct),detail:Oe(e)}),t.jsx(P,{label:"Estimated cost",value:Ae(e.cost),detail:e.pricingEstimated?"estimated pricing":"configured pricing"}),t.jsx(P,{label:"Duration",value:e.duration,detail:e.fast?"fast candidate":"normal throughput"}),t.jsx(P,{label:"Usage credits",value:e.credits?e.credits.toFixed(3):"-",detail:e.usageCreditConfidence})]}),t.jsx(pn,{call:e}),t.jsx(fn,{call:e}),t.jsx(ds,{call:e}),t.jsx(St,{call:e}),t.jsx(wt,{call:e}),e.recommendation?t.jsxs("div",{className:"recommendation-box",children:[t.jsx(Cn,{size:16}),t.jsx("p",{children:e.recommendation})]}):null]})}function ds({call:e}){return t.jsxs("div",{className:"composition-card accounting-snapshot-card",children:[t.jsxs("div",{className:"composition-head",children:[t.jsx("strong",{children:"Accounting Snapshot"}),t.jsx("span",{children:"pricing, credits, and cache savings"})]}),t.jsx(xt,{call:e})]})}function us({call:e}){return t.jsxs(t.Fragment,{children:[t.jsx(St,{call:e}),t.jsx(xt,{call:e})]})}function hs({call:e,calls:n}){return t.jsxs(t.Fragment,{children:[t.jsx(wt,{call:e}),t.jsx(gn,{call:e,calls:n}),t.jsxs("dl",{className:"detail-list",children:[t.jsx(S,{label:"Cache state",value:Oe(e)}),t.jsx(S,{label:"Cache hit rate",value:q(e.cachedPct)}),t.jsx(S,{label:"Fresh share",value:q(Math.max(100-e.cachedPct,0))}),t.jsx(S,{label:"Signal",value:e.signal})]}),t.jsx("p",{className:"privacy-note",children:"Use this readout to decide whether the aggregate call needs deeper raw-context investigation."})]})}function ms({call:e,calls:n,onOpenInvestigator:s,onCopyCallLink:a}){const i=n.reduce((b,j)=>b+j.totalTokens,0),o=n.reduce((b,j)=>b+j.cost,0),r=n.reduce((b,j)=>b+j.cachedPct,0)/Math.max(n.length,1),h=Math.max(n.findIndex(b=>b.id===e.id),0),F=Math.min(Math.max(n.length,1),5),d=e.sourceFile?`${e.sourceFile}${e.lineNumber?`:${e.lineNumber}`:""}`:"Not available";return t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"drilldown-metric-grid",children:[t.jsx(P,{label:"Loaded thread calls",value:O(n.length),detail:`selected ${h+1} of ${n.length}`}),t.jsx(P,{label:"Thread tokens",value:ee(i),detail:"loaded aggregate rows"}),t.jsx(P,{label:"Thread cost",value:Ae(o),detail:"estimated aggregate"}),t.jsx(P,{label:"Avg cache",value:q(r),detail:jn(n.map(b=>b.model),{style:"x",emptyLabel:"no model mix"})}),t.jsx(P,{label:"Context window",value:e.contextWindowPct===null?"-":q(e.contextWindowPct),detail:e.modelContextWindow?`${ee(e.modelContextWindow)} window`:"not reported"}),t.jsx(P,{label:"Parent thread",value:e.parentThread||"-",detail:e.parentSessionId||"no parent session"})]}),t.jsxs("div",{className:"composition-card",children:[t.jsxs("div",{className:"composition-head",children:[t.jsx("strong",{children:"Thread timeline"}),t.jsxs("span",{children:[F," nearby loaded calls"]})]}),t.jsx(vn,{selectedCall:e,calls:n,onOpenInvestigator:s,onCopyCallLink:a,copyAriaContext:"side-panel thread call"})]}),t.jsxs("div",{className:"composition-card",children:[t.jsxs("div",{className:"composition-head",children:[t.jsx("strong",{children:"Call Narrative"}),t.jsx("span",{children:e.initiatorConfidence||"aggregate inference"})]}),t.jsxs("dl",{className:"detail-list compact",children:[t.jsx(S,{label:"Initiated by",value:e.initiator||"unknown"}),t.jsx(S,{label:"Initiator reason",value:e.initiatorReason||"Not reported"}),t.jsx(S,{label:"Parent thread",value:e.parentThread||"None"}),t.jsx(S,{label:"Parent session",value:e.parentSessionId||"None"}),t.jsx(S,{label:"Timestamp",value:e.time}),t.jsx(S,{label:"Duration",value:e.duration}),t.jsx(S,{label:"Previous gap",value:e.previousCallGap})]})]}),t.jsxs("dl",{className:"detail-list",children:[t.jsx(S,{label:"Project",value:e.project||"Unknown"}),t.jsx(S,{label:"Project path",value:e.projectRelativeCwd||e.cwd||"."}),t.jsx(S,{label:"Source line",value:d}),t.jsx(S,{label:"Session",value:e.sessionId||"Not available"}),t.jsx(S,{label:"Git branch",value:e.gitBranch||"Unknown"}),t.jsx(S,{label:"Remote",value:e.gitRemoteLabel||e.gitRemoteHash||"None"})]})]})}function St({call:e}){const s=[{label:"Cached input",value:Math.max(e.input-e.uncachedInput,0),color:"#2563eb"},{label:"Uncached input",value:e.uncachedInput,color:"#f59e0b"},{label:"Output",value:e.output,color:"#059669"},{label:"Reasoning",value:e.reasoningOutput,color:"#7c3aed"}].filter(i=>i.value>0),a=Math.max(s.reduce((i,o)=>i+o.value,0),1);return t.jsxs("div",{className:"composition-card",children:[t.jsxs("div",{className:"composition-head",children:[t.jsx("strong",{children:"Token composition"}),t.jsxs("span",{children:[ee(a)," visible tokens"]})]}),t.jsx("div",{className:"composition-bar",role:"img","aria-label":"Token composition",children:s.map(i=>t.jsx("i",{style:{width:`${Math.max(i.value/a*100,3)}%`,background:i.color}},i.label))}),t.jsx("div",{className:"composition-legend",children:s.map(i=>t.jsxs("span",{children:[t.jsx("i",{style:{background:i.color}}),i.label]},i.label))})]})}function wt({call:e}){const n=[{label:"Cache",value:Math.min(Math.max(e.cachedPct,0),100),color:"#2563eb"},{label:"Fresh",value:Math.min(Math.max(100-e.cachedPct,0),100),color:"#f59e0b"},{label:"Output",value:Math.min(Math.max(e.output/Math.max(e.totalTokens,1)*100,0),100),color:"#059669"}];return t.jsxs("div",{className:"cache-mini-card",children:[t.jsxs("div",{children:[t.jsx("strong",{children:"Cache delta readout"}),t.jsx("span",{children:Oe(e)})]}),t.jsx("div",{className:"cache-bars","aria-label":"Cache delta readout",children:n.map(s=>t.jsxs("span",{children:[t.jsx("i",{style:{height:`${Math.max(s.value,4)}%`,background:s.color}}),t.jsx("em",{children:s.label})]},s.label))})]})}async function xs(e,n){try{const s=new URL(window.location.href);if(s.searchParams.set("view","call"),s.searchParams.set("record",e.id),s.searchParams.set("return","calls"),!await ht(s.toString()))throw new Error("Clipboard unavailable");n("Copied investigator link")}catch{n("Copy unavailable in this browser")}}const ps="_page_gyxvc_1",fs="_pageHeader_gyxvc_5",gs="_tableHeading_gyxvc_13",js="_eyebrow_gyxvc_22",vs="_headerActions_gyxvc_36",bs="_tableActions_gyxvc_37",Cs="_gridFooter_gyxvc_38",ys="_queryBar_gyxvc_46",Ss="_advancedFilters_gyxvc_80",ws="_patternsDisclosure_gyxvc_85",Fs="_advancedFilterGrid_gyxvc_107",Ns="_gridColumn_gyxvc_138",$={page:ps,pageHeader:fs,tableHeading:gs,eyebrow:js,headerActions:vs,tableActions:bs,gridFooter:Cs,queryBar:ys,advancedFilters:Ss,patternsDisclosure:ws,advancedFilterGrid:Fs,gridColumn:Ns};function Es({searchInputRef:e,localQuery:n,modelFilter:s,effortFilter:a,confidenceFilter:i,sourceFilter:o,timeFilter:r,dateStart:h,dateEnd:F,sortKey:d,sortDirection:b,modelOptions:j,effortOptions:C,sourceCoverage:D,dateRangeStatus:N,onLocalQueryChange:y,onModelFilterChange:m,onEffortFilterChange:l,onConfidenceFilterChange:p,onSourceFilterChange:w,onTimeFilterChange:g,onDateStartChange:_,onDateEndChange:T,onSortKeyChange:x,onSortDirectionChange:L,onClear:v}){return t.jsxs("section",{className:$.queryBar,"aria-label":"Call filters",children:[t.jsxs("label",{className:"search-box",children:[t.jsx("span",{className:"sr-only",children:"Search calls"}),t.jsx("input",{ref:e,value:n,onChange:u=>y(u.target.value),placeholder:"Search calls, cwd, projects, models..."})]}),t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"Model"}),t.jsxs("select",{value:s,onChange:u=>m(u.target.value),children:[t.jsx("option",{value:"all",children:"All models"}),j.map(u=>t.jsx("option",{value:u,children:u},u))]})]}),t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"Effort"}),t.jsxs("select",{value:a,onChange:u=>l(u.target.value),children:[t.jsx("option",{value:"all",children:"All effort"}),C.map(u=>t.jsx("option",{value:u,children:u},u))]})]}),t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"Confidence"}),t.jsxs("select",{"aria-label":"Confidence filter",value:i,onChange:u=>p(u.target.value),children:[t.jsx("option",{value:"all",children:"All confidence"}),t.jsx("option",{value:"cost-exact",children:"Exact cost"}),t.jsx("option",{value:"cost-estimated",children:"Estimated cost"}),t.jsx("option",{value:"cost-unpriced",children:"Unpriced cost"}),t.jsx("option",{value:"credit-exact",children:"Exact credit rate"}),t.jsx("option",{value:"credit-estimated",children:"Estimated credit mapping"}),t.jsx("option",{value:"credit-override",children:"User credit override"}),t.jsx("option",{value:"credit-missing",children:"Missing credit rate"})]})]}),t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"Time"}),t.jsxs("select",{"aria-label":"Time filter",value:r,onChange:u=>g(u.target.value),children:[t.jsx("option",{value:"all",children:"All time"}),t.jsx("option",{value:"today",children:"Today"}),t.jsx("option",{value:"this-week",children:"This week"}),t.jsx("option",{value:"last-7-days",children:"Last 7 days"}),t.jsx("option",{value:"this-month",children:"This month"}),t.jsx("option",{value:"custom",children:"Custom range"})]}),N.active||N.invalid?t.jsx("span",{className:"filter-status","data-state":N.invalid?"error":"active","aria-live":"polite",children:N.label}):null]}),t.jsxs("details",{className:$.advancedFilters,children:[t.jsxs("summary",{children:[t.jsx(Bt,{size:16}),"More filters"]}),t.jsxs("div",{className:$.advancedFilterGrid,children:[t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"Source"}),t.jsxs("select",{"aria-label":"Source filter",value:o,onChange:u=>w(u.target.value),children:[t.jsx("option",{value:"all",children:"All sources"}),t.jsx("option",{value:"project",children:"Project / cwd"}),t.jsx("option",{value:"session",children:"Session-linked"}),t.jsx("option",{value:"git",children:"Git metadata"}),t.jsx("option",{value:"source-file",children:"Source file"}),t.jsx("option",{value:"missing",children:"Missing source"})]}),t.jsx("span",{className:"filter-status","data-state":o==="all"?"active":"filtered","aria-live":"polite",children:En(D,o)})]}),t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"Start"}),t.jsx("input",{"aria-label":"Start date",type:"date",value:h,onChange:u=>_(u.target.value)})]}),t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"End"}),t.jsx("input",{"aria-label":"End date",type:"date",value:F,onChange:u=>T(u.target.value)})]}),t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"Sort"}),t.jsxs("select",{"aria-label":"Sort calls",value:d,onChange:u=>x(u.target.value),children:[t.jsx("option",{value:"time",children:"Newest calls"}),t.jsx("option",{value:"duration",children:"Longest duration"}),t.jsx("option",{value:"gap",children:"Longest gap"}),t.jsx("option",{value:"attention",children:"Needs attention"}),t.jsx("option",{value:"thread",children:"Thread name"}),t.jsx("option",{value:"initiator",children:"Initiated"}),t.jsx("option",{value:"model",children:"Model"}),t.jsx("option",{value:"effort",children:"Reasoning"}),t.jsx("option",{value:"total",children:"Most tokens"}),t.jsx("option",{value:"cached",children:"Cached"}),t.jsx("option",{value:"uncached",children:"Uncached"}),t.jsx("option",{value:"output",children:"Output"}),t.jsx("option",{value:"reasoning",children:"Reasoning output"}),t.jsx("option",{value:"cost",children:"Highest estimated cost"}),t.jsx("option",{value:"usage",children:"Highest Codex credits"}),t.jsx("option",{value:"cache",children:"Lowest cache ratio"}),t.jsx("option",{value:"context",children:"Highest context use"})]})]}),t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"Direction"}),t.jsxs("select",{"aria-label":"Sort direction",value:b,onChange:u=>L(u.target.value),children:[t.jsx("option",{value:"desc",children:"Descending"}),t.jsx("option",{value:"asc",children:"Ascending"})]})]})]})]}),t.jsxs("button",{className:"toolbar-button",type:"button",onClick:v,children:[t.jsx(Vt,{size:16}),"Clear filters"]})]})}function Ts({workspaceSwitcher:e,canExport:n,onExport:s,onCopyView:a,onRefresh:i}){return t.jsxs("header",{className:$.pageHeader,children:[t.jsxs("div",{children:[t.jsx("p",{className:$.eyebrow,children:"Evidence explorer"}),t.jsx("h1",{children:"Calls"}),t.jsx("p",{children:"Find expensive, cold, or context-heavy calls and move directly into their evidence."})]}),t.jsxs("div",{className:$.headerActions,children:[e,t.jsxs("button",{className:"toolbar-button",type:"button",onClick:s,disabled:!n,children:[t.jsx(Ht,{size:16}),"Export"]}),t.jsxs("button",{className:"toolbar-button",type:"button",onClick:a,children:[t.jsx(dt,{size:16}),"Copy view"]}),t.jsxs("button",{className:"primary-button",type:"button",onClick:i,children:[t.jsx(Kt,{size:16}),"Refresh"]})]})]})}const Ft="codexUsageDetailPanel";function Ms(e=!1){var n;try{const s=(n=window.sessionStorage)==null?void 0:n.getItem(Ft);return s?s==="expanded":e}catch{return e}}function Ds(e){var n;try{(n=window.sessionStorage)==null||n.setItem(Ft,e?"expanded":"collapsed")}catch{}}const B=250;function Ps(e,n){const s=E("density"),[a,i]=c.useState(()=>E("call_q")),[o,r]=c.useState(()=>E("model")||"all"),[h,F]=c.useState(()=>E("effort")||"all"),[d,b]=c.useState(()=>gt()),[j,C]=c.useState(()=>jt()),[D,N]=c.useState(()=>vt()),[y,m]=c.useState(()=>le("from")),[l,p]=c.useState(()=>le("to")),[w,g]=c.useState(()=>Pe()),[_,T]=c.useState(()=>Ct(Pe())),x=yn("codexUsageCallsEvidenceGrid",{density:On()==="dense"?"compact":"comfortable",columnVisibility:{}},s==="roomy"?"comfortable":s==="dense"?"compact":void 0),L=x.density==="compact"?"dense":"roomy",v=c.useMemo(()=>An(),[]),[u,te]=c.useState(v),[G,R]=c.useState(()=>Bn(B)),[ne,Q]=c.useState(""),[de,ue]=c.useState(""),[W,he]=c.useState(()=>Ms(!!v)),me=c.useRef(null),se=c.useRef(n),xe=c.useMemo(()=>Ze(e.calls.map(f=>f.model)),[e.calls]),pe=c.useMemo(()=>Ze(e.calls.map(f=>f.effort)),[e.calls]),fe=c.useMemo(()=>Dn(e.calls),[e.calls]),Y=c.useMemo(()=>ze(D,y,l,new Date),[l,y,D]);c.useEffect(()=>{se.current!==n&&(se.current=n,R(B))},[n]);function I(){R(B)}function ge(f){i(f),I()}function je(f){r(f),I()}function ve(f){F(f),I()}function be(f){b(f),I()}function Ce(f){C(f),I()}function ye(f){N(f),I()}function Se(f){m(Le(f)),N("custom"),I()}function we(f){p(Le(f)),N("custom"),I()}function Fe(f){const A=bt(f);g(A),T(U(A)),I()}function Ne(f){T(f==="asc"?"asc":"desc"),I()}function Ee(){i(""),r("all"),F("all"),b("all"),C("all"),N("all"),m(""),p(""),g("time"),T(U("time")),x.setDensity("compact"),x.setColumnVisibility({}),te(null),R(B),window.history.replaceState(null,"",_e({localQuery:"",modelFilter:"all",effortFilter:"all",confidenceFilter:"all",sourceFilter:"all",timeFilter:"all",dateStart:"",dateEnd:"",sortKey:"time",sortDirection:U("time"),density:"dense",selectedRecordId:"",visibleRowCount:B,pageSize:B})),ue("Calls filters cleared")}function Te(){he(f=>{const A=!f;return Ds(A),A})}return{localQuery:a,modelFilter:o,effortFilter:h,confidenceFilter:d,sourceFilter:j,timeFilter:D,dateStart:y,dateEnd:l,sortKey:w,setSortKey:g,sortDirection:_,setSortDirection:T,gridPreferences:x,density:L,initialSelectedCallId:v,selectedCallId:u,setSelectedCallId:te,visibleCallRows:G,setVisibleCallRows:R,exportStatus:ne,setExportStatus:Q,filterStatus:de,detailsExpanded:W,searchInputRef:me,modelOptions:xe,effortOptions:pe,sourceCoverage:fe,dateRangeStatus:Y,resetCallTablePage:I,updateLocalQuery:ge,updateModelFilter:je,updateEffortFilter:ve,updateConfidenceFilter:be,updateSourceFilter:Ce,updateTimeFilter:ye,updateDateStart:Se,updateDateEnd:we,updateSortKey:Fe,updateSortDirection:Ne,clearCallFilters:Ee,toggleCallDetails:Te}}function _s({model:e,header:n,filters:s,table:a,inspector:i}){var F;const o=ce(),r=o.t("dashboard.model_calls","Model Calls"),h=o.t("dashboard.model_calls","Model calls");return t.jsxs("div",{className:`${$.page} page-grid`,children:[t.jsx(Ts,{...n}),t.jsx(Es,{...s}),t.jsxs("div",{className:$.tableHeading,children:[t.jsxs("div",{children:[t.jsx("h2",{children:r}),t.jsx("p",{children:a.exportStatus||a.filterStatus||a.subtitle})]}),t.jsxs("div",{className:$.tableActions,children:[t.jsx(re,{label:Ls(a.isFetching,a.focused,a.focusedReason),tone:a.focused?"green":"blue"}),t.jsx(re,{label:a.activePreset?`Preset: ${mt(a.activePreset)}`:"Raw context gated",tone:a.activePreset?"green":"blue"}),t.jsxs("button",{className:"toolbar-button",type:"button","aria-expanded":a.detailsExpanded,onClick:a.onToggleDetails,children:[a.detailsExpanded?t.jsx(wn,{size:16}):t.jsx(Fn,{size:16}),a.detailsExpanded?o.t("button.hide_details","Hide details"):o.t("dashboard.call_details","Call Details")]})]})]}),t.jsxs("div",{className:a.detailsExpanded?"table-detail-layout":"table-detail-layout detail-collapsed",children:[t.jsxs("div",{className:$.gridColumn,children:[t.jsx(Yt,{ariaLabel:h,columns:a.columns,data:a.calls,identityColumnId:"thread",lockedColumnIds:["investigate"],getRowId:d=>d.id,mobile:{primary:d=>d.thread,secondary:d=>o.translateText(`${d.time} · ${d.model} · ${ee(d.totalTokens)} tokens · ${q(d.cachedPct)} cache`),actionLabel:d=>Xt(d)},sorting:a.sorting,onSortingChange:a.onSortingChange,manualSorting:!0,columnVisibility:a.gridPreferences.columnVisibility,onColumnVisibilityChange:a.gridPreferences.setColumnVisibility,density:a.gridPreferences.density,onDensityChange:a.gridPreferences.setDensity,onRestoreDefaults:a.gridPreferences.restoreDefaults,selectedRowId:(F=a.selectedCall)==null?void 0:F.id,onRowSelect:d=>a.onSelectCall(d.id),onRowActivate:d=>i.onOpenInvestigator(d.id),activateOnClick:!0,selectOnHover:!0,viewportHeight:560,emptyLabel:"No rows match current filters."}),t.jsxs("div",{className:$.gridFooter,"aria-live":"polite",children:[t.jsx("span",{children:o.translateText(`${a.calls.length.toLocaleString()} loaded / ${a.totalMatchedCalls.toLocaleString()} matched`)}),a.focused&&a.hasNextPage?t.jsx("button",{className:"toolbar-button",type:"button",onClick:a.onLoadMore,disabled:a.isFetchingNextPage,children:a.isFetchingNextPage?"Loading more...":`Load ${B.toLocaleString()} more`}):null]})]}),a.detailsExpanded?t.jsx(ls,{call:a.selectedCall,calls:e.calls,...i}):null]}),t.jsxs("details",{className:$.patternsDisclosure,children:[t.jsxs("summary",{children:[t.jsx(ut,{size:17}),"Usage patterns"]}),t.jsxs("div",{className:"dashboard-grid three",children:[t.jsx(Z,{title:"Usage Over Time",subtitle:"Tokens",children:t.jsx(et,{series:e.tokenSeries,yLabel:"Tokens",height:220})}),t.jsx(Z,{title:"Cost by Model",subtitle:"Estimated USD",children:t.jsx(ss,{data:e.modelCosts,valueLabel:Ae})}),t.jsx(Z,{title:"Cache Hit Rate Over Time",subtitle:o.translateText("Daily"),children:t.jsx(et,{series:e.cacheSeries,yLabel:"Cache %",height:220,valueFormatter:d=>`${d}%`})})]})]})]})}function Ls(e,n,s){return e&&n?"Updating focused rows":e?"Loading focused rows":n?"Focused paged API":s||"Stored snapshot"}function ia(e,n="",s=""){const a=Pe();return ke(yt(e,{globalQuery:n,localQuery:E("call_q"),modelFilter:E("model")||"all",effortFilter:E("effort")||"all",confidenceFilter:gt(),sourceFilter:jt(),timeFilter:vt(),dateStart:le("from"),dateEnd:le("to"),activePreset:s}),a,Ct(a))}const Nt={time:"time",duration:"duration",gap:"previousCallGap",attention:"signal",thread:"thread",initiator:"initiator",model:"model",effort:"effort",total:"totalTokens",cached:"cachedInput",uncached:"uncachedInput",output:"output",reasoning:"reasoningOutput",cost:"cost",usage:"credits",cache:"cachedPct",context:"contextWindowPct"},ks=Object.fromEntries(Object.entries(Nt).map(([e,n])=>[n,e]));function oa({model:e,globalQuery:n,activePreset:s,onRefresh:a,contextRuntime:i,onContextApiEnabledChange:o,onOpenInvestigator:r,onCopyCallLink:h,includeArchived:F=!1,sourceKey:d,sourceRevision:b="",scopeSince:j=null,focusedEndpointsEnabled:C=!0,workspaceSwitcher:D}){var We,Ye;const N=Ps(e,n),{localQuery:y,modelFilter:m,effortFilter:l,confidenceFilter:p,sourceFilter:w,timeFilter:g,dateStart:_,dateEnd:T,sortKey:x,setSortKey:L,sortDirection:v,setSortDirection:u,gridPreferences:te,density:G,selectedCallId:R,setSelectedCallId:ne,visibleCallRows:Q,setVisibleCallRows:de,exportStatus:ue,setExportStatus:W,filterStatus:he,detailsExpanded:me,searchInputRef:se,modelOptions:xe,effortOptions:pe,sourceCoverage:fe,dateRangeStatus:Y,resetCallTablePage:I,updateLocalQuery:ge,updateModelFilter:je,updateEffortFilter:ve,updateConfidenceFilter:be,updateSourceFilter:Ce,updateTimeFilter:ye,updateDateStart:Se,updateDateEnd:we,updateSortKey:Fe,updateSortDirection:Ne,clearCallFilters:Ee,toggleCallDetails:Te}=N,f=c.useMemo(()=>[...zt,Jt({onOpenInvestigator:r,onCopyCallLink:h})],[h,r]),A=c.useMemo(()=>Zn({runtime:i,enabled:C,activePreset:s,sourceFilter:w,sortKey:x,scopeSince:j,timeFilter:g,dateStart:_,dateEnd:T,confidenceFilter:p,globalQuery:n,localQuery:y,modelFilter:m,effortFilter:l}),[s,p,i,T,_,l,C,n,y,m,x,w,j,g]),V=Lt({...It({runtime:i,includeArchived:F,sourceKey:d,sourceRevision:b,filters:A.filters,sort:A.sort,direction:v,pageSize:B}),enabled:A.enabled,placeholderData:M=>M}),Ue=c.useMemo(()=>yt(e.calls,{globalQuery:n,localQuery:y,modelFilter:m,effortFilter:l,confidenceFilter:p,sourceFilter:w,timeFilter:g,dateStart:_,dateEnd:T,activePreset:s}),[s,p,T,_,l,n,y,e.calls,m,w,g]),qe=c.useMemo(()=>ke(Ue,x,v),[Ue,v,x]),Ge=c.useMemo(()=>{var M;return((M=V.data)==null?void 0:M.pages.flatMap(X=>X.rows))??[]},[V.data]),ae=A.enabled&&!!V.data,H=c.useMemo(()=>ae?ke(Ge,x,v):qe,[Ge,qe,v,x,ae]),Me=ae?((Ye=(We=V.data)==null?void 0:We.pages[0])==null?void 0:Ye.totalMatchedRows)??H.length:e.calls.length,Et=c.useMemo(()=>Nn({shownCount:H.length,totalCount:Me,localQuery:y,globalQuery:n,modelFilter:m,effortFilter:l,confidenceFilter:p,sourceFilter:w,timeFilter:g,dateRangeStatus:Y,activePresetLabel:s?mt(s):""}),[s,p,Y,l,n,y,m,H.length,w,g,Me]),Qe=c.useMemo(()=>[{id:Nt[x],desc:v==="desc"}],[v,x]),ie=R===oe?H[0]??null:H.find(M=>M.id===R)??H[0]??null,z=ie&&(ie.id===R||R===oe)?ie.id:"";c.useEffect(()=>{R===oe&&z&&ne(z)},[R,z]),c.useEffect(()=>{const M=_e({localQuery:y,modelFilter:m,effortFilter:l,confidenceFilter:p,sourceFilter:w,timeFilter:g,dateStart:_,dateEnd:T,sortKey:x,sortDirection:v,density:G,selectedRecordId:z,visibleRowCount:Q,pageSize:B});M.toString()!==window.location.href&&window.history.replaceState(null,"",M)},[p,T,_,G,l,y,m,z,v,x,w,g,Q]);function Tt(){Ut(`codex-calls-${Qt()}.csv`,qt(H,Gt)),W(`Exported ${H.length} calls`)}async function Mt(){try{const M=_e({localQuery:y,modelFilter:m,effortFilter:l,confidenceFilter:p,sourceFilter:w,timeFilter:g,dateStart:_,dateEnd:T,sortKey:x,sortDirection:v,density:G,selectedRecordId:z,visibleRowCount:Q,pageSize:B});if(!await ht(M.toString()))throw new Error("Clipboard unavailable");W("Copied Calls view link")}catch{W("Copy unavailable in browser")}}function Dt(M){var Xe,Je;const X=typeof M=="function"?M(Qe):M,De=ks[((Xe=X[0])==null?void 0:Xe.id)??""]??"time",_t=De!==x;L(De),u(_t?U(De):(Je=X[0])!=null&&Je.desc?"desc":"asc"),I()}async function Pt(){!V.hasNextPage||V.isFetchingNextPage||(await V.fetchNextPage(),de(M=>M+B))}return t.jsx(_s,{model:e,header:{workspaceSwitcher:D,canExport:!!H.length,onExport:Tt,onCopyView:Mt,onRefresh:a},filters:{searchInputRef:se,localQuery:y,modelFilter:m,effortFilter:l,confidenceFilter:p,sourceFilter:w,timeFilter:g,dateStart:_,dateEnd:T,sortKey:x,sortDirection:v,modelOptions:xe,effortOptions:pe,sourceCoverage:fe,dateRangeStatus:Y,onLocalQueryChange:ge,onModelFilterChange:je,onEffortFilterChange:ve,onConfidenceFilterChange:be,onSourceFilterChange:Ce,onTimeFilterChange:ye,onDateStartChange:Se,onDateEndChange:we,onSortKeyChange:Fe,onSortDirectionChange:Ne,onClear:Ee},table:{activePreset:s,exportStatus:ue,filterStatus:he,subtitle:Et,focused:ae,focusedReason:A.reason,isFetching:V.isFetching,isFetchingNextPage:V.isFetchingNextPage,hasNextPage:!!V.hasNextPage,detailsExpanded:me,calls:H,totalMatchedCalls:Me,columns:f,sorting:Qe,gridPreferences:te,selectedCall:ie,onSortingChange:Dt,onSelectCall:ne,onLoadMore:Pt,onToggleDetails:Te},inspector:{contextRuntime:i,includeArchived:F,sourceRevision:b,hydrateThreadCalls:C,onContextApiEnabledChange:o,onOpenInvestigator:r,onCopyCallLink:h}})}export{oa as CallsPage,ia as callsForCurrentUrl}; + */const Tn=Re("PanelRightOpen",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M15 3v18",key:"14nvp0"}],["path",{d:"m10 15-3-3 3-3",key:"1pgupc"}]]);function En(e){const n=[],s=[e.localQuery.trim(),e.globalQuery.trim()].filter(Boolean);s.length&&n.push(`Search ${s.map(i=>`"${i}"`).join(" + ")}`),e.modelFilter!=="all"&&n.push(`Model ${e.modelFilter}`),e.effortFilter!=="all"&&n.push(`Effort ${e.effortFilter}`),e.confidenceFilter!=="all"&&n.push(`Confidence ${Ln(e.confidenceFilter)}`),e.sourceFilter!=="all"&&n.push(`Source ${kn(e.sourceFilter)}`),e.dateRangeStatus.invalid?n.push("Date range invalid"):(e.dateRangeStatus.active||e.timeFilter!=="all")&&n.push(e.dateRangeStatus.label||In(e.timeFilter)),e.activePresetLabel&&n.push(`Preset ${e.activePresetLabel}`);const a=`Showing ${e.shownCount.toLocaleString()} of ${e.totalCount.toLocaleString()} aggregate rows`;return n.length?`${a} - Filters: ${n.join("; ")}`:a}function Dn(e,n){return n==="project"?`${e.project.toLocaleString()} project/cwd rows`:n==="session"?`${e.session.toLocaleString()} session-linked rows`:n==="git"?`${e.git.toLocaleString()} git rows`:n==="source-file"?`${e.sourceFile.toLocaleString()} source-file rows`:n==="missing"?`${e.missing.toLocaleString()} rows missing source`:`${e.project.toLocaleString()} project, ${e.session.toLocaleString()} session, ${e.git.toLocaleString()} git`}function Mn(e,n){return n==="all"?!0:n==="project"?Be(e):n==="session"?Ve(e):n==="git"?He(e):n==="source-file"?Ke(e):!ft(e)}function Pn(e,n){if(n==="all")return!0;if(n==="cost-exact")return!e.pricingEstimated&&Number.isFinite(e.cost)&&e.cost>0;if(n==="cost-estimated")return e.pricingEstimated;if(n==="cost-unpriced")return!e.pricingEstimated&&(!Number.isFinite(e.cost)||e.cost<=0);const s=e.usageCreditConfidence.toLowerCase();return n==="credit-exact"?s.includes("exact"):n==="credit-override"?s.includes("override"):n==="credit-estimated"?s.includes("estimated"):s.includes("missing")||s.includes("unpriced")}function _n(e){return e.reduce((n,s)=>({project:n.project+(Be(s)?1:0),session:n.session+(Ve(s)?1:0),git:n.git+(He(s)?1:0),sourceFile:n.sourceFile+(Ke(s)?1:0),missing:n.missing+(ft(s)?0:1),total:n.total+1}),{project:0,session:0,git:0,sourceFile:0,missing:0,total:0})}function Be(e){return!!(e.project||e.projectRelativeCwd||e.cwd||e.projectTags.length)}function Ve(e){return!!(e.sessionId||e.turnId||e.parentSessionId)}function He(e){return!!(e.gitBranch||e.gitRemoteLabel||e.gitRemoteHash)}function Ke(e){return!!(e.sourceFile||e.lineNumber!==null)}function ft(e){return Be(e)||Ve(e)||He(e)||Ke(e)}function Ln(e){return e==="cost-exact"?"exact cost":e==="cost-estimated"?"estimated cost":e==="cost-unpriced"?"unpriced cost":e==="credit-exact"?"exact credit rate":e==="credit-estimated"?"estimated credit mapping":e==="credit-override"?"user credit override":"missing credit rate"}function kn(e){return e==="project"?"project/cwd":e==="session"?"session-linked":e==="git"?"git metadata":e==="source-file"?"source file":"missing source"}function In(e){return e==="today"?"Today":e==="this-week"?"This week":e==="last-7-days"?"Last 7 days":e==="this-month"?"This month":e==="custom"?"Custom date range":"All time"}const oe="__detail_first__",$n=new Set(["all","cost-exact","cost-estimated","cost-unpriced","credit-exact","credit-estimated","credit-override","credit-missing"]),Rn=new Set(["all","today","this-week","last-7-days","this-month","custom"]),An=new Set(["all","project","session","git","source-file","missing"]),On=new Set(["time","duration","gap","attention","thread","initiator","model","effort","total","cached","uncached","output","reasoning","cost","usage","cache","context"]);function T(e,n=window.location.href){var s;return((s=new URL(n).searchParams.get(e))==null?void 0:s.trim())??""}function gt(e=window.location.href){const n=T("confidence",e)||T("pricing",e),s=zn(n);return $n.has(s)?s:"all"}function jt(e=window.location.href){const n=T("source",e);return An.has(n)?n:"all"}function vt(e=window.location.href){const n=T("date",e)||T("time",e);return Rn.has(n)?n:"all"}function le(e,n=window.location.href){return Le(T(e,n))}function Bn(e=window.location.href){const n=T("record",e);return n||(T("detail",e)==="first"?oe:null)}function Pe(e=window.location.href){const n=T("sort",e);return bt(n)}function bt(e){return On.has(e)?e:"time"}function Ct(e,n=window.location.href){const s=T("direction",n);return s==="asc"||s==="desc"?s:U(e)}function Vn(e=window.location.href){return T("density",e)==="roomy"?"roomy":"dense"}function Hn(e,n=window.location.href){const s=Number(T("page",n)||1);return Number.isFinite(s)&&s>1?Math.floor(s)*e:e}function _e(e,n=window.location.href){const s=new URL(n);return s.searchParams.set("view","calls"),s.searchParams.delete("detail"),s.searchParams.delete("pricing"),k(s,"record",e.selectedRecordId,""),k(s,"call_q",e.localQuery.trim(),""),k(s,"model",e.modelFilter,"all"),k(s,"effort",e.effortFilter,"all"),k(s,"confidence",e.confidenceFilter,"all"),k(s,"source",e.sourceFilter,"all"),k(s,"time",e.timeFilter,"all"),k(s,"date",e.timeFilter,"all"),k(s,"from",e.timeFilter==="custom"?e.dateStart:"",""),k(s,"to",e.timeFilter==="custom"?e.dateEnd:"",""),k(s,"sort",e.sortKey,"time"),k(s,"direction",e.sortDirection,U(e.sortKey)),k(s,"density",e.density,"dense"),k(s,"page",String(Kn(e.visibleRowCount,e.pageSize)),"1"),s}function U(e){return e==="cache"||e==="effort"||e==="initiator"||e==="model"||e==="thread"?"asc":"desc"}function Le(e){const n=Un(e);return n?qn(n):""}function Kn(e,n){return Math.max(1,Math.ceil(Math.max(e,n)/n))}function k(e,n,s,a){if(!s||s===a){e.searchParams.delete(n);return}e.searchParams.set(n,s)}function zn(e){return e==="official"?"cost-exact":e==="estimated"?"cost-estimated":e==="unpriced"?"cost-unpriced":e}function Un(e){if(!/^\d{4}-\d{2}-\d{2}$/.test(e))return null;const[n,s,a]=e.split("-").map(Number),i=new Date(n,s-1,a);return i.getFullYear()===n&&i.getMonth()===s-1&&i.getDate()===a?i:null}function qn(e){return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`}function yt(e,n){return e.filter(s=>Gn(s,n))}function ke(e,n,s){return[...e].sort((a,i)=>Qn(a,i,n,s))}function ze(e,n,s,a){const i=Xn(a);if(e==="custom"){const o=lt(n),r=lt(s);return o&&r&&o>r?{active:!0,invalid:!0,start:o,endExclusive:K(r,1),label:"Invalid date range"}:{active:!!(o||r),invalid:!1,start:o,endExclusive:r?K(r,1):null,label:J("Custom",o,r)}}if(e==="today")return{active:!0,invalid:!1,start:i,endExclusive:K(i,1),label:J("Today",i,i)};if(e==="this-week"){const o=Jn(i);return{active:!0,invalid:!1,start:o,endExclusive:K(o,7),label:J("This week",o,K(o,6))}}if(e==="last-7-days"){const o=K(i,-6);return{active:!0,invalid:!1,start:o,endExclusive:K(i,1),label:J("Last 7 days",o,i)}}if(e==="this-month"){const o=new Date(i.getFullYear(),i.getMonth(),1),r=new Date(i.getFullYear(),i.getMonth()+1,1);return{active:!0,invalid:!1,start:o,endExclusive:r,label:J("This month",o,K(r,-1))}}return{active:!1,invalid:!1,start:null,endExclusive:null,label:"All time"}}function Gn(e,n){if(n.modelFilter!=="all"&&e.model!==n.modelFilter||n.effortFilter!=="all"&&e.effort!==n.effortFilter||!Pn(e,n.confidenceFilter)||!Mn(e,n.sourceFilter)||!Zn(e,n.timeFilter,n.dateStart,n.dateEnd)||n.activePreset&&!$t(e,n.activePreset))return!1;const s=[e.thread,e.cwd,e.project,e.projectRelativeCwd,e.gitBranch,e.gitRemoteLabel,e.sessionId,e.model,e.effort,e.initiator,e.initiatorReason,e.signal,e.recommendation,e.rawTime,e.sourceFile,e.lineNumber,e.tags.join(" ")];return[n.globalQuery,n.localQuery].every(a=>Jt(s,a))}function Qn(e,n,s,a){const i=rt(e,s),o=rt(n,s);if(i===null&&o!==null)return 1;if(o===null&&i!==null)return-1;const r=Yn(i,o);return r!==0?a==="asc"?r:-r:Ie(e,n)||e.id.localeCompare(n.id)}function rt(e,n){return n==="time"?$e(e):n==="duration"?e.durationSeconds:n==="gap"?e.previousCallGapSeconds:n==="attention"?Wn(e):n==="thread"?e.thread.toLowerCase():n==="initiator"?e.initiator.toLowerCase():n==="model"?e.model.toLowerCase():n==="effort"?e.effort.toLowerCase():n==="total"?e.totalTokens:n==="cached"?e.input*(e.cachedPct/100):n==="uncached"?e.uncachedInput:n==="output"?e.output:n==="reasoning"?e.reasoningOutput:n==="cost"?e.cost:n==="usage"?e.credits:n==="cache"?e.cachedPct:e.contextWindowPct}function Wn(e){const n=e.signal&&e.signal!=="aggregate"?1e3:0,s=e.recommendation?750:0,a=e.contextWindowPct??0,i=Math.min(e.uncachedInput/1e3,250),o=Math.min(e.cost*25,200),r=Math.min(e.credits,200),h=Math.min(e.durationSeconds/60,120);return n+s+a+i+o+r+h}function Yn(e,n){return e===null&&n===null?0:e===null?1:n===null?-1:typeof e=="string"||typeof n=="string"?String(e).localeCompare(String(n)):e-n}function Ie(e,n){return $e(n)-$e(e)}function $e(e){const n=Date.parse(e.rawTime);return Number.isFinite(n)?n:0}function lt(e){if(!/^\d{4}-\d{2}-\d{2}$/.test(e))return null;const[n,s,a]=e.split("-").map(Number),i=new Date(n,s-1,a);return i.getFullYear()===n&&i.getMonth()===s-1&&i.getDate()===a?i:null}function ct(e){return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`}function J(e,n,s){const a=n?ct(n):"",i=s?ct(s):"";return a&&i&&a===i?`${e}: ${a}`:a&&i?`${e}: ${a} to ${i}`:a?`${e}: from ${a}`:i?`${e}: through ${i}`:e}function Xn(e=new Date){return new Date(e.getFullYear(),e.getMonth(),e.getDate())}function K(e,n){return new Date(e.getFullYear(),e.getMonth(),e.getDate()+n)}function Jn(e){const n=e.getDay();return K(e,n===0?-6:1-n)}function Zn(e,n,s="",a="",i=new Date){if(n==="all")return!0;const o=ze(n,s,a,i);if(o.invalid)return!1;if(!o.active)return!0;const r=Date.parse(e.rawTime);return!(!Number.isFinite(r)||o.start&&r=o.endExclusive.getTime())}const es={time:"time",duration:"duration",gap:"gap",thread:"thread",initiator:"initiator",model:"model",effort:"effort",total:"tokens",cached:"cached",uncached:"uncached",output:"output",reasoning:"reasoning",cache:"cache"};function ts(e){const n=es[e.sortKey],s=ze(e.timeFilter,e.dateStart,e.dateEnd,new Date),a=ns(e,n,s.invalid);return{enabled:!a,reason:a,sort:n??"time",filters:{query:[e.globalQuery.trim(),e.localQuery.trim()].filter(Boolean).join(" "),model:e.modelFilter==="all"?void 0:e.modelFilter,effort:e.effortFilter==="all"?void 0:e.effortFilter,...ss(e.confidenceFilter),...as(s.start,s.endExclusive,e.scopeSince)}}}function ns(e,n,s){return!e.enabled||e.runtime.fileMode||!e.runtime.apiToken?"Stored snapshot":e.globalQuery.trim()&&e.localQuery.trim()?"Multiple searches use loaded snapshot":e.activePreset?"Preset uses loaded snapshot":e.sourceFilter!=="all"?"Source filter uses loaded snapshot":n?s?"Invalid date range":"":"This sort uses loaded snapshot"}function ss(e){return e==="cost-exact"?{pricingStatus:"priced"}:e==="cost-estimated"?{pricingStatus:"estimated"}:e==="cost-unpriced"?{pricingStatus:"unpriced"}:e==="credit-exact"?{creditConfidence:"exact"}:e==="credit-estimated"?{creditConfidence:"estimated"}:e==="credit-override"?{creditConfidence:"user_override"}:e==="credit-missing"?{creditConfidence:"unpriced"}:{}}function as(e,n,s){return{since:(e==null?void 0:e.toISOString())??s??void 0,until:n?new Date(n.getTime()-1).toISOString():void 0}}function is({data:e,valueLabel:n=s=>String(s)}){const s=Math.max(...e.map(a=>a.value),1);return t.jsx("div",{className:"bar-list",children:e.map(a=>t.jsxs("div",{className:"bar-row",children:[t.jsx("span",{children:a.label}),t.jsx("div",{className:"bar-track","aria-hidden":"true",children:t.jsx("i",{style:{width:`${a.value/s*100}%`,backgroundColor:a.color??"#2563eb"}})}),t.jsx("strong",{children:n(a.value)})]},a.label))})}function P({label:e,value:n,detail:s}){return t.jsxs("span",{className:"drilldown-metric",children:[t.jsx("small",{children:e}),t.jsx("strong",{children:n}),t.jsx("em",{children:s})]})}function S({label:e,value:n}){return t.jsxs("div",{children:[t.jsx("dt",{children:e}),t.jsx("dd",{children:n})]})}function os({call:e,contextRuntime:n,onContextApiEnabledChange:s}){const a=ce(),[i,o]=c.useState(()=>sn(e.id)??tt),[r,h]=c.useState({status:"idle"}),F=!!n.apiToken&&!n.fileMode,d=F&&n.contextApiEnabled;c.useEffect(()=>{const l=nt(e.id,i);h(l?{status:"loaded",payload:l}:{status:"idle"})},[e.id,i.includeCompactionHistory,i.includeToolOutput,i.maxChars,i.maxEntries,i.mode]);async function b(){h({status:"loading",message:"Enabling localhost context API..."});try{const l=await on(n);s(l),h({status:"idle",message:l?"Context API enabled. Load this call when ready.":"Context API did not enable."})}catch(l){h({status:"error",message:st(l)})}}async function j(l=i,p="Loading selected-turn evidence..."){at(e.id,l);const w=nt(e.id,l);if(w){h({status:"loaded",payload:w});return}h({status:"loading",message:p});try{const g=await rn(e.id,n,l);ln(e.id,l,g),h({status:"loaded",payload:g})}catch(g){h({status:"error",message:st(g)})}}function C(){const l={...i,mode:"full"};o(l),j(l,"Loading full turn analysis...")}function M(l){const p=pn(l,i);o(p),j(p,"Loading older context...")}function N(){const l={...i,includeToolOutput:!0};o(l),j(l,"Loading omitted tool output...")}function y(){const l={...i,includeCompactionHistory:!0};o(l),j(l,"Loading compacted replacement...")}function m(l,p){o(w=>{const g={...w,[l]:p};return at(e.id,g),g})}return t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"locked-context-card",children:[t.jsx(pt,{size:20}),t.jsxs("div",{children:[t.jsx("strong",{children:"Raw context is gated"}),t.jsx("p",{children:an(n)})]})]}),t.jsxs("dl",{className:"detail-list",children:[t.jsx(S,{label:"Record id",value:e.id}),t.jsx(S,{label:"Time",value:e.time}),t.jsx(S,{label:"Thread",value:e.thread}),t.jsx(S,{label:"Model",value:e.model}),t.jsx(S,{label:"Effort",value:e.effort})]}),t.jsxs("div",{className:"context-action-grid",children:[t.jsx("button",{className:"toolbar-button",type:"button",onClick:b,disabled:!F||n.contextApiEnabled||r.status==="loading",children:a.t("button.enable_context_loading","Enable context loading")}),t.jsx("button",{className:"primary-button",type:"button",onClick:()=>j(),disabled:!d||r.status==="loading",children:a.t("button.show_turn_evidence","Show turn log evidence")}),t.jsx("button",{className:"toolbar-button",type:"button",onClick:C,disabled:!d||r.status==="loading",children:a.t("button.full_serialized_analysis","Run full serialized analysis")}),t.jsxs("label",{className:"context-field",children:[t.jsx("span",{children:"Mode"}),t.jsxs("select",{"aria-label":"Side panel context mode",value:i.mode,disabled:!d||r.status==="loading",onChange:l=>m("mode",l.target.value==="full"?"full":"quick"),children:[t.jsx("option",{value:"quick",children:"Quick"}),t.jsx("option",{value:"full",children:"Full"})]})]}),t.jsxs("label",{className:"context-field",children:[t.jsx("span",{children:"Entries"}),t.jsxs("select",{"aria-label":"Side panel context entries",value:String(i.maxEntries),disabled:!d||r.status==="loading",onChange:l=>m("maxEntries",Number(l.target.value)),children:[t.jsx("option",{value:"20",children:"20"}),t.jsx("option",{value:"50",children:"50"}),t.jsx("option",{value:"100",children:"100"}),t.jsx("option",{value:"0",children:"All"})]})]}),t.jsxs("label",{className:"toggle-row",children:[t.jsx("input",{type:"checkbox",checked:i.includeToolOutput,disabled:!d||r.status==="loading",onChange:l=>m("includeToolOutput",l.target.checked)}),a.t("button.include_tool_output","Include tool output")]}),t.jsxs("label",{className:"toggle-row",children:[t.jsx("input",{type:"checkbox",checked:i.includeCompactionHistory,disabled:!d||r.status==="loading",onChange:l=>m("includeCompactionHistory",l.target.checked)}),"Include compaction history"]}),t.jsxs("label",{className:"toggle-row",children:[t.jsx("input",{type:"checkbox",checked:i.maxChars===0,disabled:!d||r.status==="loading",onChange:l=>m("maxChars",l.target.checked?0:tt.maxChars)}),a.t("button.no_char_limit","No char limit")]})]}),r.status==="idle"&&r.message?t.jsx("p",{className:"context-state-note",children:r.message}):null,r.status==="loading"?t.jsx("p",{className:"context-state-note",children:r.message}):null,r.status==="error"?t.jsx("p",{className:"context-state-note error",children:r.message}):null,r.status==="loaded"?t.jsx(rs,{call:e,payload:r.payload,onLoadOlder:M,onRunFullAnalysis:C,onLoadCompactionHistory:y,onLoadToolOutput:N}):null,t.jsx("p",{className:"privacy-note",children:"Raw context is never embedded in the dashboard HTML. This view reads the selected local JSONL turn only after an explicit request."})]})}function rs({call:e,payload:n,onLoadOlder:s,onRunFullAnalysis:a,onLoadCompactionHistory:i,onLoadToolOutput:o}){var _,E;const r=ce(),h=n.entries??[],F=n.omitted??{},d=Number(F.older_entries??0),b=cn(n),j=8,C=e.id||String(n.record_id??""),[M,N]=c.useState(()=>it(C)),[y,m]=c.useState(()=>ot(C,h));c.useEffect(()=>{N(it(C)),m(ot(C,h))},[h.length,n.context_mode,n.include_compaction_history,n.include_tool_output,(_=n.omitted)==null?void 0:_.max_chars,(E=n.omitted)==null?void 0:E.max_entries,n.record_id,C]);const l=M?h:h.slice(0,j),p=Math.max(h.length-l.length,0);function w(){N(x=>{const L=!x;return gn(C,L),L})}function g(x,L){fn(C,x,L),m(v=>{const u=new Set(v);return L?u.add(x):u.delete(x),u})}return t.jsxs("div",{className:"context-evidence",children:[t.jsxs("div",{className:"context-evidence-summary",children:[t.jsx(P,{label:"Entries",value:O(h.length),detail:String(n.context_mode??"quick")}),t.jsx(P,{label:"Visible chars",value:O(Number(n.visible_char_count??0)),detail:"redacted local text"}),t.jsx(P,{label:"Visible tokens",value:O(Number(n.visible_token_estimate??0)),detail:"estimator"}),t.jsx(P,{label:"Older omitted",value:O(d),detail:"entry budget"})]}),b.length?t.jsx("p",{className:"context-note",children:b.join(" ")}):null,t.jsx(dn,{call:e,payload:n,onRunFullAnalysis:a}),d>0?t.jsx("div",{className:"context-followup-actions",children:t.jsx("button",{className:"toolbar-button",type:"button",onClick:()=>s(n),children:r.t("button.load_older_context","Load older entries")})}):null,t.jsxs("div",{className:"context-entry-list",children:[l.map((x,L)=>{const v=un(x,L);return t.jsxs("details",{className:"context-entry",open:y.has(v),onToggle:u=>g(v,u.currentTarget.open),children:[t.jsx("summary",{className:"context-entry-summary",children:t.jsxs("div",{className:"context-entry-meta",children:[t.jsx("strong",{children:x.label||x.role||x.type||`Entry ${L+1}`}),t.jsx("span",{children:x.line_number?`line ${x.line_number}`:x.timestamp||"local evidence"})]})}),t.jsx(hn,{entry:x}),x.tool_output_omitted?t.jsx("div",{className:"context-entry-actions",children:t.jsx("button",{className:"toolbar-button",type:"button",onClick:o,children:r.t("button.show_tool_output","Show tool output")})}):null,t.jsx(ls,{entry:x,onLoadCompactionHistory:i}),t.jsx("pre",{ref:u=>{u&&(u.scrollTop=xn(C,v))},onScroll:u=>mn(C,v,u.currentTarget.scrollTop),children:x.text||"[no visible text]"})]},v)}),h.length?null:t.jsx("p",{className:"empty-state",children:"No visible evidence entries returned for this call."})]}),h.length>j?t.jsxs("div",{className:"context-followup-actions",children:[t.jsx("button",{className:"toolbar-button",type:"button",onClick:w,children:M?`Show first ${O(j)} entries`:`Show all ${O(h.length)} returned entries`}),M?null:t.jsxs("span",{className:"context-entry-count-note",children:[O(p)," entries hidden in compact view"]})]}):null]})}function ls({entry:e,onLoadCompactionHistory:n}){const s=ce(),a=e.compaction;if(!(a!=null&&a.replacement_history_available))return null;const i=a.replacement_history??[],o=Number(a.replacement_entry_count??i.length);return t.jsxs("div",{className:"context-entry-compaction",children:[t.jsx("strong",{children:"Compaction detected"}),t.jsxs("span",{children:[O(o)," replacement history entries available."]}),i.length?t.jsx("div",{className:"context-replacement-history","aria-label":"Compacted replacement context",children:i.map((r,h)=>t.jsxs("div",{className:"context-replacement-entry",children:[t.jsx("strong",{children:r.label||`Replacement item ${h+1}`}),t.jsx("pre",{children:r.text||"[no visible replacement text]"})]},`${r.label??"replacement"}-${h}`))}):t.jsx("div",{className:"context-entry-actions",children:t.jsx("button",{className:"toolbar-button",type:"button",onClick:n,children:s.t("button.show_compaction_history","Show compacted replacement")})})]})}const cs=[{id:"summary",label:"Summary",icon:At},{id:"tokens",label:"Tokens",icon:ut},{id:"cache",label:"Cache",icon:Ot},{id:"thread",label:"Thread",icon:Fn},{id:"evidence",label:"Evidence",icon:pt}];function ds({call:e,calls:n,contextRuntime:s,includeArchived:a,sourceRevision:i,hydrateThreadCalls:o,onContextApiEnabledChange:r,onOpenInvestigator:h,onCopyCallLink:F}){const[d,b]=c.useState("summary"),[j,C]=c.useState(""),M=c.useMemo(()=>e?n.filter(m=>m.thread===e.thread).sort(Ie):[],[e,n]),N=nn({...kt({runtime:s,includeArchived:a,sourceRevision:i,threadKey:(e==null?void 0:e.threadKey)||(e==null?void 0:e.thread)||"",selectedRecordId:(e==null?void 0:e.id)??"",selectedEventTimestamp:(e==null?void 0:e.eventTimestamp)??""}),enabled:o&&!s.fileMode&&!!s.apiToken&&!!e,placeholderData:m=>m}),y=c.useMemo(()=>{var l;const m=((l=N.data)==null?void 0:l.rows)??M;return!e||m.some(p=>p.id===e.id)?m:[...m,e].sort(Ie)},[e,M,N.data]);return e?t.jsx("aside",{className:"side-panel drilldown-panel",children:t.jsxs(Z,{title:"Call Drill-Down",subtitle:`${e.thread} / ${e.model}`,children:[t.jsxs("div",{className:"call-summary",children:[t.jsx(re,{label:"Aggregate only",tone:"green"}),t.jsx(Rt,{call:e}),t.jsx(re,{label:"Raw context gated",tone:"blue"}),t.jsx("span",{className:"call-id",children:e.id.slice(0,12)})]}),t.jsxs("div",{className:"action-row",children:[t.jsxs("button",{className:"toolbar-button",type:"button",onClick:()=>h(e.id),children:[t.jsx(yn,{size:16}),"Open investigator"]}),t.jsxs("button",{className:"toolbar-button",type:"button",onClick:()=>fs(e,C),children:[t.jsx(dt,{size:16}),"Copy link"]})]}),j?t.jsx("p",{className:"context-state-note",children:j}):null,t.jsx("div",{className:"drilldown-tabs",role:"tablist","aria-label":"Call drill-down sections",children:cs.map(m=>{const l=m.icon,p=d===m.id;return t.jsxs("button",{type:"button",role:"tab","aria-selected":p,className:p?"active":"",onClick:()=>b(m.id),children:[t.jsx(l,{size:14}),m.label]},m.id)})}),t.jsxs("div",{className:"drilldown-tab-panel",role:"tabpanel",children:[d==="summary"?t.jsx(us,{call:e}):null,d==="tokens"?t.jsx(ms,{call:e}):null,d==="cache"?t.jsx(xs,{call:e,calls:y}):null,d==="thread"?t.jsx(ps,{call:e,calls:y,onOpenInvestigator:h,onCopyCallLink:F}):null,d==="evidence"?t.jsx(os,{call:e,contextRuntime:s,onContextApiEnabledChange:r},e.id):null]})]})}):t.jsx("aside",{className:"side-panel drilldown-panel",children:t.jsx(Z,{title:"Call Drill-Down",subtitle:"No matching call",children:t.jsx("p",{className:"empty-state",children:"No aggregate row matches the active filters."})})})}function us({call:e}){return t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"drilldown-metric-grid",children:[t.jsx(P,{label:"Total tokens",value:O(e.totalTokens),detail:`${ee(e.input)} input`}),t.jsx(P,{label:"Uncached input",value:O(e.uncachedInput),detail:"fresh billed input"}),t.jsx(P,{label:"Cache hit rate",value:q(e.cachedPct),detail:Ae(e)}),t.jsx(P,{label:"Estimated cost",value:Oe(e.cost),detail:Bt(e)}),t.jsx(P,{label:"Duration",value:e.duration,detail:Vt(e)}),t.jsx(P,{label:"Usage credits",value:e.credits?e.credits.toFixed(3):"-",detail:e.usageCreditConfidence})]}),t.jsx(jn,{call:e}),t.jsx(vn,{call:e}),t.jsx(hs,{call:e}),t.jsx(St,{call:e}),t.jsx(wt,{call:e}),e.recommendation?t.jsxs("div",{className:"recommendation-box",children:[t.jsx(Sn,{size:16}),t.jsx("p",{children:e.recommendation})]}):null]})}function hs({call:e}){return t.jsxs("div",{className:"composition-card accounting-snapshot-card",children:[t.jsxs("div",{className:"composition-head",children:[t.jsx("strong",{children:"Accounting Snapshot"}),t.jsx("span",{children:"pricing, credits, and cache savings"})]}),t.jsx(xt,{call:e})]})}function ms({call:e}){return t.jsxs(t.Fragment,{children:[t.jsx(St,{call:e}),t.jsx(xt,{call:e})]})}function xs({call:e,calls:n}){return t.jsxs(t.Fragment,{children:[t.jsx(wt,{call:e}),t.jsx(bn,{call:e,calls:n}),t.jsxs("dl",{className:"detail-list",children:[t.jsx(S,{label:"Cache state",value:Ae(e)}),t.jsx(S,{label:"Cache hit rate",value:q(e.cachedPct)}),t.jsx(S,{label:"Fresh share",value:q(Math.max(100-e.cachedPct,0))}),t.jsx(S,{label:"Signal",value:e.signal})]}),t.jsx("p",{className:"privacy-note",children:"Use this readout to decide whether the aggregate call needs deeper raw-context investigation."})]})}function ps({call:e,calls:n,onOpenInvestigator:s,onCopyCallLink:a}){const i=n.reduce((b,j)=>b+j.totalTokens,0),o=n.reduce((b,j)=>b+j.cost,0),r=n.reduce((b,j)=>b+j.cachedPct,0)/Math.max(n.length,1),h=Math.max(n.findIndex(b=>b.id===e.id),0),F=Math.min(Math.max(n.length,1),5),d=e.sourceFile?`${e.sourceFile}${e.lineNumber?`:${e.lineNumber}`:""}`:"Not available";return t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"drilldown-metric-grid",children:[t.jsx(P,{label:"Loaded thread calls",value:O(n.length),detail:`selected ${h+1} of ${n.length}`}),t.jsx(P,{label:"Thread tokens",value:ee(i),detail:"loaded aggregate rows"}),t.jsx(P,{label:"Thread cost",value:Oe(o),detail:"estimated aggregate"}),t.jsx(P,{label:"Avg cache",value:q(r),detail:Ht(n.map(b=>b.model),{style:"x",emptyLabel:"no model mix"})}),t.jsx(P,{label:"Context window",value:e.contextWindowPct===null?"-":q(e.contextWindowPct),detail:e.modelContextWindow?`${ee(e.modelContextWindow)} window`:"not reported"}),t.jsx(P,{label:"Parent thread",value:e.parentThread||"-",detail:e.parentSessionId||"no parent session"})]}),t.jsxs("div",{className:"composition-card",children:[t.jsxs("div",{className:"composition-head",children:[t.jsx("strong",{children:"Thread timeline"}),t.jsxs("span",{children:[F," nearby loaded calls"]})]}),t.jsx(Cn,{selectedCall:e,calls:n,onOpenInvestigator:s,onCopyCallLink:a,copyAriaContext:"side-panel thread call"})]}),t.jsxs("div",{className:"composition-card",children:[t.jsxs("div",{className:"composition-head",children:[t.jsx("strong",{children:"Call Narrative"}),t.jsx("span",{children:e.initiatorConfidence||"aggregate inference"})]}),t.jsxs("dl",{className:"detail-list compact",children:[t.jsx(S,{label:"Initiated by",value:e.initiator||"unknown"}),t.jsx(S,{label:"Initiator reason",value:e.initiatorReason||"Not reported"}),t.jsx(S,{label:"Parent thread",value:e.parentThread||"None"}),t.jsx(S,{label:"Parent session",value:e.parentSessionId||"None"}),t.jsx(S,{label:"Timestamp",value:e.time}),t.jsx(S,{label:"Duration",value:e.duration}),t.jsx(S,{label:"Previous gap",value:e.previousCallGap})]})]}),t.jsxs("dl",{className:"detail-list",children:[t.jsx(S,{label:"Project",value:e.project||"Unknown"}),t.jsx(S,{label:"Project path",value:e.projectRelativeCwd||e.cwd||"."}),t.jsx(S,{label:"Source line",value:d}),t.jsx(S,{label:"Session",value:e.sessionId||"Not available"}),t.jsx(S,{label:"Git branch",value:e.gitBranch||"Unknown"}),t.jsx(S,{label:"Remote",value:e.gitRemoteLabel||e.gitRemoteHash||"None"})]})]})}function St({call:e}){const s=[{label:"Cached input",value:Math.max(e.input-e.uncachedInput,0),color:"#2563eb"},{label:"Uncached input",value:e.uncachedInput,color:"#f59e0b"},{label:"Output",value:e.output,color:"#059669"},{label:"Reasoning",value:e.reasoningOutput,color:"#7c3aed"}].filter(i=>i.value>0),a=Math.max(s.reduce((i,o)=>i+o.value,0),1);return t.jsxs("div",{className:"composition-card",children:[t.jsxs("div",{className:"composition-head",children:[t.jsx("strong",{children:"Token composition"}),t.jsxs("span",{children:[ee(a)," visible tokens"]})]}),t.jsx("div",{className:"composition-bar",role:"img","aria-label":"Token composition",children:s.map(i=>t.jsx("i",{style:{width:`${Math.max(i.value/a*100,3)}%`,background:i.color}},i.label))}),t.jsx("div",{className:"composition-legend",children:s.map(i=>t.jsxs("span",{children:[t.jsx("i",{style:{background:i.color}}),i.label]},i.label))})]})}function wt({call:e}){const n=[{label:"Cache",value:Math.min(Math.max(e.cachedPct,0),100),color:"#2563eb"},{label:"Fresh",value:Math.min(Math.max(100-e.cachedPct,0),100),color:"#f59e0b"},{label:"Output",value:Math.min(Math.max(e.output/Math.max(e.totalTokens,1)*100,0),100),color:"#059669"}];return t.jsxs("div",{className:"cache-mini-card",children:[t.jsxs("div",{children:[t.jsx("strong",{children:"Cache delta readout"}),t.jsx("span",{children:Ae(e)})]}),t.jsx("div",{className:"cache-bars","aria-label":"Cache delta readout",children:n.map(s=>t.jsxs("span",{children:[t.jsx("i",{style:{height:`${Math.max(s.value,4)}%`,background:s.color}}),t.jsx("em",{children:s.label})]},s.label))})]})}async function fs(e,n){try{const s=new URL(window.location.href);if(s.searchParams.set("view","call"),s.searchParams.set("record",e.id),s.searchParams.set("return","calls"),!await ht(s.toString()))throw new Error("Clipboard unavailable");n("Copied investigator link")}catch{n("Copy unavailable in this browser")}}const gs="_page_gyxvc_1",js="_pageHeader_gyxvc_5",vs="_tableHeading_gyxvc_13",bs="_eyebrow_gyxvc_22",Cs="_headerActions_gyxvc_36",ys="_tableActions_gyxvc_37",Ss="_gridFooter_gyxvc_38",ws="_queryBar_gyxvc_46",Fs="_advancedFilters_gyxvc_80",Ns="_patternsDisclosure_gyxvc_85",Ts="_advancedFilterGrid_gyxvc_107",Es="_gridColumn_gyxvc_138",$={page:gs,pageHeader:js,tableHeading:vs,eyebrow:bs,headerActions:Cs,tableActions:ys,gridFooter:Ss,queryBar:ws,advancedFilters:Fs,patternsDisclosure:Ns,advancedFilterGrid:Ts,gridColumn:Es};function Ds({searchInputRef:e,localQuery:n,modelFilter:s,effortFilter:a,confidenceFilter:i,sourceFilter:o,timeFilter:r,dateStart:h,dateEnd:F,sortKey:d,sortDirection:b,modelOptions:j,effortOptions:C,sourceCoverage:M,dateRangeStatus:N,onLocalQueryChange:y,onModelFilterChange:m,onEffortFilterChange:l,onConfidenceFilterChange:p,onSourceFilterChange:w,onTimeFilterChange:g,onDateStartChange:_,onDateEndChange:E,onSortKeyChange:x,onSortDirectionChange:L,onClear:v}){return t.jsxs("section",{className:$.queryBar,"aria-label":"Call filters",children:[t.jsxs("label",{className:"search-box",children:[t.jsx("span",{className:"sr-only",children:"Search calls"}),t.jsx("input",{ref:e,value:n,onChange:u=>y(u.target.value),placeholder:"Search calls, cwd, projects, models..."})]}),t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"Model"}),t.jsxs("select",{value:s,onChange:u=>m(u.target.value),children:[t.jsx("option",{value:"all",children:"All models"}),j.map(u=>t.jsx("option",{value:u,children:u},u))]})]}),t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"Effort"}),t.jsxs("select",{value:a,onChange:u=>l(u.target.value),children:[t.jsx("option",{value:"all",children:"All effort"}),C.map(u=>t.jsx("option",{value:u,children:u},u))]})]}),t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"Confidence"}),t.jsxs("select",{"aria-label":"Confidence filter",value:i,onChange:u=>p(u.target.value),children:[t.jsx("option",{value:"all",children:"All confidence"}),t.jsx("option",{value:"cost-exact",children:"Exact cost"}),t.jsx("option",{value:"cost-estimated",children:"Estimated cost"}),t.jsx("option",{value:"cost-unpriced",children:"Unpriced cost"}),t.jsx("option",{value:"credit-exact",children:"Exact credit rate"}),t.jsx("option",{value:"credit-estimated",children:"Estimated credit mapping"}),t.jsx("option",{value:"credit-override",children:"User credit override"}),t.jsx("option",{value:"credit-missing",children:"Missing credit rate"})]})]}),t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"Time"}),t.jsxs("select",{"aria-label":"Time filter",value:r,onChange:u=>g(u.target.value),children:[t.jsx("option",{value:"all",children:"All time"}),t.jsx("option",{value:"today",children:"Today"}),t.jsx("option",{value:"this-week",children:"This week"}),t.jsx("option",{value:"last-7-days",children:"Last 7 days"}),t.jsx("option",{value:"this-month",children:"This month"}),t.jsx("option",{value:"custom",children:"Custom range"})]}),N.active||N.invalid?t.jsx("span",{className:"filter-status","data-state":N.invalid?"error":"active","aria-live":"polite",children:N.label}):null]}),t.jsxs("details",{className:$.advancedFilters,children:[t.jsxs("summary",{children:[t.jsx(Kt,{size:16}),"More filters"]}),t.jsxs("div",{className:$.advancedFilterGrid,children:[t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"Source"}),t.jsxs("select",{"aria-label":"Source filter",value:o,onChange:u=>w(u.target.value),children:[t.jsx("option",{value:"all",children:"All sources"}),t.jsx("option",{value:"project",children:"Project / cwd"}),t.jsx("option",{value:"session",children:"Session-linked"}),t.jsx("option",{value:"git",children:"Git metadata"}),t.jsx("option",{value:"source-file",children:"Source file"}),t.jsx("option",{value:"missing",children:"Missing source"})]}),t.jsx("span",{className:"filter-status","data-state":o==="all"?"active":"filtered","aria-live":"polite",children:Dn(M,o)})]}),t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"Start"}),t.jsx("input",{"aria-label":"Start date",type:"date",value:h,onChange:u=>_(u.target.value)})]}),t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"End"}),t.jsx("input",{"aria-label":"End date",type:"date",value:F,onChange:u=>E(u.target.value)})]}),t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"Sort"}),t.jsxs("select",{"aria-label":"Sort calls",value:d,onChange:u=>x(u.target.value),children:[t.jsx("option",{value:"time",children:"Newest calls"}),t.jsx("option",{value:"duration",children:"Longest duration"}),t.jsx("option",{value:"gap",children:"Longest gap"}),t.jsx("option",{value:"attention",children:"Needs attention"}),t.jsx("option",{value:"thread",children:"Thread name"}),t.jsx("option",{value:"initiator",children:"Initiated"}),t.jsx("option",{value:"model",children:"Model"}),t.jsx("option",{value:"effort",children:"Reasoning"}),t.jsx("option",{value:"total",children:"Most tokens"}),t.jsx("option",{value:"cached",children:"Cached"}),t.jsx("option",{value:"uncached",children:"Uncached"}),t.jsx("option",{value:"output",children:"Output"}),t.jsx("option",{value:"reasoning",children:"Reasoning output"}),t.jsx("option",{value:"cost",children:"Highest estimated cost"}),t.jsx("option",{value:"usage",children:"Highest Codex credits"}),t.jsx("option",{value:"cache",children:"Lowest cache ratio"}),t.jsx("option",{value:"context",children:"Highest context use"})]})]}),t.jsxs("label",{className:"filter-field",children:[t.jsx("span",{children:"Direction"}),t.jsxs("select",{"aria-label":"Sort direction",value:b,onChange:u=>L(u.target.value),children:[t.jsx("option",{value:"desc",children:"Descending"}),t.jsx("option",{value:"asc",children:"Ascending"})]})]})]})]}),t.jsxs("button",{className:"toolbar-button",type:"button",onClick:v,children:[t.jsx(zt,{size:16}),"Clear filters"]})]})}function Ms({workspaceSwitcher:e,canExport:n,onExport:s,onCopyView:a,onRefresh:i}){return t.jsxs("header",{className:$.pageHeader,children:[t.jsxs("div",{children:[t.jsx("p",{className:$.eyebrow,children:"Evidence explorer"}),t.jsx("h1",{children:"Calls"}),t.jsx("p",{children:"Find expensive, cold, or context-heavy calls and move directly into their evidence."})]}),t.jsxs("div",{className:$.headerActions,children:[e,t.jsxs("button",{className:"toolbar-button",type:"button",onClick:s,disabled:!n,children:[t.jsx(Ut,{size:16}),"Export"]}),t.jsxs("button",{className:"toolbar-button",type:"button",onClick:a,children:[t.jsx(dt,{size:16}),"Copy view"]}),t.jsxs("button",{className:"primary-button",type:"button",onClick:i,children:[t.jsx(qt,{size:16}),"Refresh"]})]})]})}const Ft="codexUsageDetailPanel";function Ps(e=!1){var n;try{const s=(n=window.sessionStorage)==null?void 0:n.getItem(Ft);return s?s==="expanded":e}catch{return e}}function _s(e){var n;try{(n=window.sessionStorage)==null||n.setItem(Ft,e?"expanded":"collapsed")}catch{}}const B=250;function Ls(e,n){const s=T("density"),[a,i]=c.useState(()=>T("call_q")),[o,r]=c.useState(()=>T("model")||"all"),[h,F]=c.useState(()=>T("effort")||"all"),[d,b]=c.useState(()=>gt()),[j,C]=c.useState(()=>jt()),[M,N]=c.useState(()=>vt()),[y,m]=c.useState(()=>le("from")),[l,p]=c.useState(()=>le("to")),[w,g]=c.useState(()=>Pe()),[_,E]=c.useState(()=>Ct(Pe())),x=wn("codexUsageCallsEvidenceGrid",{density:Vn()==="dense"?"compact":"comfortable",columnVisibility:{}},s==="roomy"?"comfortable":s==="dense"?"compact":void 0),L=x.density==="compact"?"dense":"roomy",v=c.useMemo(()=>Bn(),[]),[u,te]=c.useState(v),[G,R]=c.useState(()=>Hn(B)),[ne,Q]=c.useState(""),[de,ue]=c.useState(""),[W,he]=c.useState(()=>Ps(!!v)),me=c.useRef(null),se=c.useRef(n),xe=c.useMemo(()=>Ze(e.calls.map(f=>f.model)),[e.calls]),pe=c.useMemo(()=>Ze(e.calls.map(f=>f.effort)),[e.calls]),fe=c.useMemo(()=>_n(e.calls),[e.calls]),Y=c.useMemo(()=>ze(M,y,l,new Date),[l,y,M]);c.useEffect(()=>{se.current!==n&&(se.current=n,R(B))},[n]);function I(){R(B)}function ge(f){i(f),I()}function je(f){r(f),I()}function ve(f){F(f),I()}function be(f){b(f),I()}function Ce(f){C(f),I()}function ye(f){N(f),I()}function Se(f){m(Le(f)),N("custom"),I()}function we(f){p(Le(f)),N("custom"),I()}function Fe(f){const A=bt(f);g(A),E(U(A)),I()}function Ne(f){E(f==="asc"?"asc":"desc"),I()}function Te(){i(""),r("all"),F("all"),b("all"),C("all"),N("all"),m(""),p(""),g("time"),E(U("time")),x.setDensity("compact"),x.setColumnVisibility({}),te(null),R(B),window.history.replaceState(null,"",_e({localQuery:"",modelFilter:"all",effortFilter:"all",confidenceFilter:"all",sourceFilter:"all",timeFilter:"all",dateStart:"",dateEnd:"",sortKey:"time",sortDirection:U("time"),density:"dense",selectedRecordId:"",visibleRowCount:B,pageSize:B})),ue("Calls filters cleared")}function Ee(){he(f=>{const A=!f;return _s(A),A})}return{localQuery:a,modelFilter:o,effortFilter:h,confidenceFilter:d,sourceFilter:j,timeFilter:M,dateStart:y,dateEnd:l,sortKey:w,setSortKey:g,sortDirection:_,setSortDirection:E,gridPreferences:x,density:L,initialSelectedCallId:v,selectedCallId:u,setSelectedCallId:te,visibleCallRows:G,setVisibleCallRows:R,exportStatus:ne,setExportStatus:Q,filterStatus:de,detailsExpanded:W,searchInputRef:me,modelOptions:xe,effortOptions:pe,sourceCoverage:fe,dateRangeStatus:Y,resetCallTablePage:I,updateLocalQuery:ge,updateModelFilter:je,updateEffortFilter:ve,updateConfidenceFilter:be,updateSourceFilter:Ce,updateTimeFilter:ye,updateDateStart:Se,updateDateEnd:we,updateSortKey:Fe,updateSortDirection:Ne,clearCallFilters:Te,toggleCallDetails:Ee}}function ks({model:e,header:n,filters:s,table:a,inspector:i}){var F;const o=ce(),r=o.t("dashboard.model_calls","Model Calls"),h=o.t("dashboard.model_calls","Model calls");return t.jsxs("div",{className:`${$.page} page-grid`,children:[t.jsx(Ms,{...n}),t.jsx(Ds,{...s}),t.jsxs("div",{className:$.tableHeading,children:[t.jsxs("div",{children:[t.jsx("h2",{children:r}),t.jsx("p",{children:a.exportStatus||a.filterStatus||a.subtitle})]}),t.jsxs("div",{className:$.tableActions,children:[t.jsx(re,{label:Is(a.isFetching,a.focused,a.focusedReason),tone:a.focused?"green":"blue"}),t.jsx(re,{label:a.activePreset?`Preset: ${mt(a.activePreset)}`:"Raw context gated",tone:a.activePreset?"green":"blue"}),t.jsxs("button",{className:"toolbar-button",type:"button","aria-expanded":a.detailsExpanded,onClick:a.onToggleDetails,children:[a.detailsExpanded?t.jsx(Nn,{size:16}):t.jsx(Tn,{size:16}),a.detailsExpanded?o.t("button.hide_details","Hide details"):o.t("dashboard.call_details","Call Details")]})]})]}),t.jsxs("div",{className:a.detailsExpanded?"table-detail-layout":"table-detail-layout detail-collapsed",children:[t.jsxs("div",{className:$.gridColumn,children:[t.jsx(Zt,{ariaLabel:h,columns:a.columns,data:a.calls,identityColumnId:"thread",lockedColumnIds:["investigate"],getRowId:d=>d.id,mobile:{primary:d=>d.thread,secondary:d=>o.translateText(`${d.time} · ${d.model} · ${ee(d.totalTokens)} tokens · ${q(d.cachedPct)} cache`),actionLabel:d=>en(d)},sorting:a.sorting,onSortingChange:a.onSortingChange,manualSorting:!0,columnVisibility:a.gridPreferences.columnVisibility,onColumnVisibilityChange:a.gridPreferences.setColumnVisibility,density:a.gridPreferences.density,onDensityChange:a.gridPreferences.setDensity,onRestoreDefaults:a.gridPreferences.restoreDefaults,selectedRowId:(F=a.selectedCall)==null?void 0:F.id,onRowSelect:d=>a.onSelectCall(d.id),onRowActivate:d=>i.onOpenInvestigator(d.id),activateOnClick:!0,selectOnHover:!0,viewportHeight:560,emptyLabel:"No rows match current filters."}),t.jsxs("div",{className:$.gridFooter,"aria-live":"polite",children:[t.jsx("span",{children:o.translateText(`${a.calls.length.toLocaleString()} loaded / ${a.totalMatchedCalls.toLocaleString()} matched`)}),a.focused&&a.hasNextPage?t.jsx("button",{className:"toolbar-button",type:"button",onClick:a.onLoadMore,disabled:a.isFetchingNextPage,children:a.isFetchingNextPage?"Loading more...":`Load ${B.toLocaleString()} more`}):null]})]}),a.detailsExpanded?t.jsx(ds,{call:a.selectedCall,calls:e.calls,...i}):null]}),t.jsxs("details",{className:$.patternsDisclosure,children:[t.jsxs("summary",{children:[t.jsx(ut,{size:17}),"Usage patterns"]}),t.jsxs("div",{className:"dashboard-grid three",children:[t.jsx(Z,{title:"Usage Over Time",subtitle:"Tokens",children:t.jsx(et,{series:e.tokenSeries,yLabel:"Tokens",height:220})}),t.jsx(Z,{title:"Cost by Model",subtitle:"Estimated USD",children:t.jsx(is,{data:e.modelCosts,valueLabel:Oe})}),t.jsx(Z,{title:"Cache Hit Rate Over Time",subtitle:o.translateText("Daily"),children:t.jsx(et,{series:e.cacheSeries,yLabel:"Cache %",height:220,valueFormatter:d=>`${d}%`})})]})]})]})}function Is(e,n,s){return e&&n?"Updating focused rows":e?"Loading focused rows":n?"Focused paged API":s||"Stored snapshot"}function ra(e,n="",s=""){const a=Pe();return ke(yt(e,{globalQuery:n,localQuery:T("call_q"),modelFilter:T("model")||"all",effortFilter:T("effort")||"all",confidenceFilter:gt(),sourceFilter:jt(),timeFilter:vt(),dateStart:le("from"),dateEnd:le("to"),activePreset:s}),a,Ct(a))}const Nt={time:"time",duration:"duration",gap:"previousCallGap",attention:"signal",thread:"thread",initiator:"initiator",model:"model",effort:"effort",total:"totalTokens",cached:"cachedInput",uncached:"uncachedInput",output:"output",reasoning:"reasoningOutput",cost:"cost",usage:"credits",cache:"cachedPct",context:"contextWindowPct"},$s=Object.fromEntries(Object.entries(Nt).map(([e,n])=>[n,e]));function la({model:e,globalQuery:n,activePreset:s,onRefresh:a,contextRuntime:i,onContextApiEnabledChange:o,onOpenInvestigator:r,onCopyCallLink:h,includeArchived:F=!1,sourceKey:d,sourceRevision:b="",scopeSince:j=null,focusedEndpointsEnabled:C=!0,workspaceSwitcher:M}){var We,Ye;const N=Ls(e,n),{localQuery:y,modelFilter:m,effortFilter:l,confidenceFilter:p,sourceFilter:w,timeFilter:g,dateStart:_,dateEnd:E,sortKey:x,setSortKey:L,sortDirection:v,setSortDirection:u,gridPreferences:te,density:G,selectedCallId:R,setSelectedCallId:ne,visibleCallRows:Q,setVisibleCallRows:de,exportStatus:ue,setExportStatus:W,filterStatus:he,detailsExpanded:me,searchInputRef:se,modelOptions:xe,effortOptions:pe,sourceCoverage:fe,dateRangeStatus:Y,resetCallTablePage:I,updateLocalQuery:ge,updateModelFilter:je,updateEffortFilter:ve,updateConfidenceFilter:be,updateSourceFilter:Ce,updateTimeFilter:ye,updateDateStart:Se,updateDateEnd:we,updateSortKey:Fe,updateSortDirection:Ne,clearCallFilters:Te,toggleCallDetails:Ee}=N,f=c.useMemo(()=>[...Gt,tn({onOpenInvestigator:r,onCopyCallLink:h})],[h,r]),A=c.useMemo(()=>ts({runtime:i,enabled:C,activePreset:s,sourceFilter:w,sortKey:x,scopeSince:j,timeFilter:g,dateStart:_,dateEnd:E,confidenceFilter:p,globalQuery:n,localQuery:y,modelFilter:m,effortFilter:l}),[s,p,i,E,_,l,C,n,y,m,x,w,j,g]),V=Lt({...It({runtime:i,includeArchived:F,sourceKey:d,sourceRevision:b,filters:A.filters,sort:A.sort,direction:v,pageSize:B}),enabled:A.enabled,placeholderData:D=>D}),Ue=c.useMemo(()=>yt(e.calls,{globalQuery:n,localQuery:y,modelFilter:m,effortFilter:l,confidenceFilter:p,sourceFilter:w,timeFilter:g,dateStart:_,dateEnd:E,activePreset:s}),[s,p,E,_,l,n,y,e.calls,m,w,g]),qe=c.useMemo(()=>ke(Ue,x,v),[Ue,v,x]),Ge=c.useMemo(()=>{var D;return((D=V.data)==null?void 0:D.pages.flatMap(X=>X.rows))??[]},[V.data]),ae=A.enabled&&!!V.data,H=c.useMemo(()=>ae?ke(Ge,x,v):qe,[Ge,qe,v,x,ae]),De=ae?((Ye=(We=V.data)==null?void 0:We.pages[0])==null?void 0:Ye.totalMatchedRows)??H.length:e.calls.length,Tt=c.useMemo(()=>En({shownCount:H.length,totalCount:De,localQuery:y,globalQuery:n,modelFilter:m,effortFilter:l,confidenceFilter:p,sourceFilter:w,timeFilter:g,dateRangeStatus:Y,activePresetLabel:s?mt(s):""}),[s,p,Y,l,n,y,m,H.length,w,g,De]),Qe=c.useMemo(()=>[{id:Nt[x],desc:v==="desc"}],[v,x]),ie=R===oe?H[0]??null:H.find(D=>D.id===R)??H[0]??null,z=ie&&(ie.id===R||R===oe)?ie.id:"";c.useEffect(()=>{R===oe&&z&&ne(z)},[R,z]),c.useEffect(()=>{const D=_e({localQuery:y,modelFilter:m,effortFilter:l,confidenceFilter:p,sourceFilter:w,timeFilter:g,dateStart:_,dateEnd:E,sortKey:x,sortDirection:v,density:G,selectedRecordId:z,visibleRowCount:Q,pageSize:B});D.toString()!==window.location.href&&window.history.replaceState(null,"",D)},[p,E,_,G,l,y,m,z,v,x,w,g,Q]);function Et(){Qt(`codex-calls-${Xt()}.csv`,Wt(H,Yt)),W(`Exported ${H.length} calls`)}async function Dt(){try{const D=_e({localQuery:y,modelFilter:m,effortFilter:l,confidenceFilter:p,sourceFilter:w,timeFilter:g,dateStart:_,dateEnd:E,sortKey:x,sortDirection:v,density:G,selectedRecordId:z,visibleRowCount:Q,pageSize:B});if(!await ht(D.toString()))throw new Error("Clipboard unavailable");W("Copied Calls view link")}catch{W("Copy unavailable in browser")}}function Mt(D){var Xe,Je;const X=typeof D=="function"?D(Qe):D,Me=$s[((Xe=X[0])==null?void 0:Xe.id)??""]??"time",_t=Me!==x;L(Me),u(_t?U(Me):(Je=X[0])!=null&&Je.desc?"desc":"asc"),I()}async function Pt(){!V.hasNextPage||V.isFetchingNextPage||(await V.fetchNextPage(),de(D=>D+B))}return t.jsx(ks,{model:e,header:{workspaceSwitcher:M,canExport:!!H.length,onExport:Et,onCopyView:Dt,onRefresh:a},filters:{searchInputRef:se,localQuery:y,modelFilter:m,effortFilter:l,confidenceFilter:p,sourceFilter:w,timeFilter:g,dateStart:_,dateEnd:E,sortKey:x,sortDirection:v,modelOptions:xe,effortOptions:pe,sourceCoverage:fe,dateRangeStatus:Y,onLocalQueryChange:ge,onModelFilterChange:je,onEffortFilterChange:ve,onConfidenceFilterChange:be,onSourceFilterChange:Ce,onTimeFilterChange:ye,onDateStartChange:Se,onDateEndChange:we,onSortKeyChange:Fe,onSortDirectionChange:Ne,onClear:Te},table:{activePreset:s,exportStatus:ue,filterStatus:he,subtitle:Tt,focused:ae,focusedReason:A.reason,isFetching:V.isFetching,isFetchingNextPage:V.isFetchingNextPage,hasNextPage:!!V.hasNextPage,detailsExpanded:me,calls:H,totalMatchedCalls:De,columns:f,sorting:Qe,gridPreferences:te,selectedCall:ie,onSortingChange:Mt,onSelectCall:ne,onLoadMore:Pt,onToggleDetails:Ee},inspector:{contextRuntime:i,includeArchived:F,sourceRevision:b,hydrateThreadCalls:C,onContextApiEnabledChange:o,onOpenInvestigator:r,onCopyCallLink:h}})}export{la as CallsPage,ra as callsForCurrentUrl}; diff --git a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/ReportsPage.js b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/ReportsPage.js index a64c284a..a1128c12 100644 --- a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/ReportsPage.js +++ b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/ReportsPage.js @@ -1 +1 @@ -import{r as _,j as a}from"./dashboard-react.js";import{u as q}from"./useQuery.js";import{v as B,w as H,x as J,y as G,z as W,f as X,m as Y,p as Z,u as ee,i as te,h as re,D as ae,R as se,g as ie}from"./App.js";import{q as oe}from"./queryOptions.js";import{P as U}from"./Panel.js";import{S as P}from"./StatusBadge.js";import{P as ne}from"./PageLoadProgress.js";import"./Primitives.js";import{v as le,V as ce}from"./UsageConstellation.js";import{D as de}from"./DataTable.js";import{c as ue,b as he}from"./tableActions.js";import"./useBaseQuery.js";import"./locale-zh-Hans.js";import"./dashboardRouter.js";import"./router.js";import"./triangle-alert.js";import"./index2.js";import"./rowActionEvents.js";import"./search.js";const pe="codex-usage-tracker-reports-pack-v1";async function ge(e,r={}){if(window.location.protocol==="file:")throw new Error("Live report pack requires the localhost dashboard server.");if(!e.apiToken)throw new Error("Live report pack requires localhost dashboard API token.");const s=new URLSearchParams({limit:String(r.limit??500),evidence_limit:String(r.evidenceLimit??8),_:String(Date.now())});r.includeArchived&&s.set("include_archived","1");const o=await fetch(`/api/reports/pack?${s.toString()}`,{headers:{Accept:"application/json","X-Codex-Usage-Token":e.apiToken},cache:"no-store",signal:r.signal}),t=await fe(o,"Reports pack");if(t.schema!==pe)throw new Error("Reports pack returned an unsupported schema.");return{reports:t.reports??[],evidence:me(t.evidence??{}),rowCount:Number(t.row_count??0),totalMatchedRows:Number(t.total_matched_rows??t.row_count??0),schema:t.schema,generatedAt:t.generated_at??"",rawContextIncluded:t.raw_context_included===!0,rawPayload:t}}function me(e){return Object.fromEntries(Object.entries(e).map(([r,s])=>[r,(s.rows??[]).map((o,t)=>B(o,t))]))}async function fe(e,r){const s=await e.json().catch(()=>({}));if(!e.ok)throw new Error(typeof s.error=="string"?s.error:`${r} failed with HTTP ${e.status}`);return s}const M=W("reports");function be(e){return oe({queryKey:J(M,G({sourceKey:e.sourceKey??(e.runtime.fileMode?"static-file":"local-api"),sourceRevision:e.sourceRevision}),{historyScope:e.includeArchived?"all":"active",loadWindow:e.loadWindow,limit:e.limit},e.evidenceLimit),queryFn:({signal:r})=>ge(e.runtime,{limit:e.limit,evidenceLimit:e.evidenceLimit,includeArchived:e.includeArchived,signal:r}),...H(M.dataClass)})}function f(e){if(e!=null&&e.key)return e.key;const r=L(e);return r.includes("fast")?"fast-mode-proxy":r.includes("cost")||r.includes("thread")?"cost-curves":r.includes("remaining")?"usage-remaining":r.includes("allowance")?"allowance-change":r.includes("weekly")?"weekly-credits":r.includes("drain")?"usage-drain-model":r.replace(/[^a-z0-9]+/g,"-").replace(/(^-|-$)/g,"")||"report"}function A(e){var s;const r=(s=new URLSearchParams(window.location.search).get("report"))==null?void 0:s.trim();return r?e.find(o=>f(o)===r||o.title===r):void 0}function we(e){const r=new URL(window.location.href);r.searchParams.set("view","reports"),r.searchParams.set("report",f(e)),window.history.replaceState(null,"",r)}function z(e,r){const s=L(e),o=[...r];return s.includes("fast")?o.filter(t=>t.fast||t.effort.toLowerCase()==="low").sort((t,i)=>t.durationSeconds-i.durationSeconds||y(i)-y(t)).slice(0,8):s.includes("cost")||s.includes("thread")?o.sort((t,i)=>i.cost-t.cost||i.totalTokens-t.totalTokens).slice(0,8):o.sort((t,i)=>y(i)-y(t)||i.totalTokens-t.totalTokens).slice(0,8)}function D(e,r){const s=L(e);return s.includes("fast")?{eyebrow:"Speed proxy",finding:`${r.length} loaded calls are fast-tagged or low effort. Duration is a proxy, not a quality score.`,method:"Filter fast-tagged and low-effort calls, then order by shortest duration and credit impact.",caveat:"Aggregate timing cannot distinguish model latency from tool, network, or queue time.",selection:"Fast/low-effort candidates · shortest duration first · top 8",chartLabel:"Calls"}:s.includes("cost")||s.includes("thread")?{eyebrow:"Cost concentration",finding:`${r.length} highest-cost loaded calls anchor this report. Estimates use the local pricing model.`,method:"Rank aggregate calls by estimated cost, breaking ties with total token volume.",caveat:"Estimated local cost may differ from billing and excludes costs not represented in loaded rows.",selection:"Highest estimated cost · token volume tie-break · top 8",chartLabel:"USD"}:{eyebrow:"Usage trajectory",finding:`${r.length} highest credit-impact loaded calls support the selected usage report.`,method:"Rank aggregate calls by estimated Codex credit impact and compare the selected time series.",caveat:"Allowance and credit projections are local observations, not universal account limits.",selection:"Highest credit impact · token volume tie-break · top 8",chartLabel:s.includes("remaining")||s.includes("allowance")?"Percent":"Credits"}}function ve(e,r){const s=L(e);return s.includes("remaining")||s.includes("allowance")?r.usageRemainingSeries:s.includes("drain")?r.actualVsPredictedSeries:r.weeklyCreditSeries}function xe(e,r,s){const o=L(e);if(o.includes("cost")||o.includes("thread"))return{bars:r.threads.slice(0,10).map(i=>({label:i.name,value:i.cost})),labelsAreUiText:!1};if(!o.includes("fast"))return null;const t=s.map(i=>i.durationSeconds).filter(i=>Number.isFinite(i)&&i>=0);return{bars:[{label:"Under 5s",value:t.filter(i=>i<5).length},{label:"5-15s",value:t.filter(i=>i>=5&&i<15).length},{label:"15-30s",value:t.filter(i=>i>=15&&i<30).length},{label:"30s+",value:t.filter(i=>i>=30).length}],labelsAreUiText:!0}}function y(e){return e.credits>0?e.credits:e.cost*25}function L(e){return((e==null?void 0:e.title)??"").toLowerCase()}function ye({calls:e,onOpenInvestigator:r,onCopyCallLink:s}){const o=_.useMemo(()=>[{accessorKey:"time",header:"Time"},{accessorKey:"thread",header:"Thread"},{accessorKey:"model",header:"Model"},{accessorKey:"effort",header:"Effort",cell:t=>a.jsx("span",{className:`pill effort-${String(t.getValue())}`,children:String(t.getValue())})},{id:"credits",header:"Credits",cell:t=>a.jsx("span",{className:"num",children:X(y(t.row.original))}),sortingFn:(t,i)=>y(t.original)-y(i.original)},{accessorKey:"cost",header:"Est. Cost",cell:t=>a.jsx("span",{className:"num",children:Y(Number(t.getValue()))})},{accessorKey:"cachedPct",header:"Cached %",cell:t=>a.jsx("span",{className:"cache-pill",children:Z(Number(t.getValue()))})},ue({onOpenInvestigator:r,onCopyCallLink:s,labelPrefix:"report side evidence call"})],[s,r]);return a.jsx(de,{columns:o,data:e,compact:!0,emptyLabel:"No loaded aggregate calls match this selected report.",getRowId:t=>t.id,getRowActionLabel:t=>he(t,"report side evidence call"),onRowActivate:t=>r(t.id),ariaLabel:"Selected report evidence calls"})}function je(e,r,s,o){var w;const t=D(e,s),i=Se(t.chartLabel),c=xe(e,r,s),u=c?ke(c.bars,i,t.chartLabel,c.labelsAreUiText):Re(ve(e,r),i,t.chartLabel),b=(e==null?void 0:e.title)??"Selected report",v=`${t.eyebrow} evidence`;return{schema:le,id:`report-${f(e)}`,title:v,description:t.selection,state:u.rows.length?{kind:"ready"}:{kind:"empty",message:`No loaded aggregate data is available for ${b}.`},scope:{label:`${o.source} · ${t.selection}`,rowCount:u.rows.length,historyScope:o.historyScope},freshness:{generatedAt:o.generatedAt,sourceRevision:o.sourceRevision},caveats:[t.caveat,"Raw context is available only through an explicitly linked Call Investigator record."],accessibility:{summary:u.rows.length?`${b} contains ${u.rows.length} plotted rows. ${t.finding}`:`No plotted rows are available for ${b}.`,details:[t.method,t.caveat],keyboardInstructions:"Use left and right arrow keys to inspect chart values, or switch to the table for exact values."},table:{caption:`${b} report data`,columns:u.columns,defaultSort:{field:"label",direction:"asc"}},interactions:{selection:{keyField:"id",labelField:"label"},...c?{}:{zoom:{axis:"x",startPercent:u.rows.length>12?Math.round(100-12/u.rows.length*100):0,endPercent:100},brush:{axis:"x"}}},kind:"cartesian",data:{rows:u.rows},axes:{x:{field:"label",label:c?"Category":"Observed period",type:"category"},y:{field:((w=u.series[0])==null?void 0:w.yField)??"value",label:t.chartLabel,type:"number",unit:i}},series:u.series}}function ke(e,r,s,o){return{rows:e.map((i,c)=>({id:`bar-${c}`,label:i.label,value:i.value})),columns:[{field:"label",label:"Category",type:"category",align:"left",localizeValues:o},{field:"value",label:s,type:"number",unit:r,align:"right"}],series:[{id:"report-values",label:s,mark:"bar",xField:"label",yField:"value",color:"#2f6fed"}]}}function Re(e,r,s){const o=new Map,t=[],i=[{field:"label",label:"Observed period",type:"category",align:"left"}];return e.forEach((c,u)=>{const b=`value_${u}`,v=`low_${u}`,w=`high_${u}`,S=c.points.some(m=>Number.isFinite(m.low)&&Number.isFinite(m.high));i.push({field:b,label:c.label||s,type:"number",unit:r,align:"right"}),S&&i.push({field:v,label:`${c.label} low`,type:"number",unit:r,align:"right"},{field:w,label:`${c.label} high`,type:"number",unit:r,align:"right"}),c.points.forEach((m,j)=>{const h=m.label||`Point ${j+1}`,k=o.get(h)??{id:`point-${o.size}`,label:h};k[b]=m.value,S&&Number.isFinite(m.low)&&Number.isFinite(m.high)&&(k[v]=Number(m.low),k[w]=Number(m.high)),o.set(h,k)}),t.push({id:c.id||`series-${u}`,label:c.label||s,mark:"line",xField:"label",yField:b,color:c.color,smooth:!1,...S?{lowerField:v,upperField:w}:{}})}),{rows:[...o.values()],columns:i,series:t}}function Se(e){return e==="USD"?"usd":e==="Calls"?"count":e==="Percent"?"percent":"credits"}const _e="_page_p90q5_1",Le="_header_p90q5_7",Ce="_actions_p90q5_8",Ne="_status_p90q5_9",$e="_selectedMeta_p90q5_10",Fe="_switcher_p90q5_11",Ue="_selected_p90q5_10",Pe="_research_p90q5_24",Ee="_action_p90q5_8",Te="_eyebrow_p90q5_109",Me="_workspace_p90q5_166",Ae="_main_p90q5_173",ze="_metadata_p90q5_195",p={page:_e,header:Le,actions:Ce,status:Ne,selectedMeta:$e,switcher:Fe,selected:Ue,research:Pe,action:Ee,eyebrow:Te,workspace:Me,main:Ae,metadata:ze};function ot(e){return z(A(e.reports)??e.reports[0],e.calls)}function nt({model:e,refreshState:r,includeArchived:s,loadWindow:o,loadLimit:t,sourceKey:i,sourceRevision:c,onOpenInvestigator:u,onCopyCallLink:b}){const v=ee(),[w,S]=_.useState(()=>f(A(e.reports)??e.reports[0])),[m,j]=_.useState(""),h=!!e.contextRuntime.apiToken&&!e.contextRuntime.fileMode,k=o==="rows"?t:0,g=q({...be({runtime:e.contextRuntime,includeArchived:s,loadWindow:o,limit:k,evidenceLimit:8,sourceKey:i,sourceRevision:c}),enabled:h,placeholderData:d=>d}),l=h?g.data:void 0,N=l!=null&&l.reports.length?l.reports:e.reports,n=N.find(d=>f(d)===w)??N[0],x=_.useMemo(()=>{const d=n&&l?l.evidence[f(n)]:void 0;return d===void 0?z(n,e.calls):d},[n,e.calls,l]),R=D(n,x),C=l?"Live localhost report pack":"Loaded dashboard aggregates",$=(l==null?void 0:l.generatedAt)||(l?"Server timestamp unavailable":"Current dashboard snapshot"),K=De(h,g,m,r),V=_.useMemo(()=>je(n,e,x,{generatedAt:$,historyScope:s?"all":"active",source:C,sourceRevision:c}),[n,x,$,s,e,C,c]),F={label:"Report pack",status:te({enabled:h,hasData:!!g.data,isError:g.isError,isFetching:g.isFetching,isPending:g.isPending})},T=re([F.status]);async function O(){if(!h)return;j("Refreshing selected report…");const d=await g.refetch();j(d.isError?`Refresh failed: ${E(d.error)}. Showing cached live report pack.`:"Selected report refreshed")}function I(){Ke(`codex-report-${f(n)}-${ie()}.json`,{schema:"codex-usage-tracker-react-selected-report-v1",generated_at:(l==null?void 0:l.generatedAt)??null,source:C,report:n,method:R.method,caveat:R.caveat,evidence:x,raw_context_included:(l==null?void 0:l.rawContextIncluded)??!1}),j(`Exported ${(n==null?void 0:n.title)??"selected report"}`)}function Q(d){S(f(d)),j(`${d.title} selected`),we(d)}return a.jsxs("div",{className:p.page,children:[a.jsxs("header",{className:p.header,children:[a.jsxs("div",{children:[a.jsx("h1",{children:"Reports"}),a.jsx("p",{children:"Focused local research snapshots with their methods, limits, and investigator-linked evidence."})]}),a.jsxs("div",{className:p.actions,children:[a.jsxs("button",{className:p.action,type:"button",onClick:I,disabled:!n,children:[a.jsx(ae,{size:16,"aria-hidden":"true"}),"Export selected"]}),a.jsxs("button",{className:p.action,"data-primary":"true",type:"button",onClick:O,disabled:!h||g.isFetching,title:h?void 0:"Live report refresh requires the localhost server.",children:[a.jsx(se,{size:16,"aria-hidden":"true"}),h?g.isFetching?"Refreshing":"Refresh report":"Live refresh unavailable"]})]})]}),a.jsx(ne,{active:h&&g.isFetching,completed:T.ready,total:T.total,label:"Loading full-scope report pack",error:h&&g.error&&!g.data?`Report pack unavailable: ${E(g.error)}`:null,modules:[F],updating:F.status==="updating"}),a.jsxs("div",{className:p.status,role:"status","aria-live":"polite",children:[a.jsx(P,{label:l?"Live report pack":"Aggregate fallback",tone:l?"green":"orange"}),a.jsx("span",{children:K})]}),a.jsxs("section",{className:p.selected,"aria-labelledby":"selected-report-title",children:[a.jsxs("div",{children:[a.jsx("span",{className:p.eyebrow,children:R.eyebrow}),a.jsx("h2",{id:"selected-report-title",children:(n==null?void 0:n.title)??"No report available"}),a.jsx("p",{children:n==null?void 0:n.description}),a.jsx("p",{children:R.finding})]}),a.jsxs("div",{className:p.selectedMeta,children:[a.jsx(P,{label:(n==null?void 0:n.status)??"Blocked",tone:(n==null?void 0:n.status)==="Ready"?"green":"orange"}),a.jsxs("span",{children:[(n==null?void 0:n.owner)??"No owner"," · ",x.length," evidence rows"]})]})]}),a.jsx("div",{className:p.switcher,role:"group","aria-label":"Select report",children:N.map(d=>a.jsx("button",{type:"button","aria-pressed":f(d)===f(n),onClick:()=>Q(d),children:d.title},f(d)))}),a.jsxs("div",{className:p.workspace,children:[a.jsxs("section",{className:p.main,"aria-label":"Report evidence",children:[a.jsx(ce,{spec:V,height:320}),a.jsx(U,{title:"Report Evidence Calls",subtitle:v.translateText(`${x.length} aggregate rows · select a row to open Call Investigator`),action:a.jsx(P,{label:"Investigator linked",tone:"green"}),children:a.jsx(ye,{calls:x,onOpenInvestigator:u,onCopyCallLink:b})})]}),a.jsxs("aside",{className:p.research,children:[a.jsxs(U,{title:"Research Notes",subtitle:"Interpretation boundary",children:[a.jsxs("section",{children:[a.jsx("h3",{children:"Method"}),a.jsx("p",{children:R.method})]}),a.jsxs("section",{children:[a.jsx("h3",{children:"Caveat"}),a.jsx("p",{children:R.caveat})]}),a.jsxs("section",{children:[a.jsx("h3",{children:"Evidence privacy"}),a.jsx("p",{children:"Raw context is not included here; inspect an explicitly linked call in Call Investigator."})]})]}),a.jsx(U,{title:"Generation Metadata",subtitle:"Provenance for this view",children:a.jsxs("dl",{className:p.metadata,children:[a.jsxs("div",{children:[a.jsx("dt",{children:"Source"}),a.jsx("dd",{children:C})]}),a.jsxs("div",{children:[a.jsx("dt",{children:"Generated"}),a.jsx("dd",{children:$})]}),a.jsxs("div",{children:[a.jsx("dt",{children:"Schema"}),a.jsx("dd",{children:(l==null?void 0:l.schema)||"dashboard aggregate model"})]}),a.jsxs("div",{children:[a.jsx("dt",{children:"Matched"}),a.jsxs("dd",{children:[l?l.totalMatchedRows.toLocaleString():e.calls.length.toLocaleString()," loaded rows"]})]})]})})]})]})]})}function De(e,r,s,o){return s||(e?r.isLoading?"Loading live report pack…":r.error?`Live report pack unavailable: ${E(r.error)}. Using loaded aggregate fallback.`:r.isFetching?"Refreshing live report pack…":r.data&&r.isStale?"Cached live report pack is stale; refresh is available.":"Live report pack ready.":o||"Static mode: using the loaded aggregate snapshot.")}function Ke(e,r){const s=JSON.stringify(r,null,2),o=new Blob([s],{type:"application/json;charset=utf-8"}),t=typeof URL.createObjectURL=="function"?URL.createObjectURL(o):`data:application/json;charset=utf-8,${encodeURIComponent(s)}`,i=document.createElement("a");i.href=t,i.download=e,i.rel="noopener",document.body.append(i),i.click(),i.remove(),t.startsWith("blob:")&&typeof URL.revokeObjectURL=="function"&&URL.revokeObjectURL(t)}function E(e){return e instanceof Error?e.message:String(e)}export{nt as ReportsPage,ot as reportCallsForCurrentUrl}; +import{r as _,j as a}from"./dashboard-react.js";import{u as q}from"./useQuery.js";import{v as B,w as H,x as J,y as G,z as W,f as X,m as Y,p as Z,u as ee,i as te,h as re,D as ae,R as se,g as ie}from"./App.js";import{q as oe}from"./queryOptions.js";import{P}from"./Panel.js";import{S as U}from"./StatusBadge.js";import{P as ne}from"./PageLoadProgress.js";import"./Primitives.js";import{v as le,V as ce}from"./UsageConstellation.js";import{D as de}from"./DataTable.js";import{c as ue,b as he}from"./tableActions.js";import"./useBaseQuery.js";import"./locale-zh-Hans.js";import"./dashboardRouter.js";import"./router.js";import"./triangle-alert.js";import"./index2.js";import"./rowActionEvents.js";import"./search.js";const pe="codex-usage-tracker-reports-pack-v1";async function ge(e,r={}){if(window.location.protocol==="file:")throw new Error("Live report pack requires the localhost dashboard server.");if(!e.apiToken)throw new Error("Live report pack requires localhost dashboard API token.");const s=new URLSearchParams({limit:String(r.limit??500),evidence_limit:String(r.evidenceLimit??8),_:String(Date.now())});r.includeArchived&&s.set("include_archived","1");const o=await fetch(`/api/reports/pack?${s.toString()}`,{headers:{Accept:"application/json","X-Codex-Usage-Token":e.apiToken},cache:"no-store",signal:r.signal}),t=await fe(o,"Reports pack");if(t.schema!==pe)throw new Error("Reports pack returned an unsupported schema.");return{reports:t.reports??[],evidence:me(t.evidence??{}),rowCount:Number(t.row_count??0),totalMatchedRows:Number(t.total_matched_rows??t.row_count??0),schema:t.schema,generatedAt:t.generated_at??"",rawContextIncluded:t.raw_context_included===!0,rawPayload:t}}function me(e){return Object.fromEntries(Object.entries(e).map(([r,s])=>[r,(s.rows??[]).map((o,t)=>B(o,t))]))}async function fe(e,r){const s=await e.json().catch(()=>({}));if(!e.ok)throw new Error(typeof s.error=="string"?s.error:`${r} failed with HTTP ${e.status}`);return s}const M=W("reports");function be(e){return oe({queryKey:J(M,G({sourceKey:e.sourceKey??(e.runtime.fileMode?"static-file":"local-api"),sourceRevision:e.sourceRevision}),{historyScope:e.includeArchived?"all":"active",loadWindow:e.loadWindow,limit:e.limit},e.evidenceLimit),queryFn:({signal:r})=>ge(e.runtime,{limit:e.limit,evidenceLimit:e.evidenceLimit,includeArchived:e.includeArchived,signal:r}),...H(M.dataClass)})}function f(e){if(e!=null&&e.key)return e.key;const r=L(e);return r.includes("fast")?"fast-mode-proxy":r.includes("cost")||r.includes("thread")?"cost-curves":r.includes("remaining")?"usage-remaining":r.includes("allowance")?"allowance-change":r.includes("weekly")?"weekly-credits":r.includes("drain")?"usage-drain-model":r.replace(/[^a-z0-9]+/g,"-").replace(/(^-|-$)/g,"")||"report"}function A(e){var s;const r=(s=new URLSearchParams(window.location.search).get("report"))==null?void 0:s.trim();return r?e.find(o=>f(o)===r||o.title===r):void 0}function we(e){const r=new URL(window.location.href);r.searchParams.set("view","reports"),r.searchParams.set("report",f(e)),window.history.replaceState(null,"",r)}function z(e,r){const s=L(e),o=[...r];return s.includes("fast")?o.filter(t=>t.fastProxyCandidate||t.effort.toLowerCase()==="low").sort((t,i)=>t.durationSeconds-i.durationSeconds||y(i)-y(t)).slice(0,8):s.includes("cost")||s.includes("thread")?o.sort((t,i)=>i.cost-t.cost||i.totalTokens-t.totalTokens).slice(0,8):o.sort((t,i)=>y(i)-y(t)||i.totalTokens-t.totalTokens).slice(0,8)}function D(e,r){const s=L(e);return s.includes("fast")?{eyebrow:"Speed proxy",finding:`${r.length} loaded calls are fast-tagged or low effort. Duration is a proxy, not a quality score.`,method:"Filter fast-tagged and low-effort calls, then order by shortest duration and credit impact.",caveat:"Aggregate timing cannot distinguish model latency from tool, network, or queue time.",selection:"Fast/low-effort candidates · shortest duration first · top 8",chartLabel:"Calls"}:s.includes("cost")||s.includes("thread")?{eyebrow:"Cost concentration",finding:`${r.length} highest-cost loaded calls anchor this report. Estimates use the local pricing model.`,method:"Rank aggregate calls by estimated cost, breaking ties with total token volume.",caveat:"Estimated local cost may differ from billing and excludes costs not represented in loaded rows.",selection:"Highest estimated cost · token volume tie-break · top 8",chartLabel:"USD"}:{eyebrow:"Usage trajectory",finding:`${r.length} highest credit-impact loaded calls support the selected usage report.`,method:"Rank aggregate calls by estimated Codex credit impact and compare the selected time series.",caveat:"Allowance and credit projections are local observations, not universal account limits.",selection:"Highest credit impact · token volume tie-break · top 8",chartLabel:s.includes("remaining")||s.includes("allowance")?"Percent":"Credits"}}function ve(e,r){const s=L(e);return s.includes("remaining")||s.includes("allowance")?r.usageRemainingSeries:s.includes("drain")?r.actualVsPredictedSeries:r.weeklyCreditSeries}function xe(e,r,s){const o=L(e);if(o.includes("cost")||o.includes("thread"))return{bars:r.threads.slice(0,10).map(i=>({label:i.name,value:i.cost})),labelsAreUiText:!1};if(!o.includes("fast"))return null;const t=s.map(i=>i.durationSeconds).filter(i=>Number.isFinite(i)&&i>=0);return{bars:[{label:"Under 5s",value:t.filter(i=>i<5).length},{label:"5-15s",value:t.filter(i=>i>=5&&i<15).length},{label:"15-30s",value:t.filter(i=>i>=15&&i<30).length},{label:"30s+",value:t.filter(i=>i>=30).length}],labelsAreUiText:!0}}function y(e){return e.credits>0?e.credits:e.cost*25}function L(e){return((e==null?void 0:e.title)??"").toLowerCase()}function ye({calls:e,onOpenInvestigator:r,onCopyCallLink:s}){const o=_.useMemo(()=>[{accessorKey:"time",header:"Time"},{accessorKey:"thread",header:"Thread"},{accessorKey:"model",header:"Model"},{accessorKey:"effort",header:"Effort",cell:t=>a.jsx("span",{className:`pill effort-${String(t.getValue())}`,children:String(t.getValue())})},{id:"credits",header:"Credits",cell:t=>a.jsx("span",{className:"num",children:X(y(t.row.original))}),sortingFn:(t,i)=>y(t.original)-y(i.original)},{accessorKey:"cost",header:"Est. Cost",cell:t=>a.jsx("span",{className:"num",children:Y(Number(t.getValue()))})},{accessorKey:"cachedPct",header:"Cached %",cell:t=>a.jsx("span",{className:"cache-pill",children:Z(Number(t.getValue()))})},ue({onOpenInvestigator:r,onCopyCallLink:s,labelPrefix:"report side evidence call"})],[s,r]);return a.jsx(de,{columns:o,data:e,compact:!0,emptyLabel:"No loaded aggregate calls match this selected report.",getRowId:t=>t.id,getRowActionLabel:t=>he(t,"report side evidence call"),onRowActivate:t=>r(t.id),ariaLabel:"Selected report evidence calls"})}function je(e,r,s,o){var w;const t=D(e,s),i=Se(t.chartLabel),c=xe(e,r,s),u=c?ke(c.bars,i,t.chartLabel,c.labelsAreUiText):Re(ve(e,r),i,t.chartLabel),b=(e==null?void 0:e.title)??"Selected report",v=`${t.eyebrow} evidence`;return{schema:le,id:`report-${f(e)}`,title:v,description:t.selection,state:u.rows.length?{kind:"ready"}:{kind:"empty",message:`No loaded aggregate data is available for ${b}.`},scope:{label:`${o.source} · ${t.selection}`,rowCount:u.rows.length,historyScope:o.historyScope},freshness:{generatedAt:o.generatedAt,sourceRevision:o.sourceRevision},caveats:[t.caveat,"Raw context is available only through an explicitly linked Call Investigator record."],accessibility:{summary:u.rows.length?`${b} contains ${u.rows.length} plotted rows. ${t.finding}`:`No plotted rows are available for ${b}.`,details:[t.method,t.caveat],keyboardInstructions:"Use left and right arrow keys to inspect chart values, or switch to the table for exact values."},table:{caption:`${b} report data`,columns:u.columns,defaultSort:{field:"label",direction:"asc"}},interactions:{selection:{keyField:"id",labelField:"label"},...c?{}:{zoom:{axis:"x",startPercent:u.rows.length>12?Math.round(100-12/u.rows.length*100):0,endPercent:100},brush:{axis:"x"}}},kind:"cartesian",data:{rows:u.rows},axes:{x:{field:"label",label:c?"Category":"Observed period",type:"category"},y:{field:((w=u.series[0])==null?void 0:w.yField)??"value",label:t.chartLabel,type:"number",unit:i}},series:u.series}}function ke(e,r,s,o){return{rows:e.map((i,c)=>({id:`bar-${c}`,label:i.label,value:i.value})),columns:[{field:"label",label:"Category",type:"category",align:"left",localizeValues:o},{field:"value",label:s,type:"number",unit:r,align:"right"}],series:[{id:"report-values",label:s,mark:"bar",xField:"label",yField:"value",color:"#2f6fed"}]}}function Re(e,r,s){const o=new Map,t=[],i=[{field:"label",label:"Observed period",type:"category",align:"left"}];return e.forEach((c,u)=>{const b=`value_${u}`,v=`low_${u}`,w=`high_${u}`,S=c.points.some(m=>Number.isFinite(m.low)&&Number.isFinite(m.high));i.push({field:b,label:c.label||s,type:"number",unit:r,align:"right"}),S&&i.push({field:v,label:`${c.label} low`,type:"number",unit:r,align:"right"},{field:w,label:`${c.label} high`,type:"number",unit:r,align:"right"}),c.points.forEach((m,j)=>{const h=m.label||`Point ${j+1}`,k=o.get(h)??{id:`point-${o.size}`,label:h};k[b]=m.value,S&&Number.isFinite(m.low)&&Number.isFinite(m.high)&&(k[v]=Number(m.low),k[w]=Number(m.high)),o.set(h,k)}),t.push({id:c.id||`series-${u}`,label:c.label||s,mark:"line",xField:"label",yField:b,color:c.color,smooth:!1,...S?{lowerField:v,upperField:w}:{}})}),{rows:[...o.values()],columns:i,series:t}}function Se(e){return e==="USD"?"usd":e==="Calls"?"count":e==="Percent"?"percent":"credits"}const _e="_page_p90q5_1",Le="_header_p90q5_7",Ce="_actions_p90q5_8",Ne="_status_p90q5_9",$e="_selectedMeta_p90q5_10",Fe="_switcher_p90q5_11",Pe="_selected_p90q5_10",Ue="_research_p90q5_24",Ee="_action_p90q5_8",Te="_eyebrow_p90q5_109",Me="_workspace_p90q5_166",Ae="_main_p90q5_173",ze="_metadata_p90q5_195",p={page:_e,header:Le,actions:Ce,status:Ne,selectedMeta:$e,switcher:Fe,selected:Pe,research:Ue,action:Ee,eyebrow:Te,workspace:Me,main:Ae,metadata:ze};function ot(e){return z(A(e.reports)??e.reports[0],e.calls)}function nt({model:e,refreshState:r,includeArchived:s,loadWindow:o,loadLimit:t,sourceKey:i,sourceRevision:c,onOpenInvestigator:u,onCopyCallLink:b}){const v=ee(),[w,S]=_.useState(()=>f(A(e.reports)??e.reports[0])),[m,j]=_.useState(""),h=!!e.contextRuntime.apiToken&&!e.contextRuntime.fileMode,k=o==="rows"?t:0,g=q({...be({runtime:e.contextRuntime,includeArchived:s,loadWindow:o,limit:k,evidenceLimit:8,sourceKey:i,sourceRevision:c}),enabled:h,placeholderData:d=>d}),l=h?g.data:void 0,N=l!=null&&l.reports.length?l.reports:e.reports,n=N.find(d=>f(d)===w)??N[0],x=_.useMemo(()=>{const d=n&&l?l.evidence[f(n)]:void 0;return d===void 0?z(n,e.calls):d},[n,e.calls,l]),R=D(n,x),C=l?"Live localhost report pack":"Loaded dashboard aggregates",$=(l==null?void 0:l.generatedAt)||(l?"Server timestamp unavailable":"Current dashboard snapshot"),K=De(h,g,m,r),V=_.useMemo(()=>je(n,e,x,{generatedAt:$,historyScope:s?"all":"active",source:C,sourceRevision:c}),[n,x,$,s,e,C,c]),F={label:"Report pack",status:te({enabled:h,hasData:!!g.data,isError:g.isError,isFetching:g.isFetching,isPending:g.isPending})},T=re([F.status]);async function O(){if(!h)return;j("Refreshing selected report…");const d=await g.refetch();j(d.isError?`Refresh failed: ${E(d.error)}. Showing cached live report pack.`:"Selected report refreshed")}function I(){Ke(`codex-report-${f(n)}-${ie()}.json`,{schema:"codex-usage-tracker-react-selected-report-v1",generated_at:(l==null?void 0:l.generatedAt)??null,source:C,report:n,method:R.method,caveat:R.caveat,evidence:x,raw_context_included:(l==null?void 0:l.rawContextIncluded)??!1}),j(`Exported ${(n==null?void 0:n.title)??"selected report"}`)}function Q(d){S(f(d)),j(`${d.title} selected`),we(d)}return a.jsxs("div",{className:p.page,children:[a.jsxs("header",{className:p.header,children:[a.jsxs("div",{children:[a.jsx("h1",{children:"Reports"}),a.jsx("p",{children:"Focused local research snapshots with their methods, limits, and investigator-linked evidence."})]}),a.jsxs("div",{className:p.actions,children:[a.jsxs("button",{className:p.action,type:"button",onClick:I,disabled:!n,children:[a.jsx(ae,{size:16,"aria-hidden":"true"}),"Export selected"]}),a.jsxs("button",{className:p.action,"data-primary":"true",type:"button",onClick:O,disabled:!h||g.isFetching,title:h?void 0:"Live report refresh requires the localhost server.",children:[a.jsx(se,{size:16,"aria-hidden":"true"}),h?g.isFetching?"Refreshing":"Refresh report":"Live refresh unavailable"]})]})]}),a.jsx(ne,{active:h&&g.isFetching,completed:T.ready,total:T.total,label:"Loading full-scope report pack",error:h&&g.error&&!g.data?`Report pack unavailable: ${E(g.error)}`:null,modules:[F],updating:F.status==="updating"}),a.jsxs("div",{className:p.status,role:"status","aria-live":"polite",children:[a.jsx(U,{label:l?"Live report pack":"Aggregate fallback",tone:l?"green":"orange"}),a.jsx("span",{children:K})]}),a.jsxs("section",{className:p.selected,"aria-labelledby":"selected-report-title",children:[a.jsxs("div",{children:[a.jsx("span",{className:p.eyebrow,children:R.eyebrow}),a.jsx("h2",{id:"selected-report-title",children:(n==null?void 0:n.title)??"No report available"}),a.jsx("p",{children:n==null?void 0:n.description}),a.jsx("p",{children:R.finding})]}),a.jsxs("div",{className:p.selectedMeta,children:[a.jsx(U,{label:(n==null?void 0:n.status)??"Blocked",tone:(n==null?void 0:n.status)==="Ready"?"green":"orange"}),a.jsxs("span",{children:[(n==null?void 0:n.owner)??"No owner"," · ",x.length," evidence rows"]})]})]}),a.jsx("div",{className:p.switcher,role:"group","aria-label":"Select report",children:N.map(d=>a.jsx("button",{type:"button","aria-pressed":f(d)===f(n),onClick:()=>Q(d),children:d.title},f(d)))}),a.jsxs("div",{className:p.workspace,children:[a.jsxs("section",{className:p.main,"aria-label":"Report evidence",children:[a.jsx(ce,{spec:V,height:320}),a.jsx(P,{title:"Report Evidence Calls",subtitle:v.translateText(`${x.length} aggregate rows · select a row to open Call Investigator`),action:a.jsx(U,{label:"Investigator linked",tone:"green"}),children:a.jsx(ye,{calls:x,onOpenInvestigator:u,onCopyCallLink:b})})]}),a.jsxs("aside",{className:p.research,children:[a.jsxs(P,{title:"Research Notes",subtitle:"Interpretation boundary",children:[a.jsxs("section",{children:[a.jsx("h3",{children:"Method"}),a.jsx("p",{children:R.method})]}),a.jsxs("section",{children:[a.jsx("h3",{children:"Caveat"}),a.jsx("p",{children:R.caveat})]}),a.jsxs("section",{children:[a.jsx("h3",{children:"Evidence privacy"}),a.jsx("p",{children:"Raw context is not included here; inspect an explicitly linked call in Call Investigator."})]})]}),a.jsx(P,{title:"Generation Metadata",subtitle:"Provenance for this view",children:a.jsxs("dl",{className:p.metadata,children:[a.jsxs("div",{children:[a.jsx("dt",{children:"Source"}),a.jsx("dd",{children:C})]}),a.jsxs("div",{children:[a.jsx("dt",{children:"Generated"}),a.jsx("dd",{children:$})]}),a.jsxs("div",{children:[a.jsx("dt",{children:"Schema"}),a.jsx("dd",{children:(l==null?void 0:l.schema)||"dashboard aggregate model"})]}),a.jsxs("div",{children:[a.jsx("dt",{children:"Matched"}),a.jsxs("dd",{children:[l?l.totalMatchedRows.toLocaleString():e.calls.length.toLocaleString()," loaded rows"]})]})]})})]})]})]})}function De(e,r,s,o){return s||(e?r.isLoading?"Loading live report pack…":r.error?`Live report pack unavailable: ${E(r.error)}. Using loaded aggregate fallback.`:r.isFetching?"Refreshing live report pack…":r.data&&r.isStale?"Cached live report pack is stale; refresh is available.":"Live report pack ready.":o||"Static mode: using the loaded aggregate snapshot.")}function Ke(e,r){const s=JSON.stringify(r,null,2),o=new Blob([s],{type:"application/json;charset=utf-8"}),t=typeof URL.createObjectURL=="function"?URL.createObjectURL(o):`data:application/json;charset=utf-8,${encodeURIComponent(s)}`,i=document.createElement("a");i.href=t,i.download=e,i.rel="noopener",document.body.append(i),i.click(),i.remove(),t.startsWith("blob:")&&typeof URL.revokeObjectURL=="function"&&URL.revokeObjectURL(t)}function E(e){return e instanceof Error?e.message:String(e)}export{nt as ReportsPage,ot as reportCallsForCurrentUrl}; diff --git a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/SettingsPage.js b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/SettingsPage.js index bf084465..c1245fa2 100644 --- a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/SettingsPage.js +++ b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/SettingsPage.js @@ -1,4 +1,4 @@ -import{r as j,j as t}from"./dashboard-react.js";import{P as A}from"./Panel.js";import{S as v}from"./StatusBadge.js";import{c as W,H as h,G as B,af as M,R as $}from"./App.js";import{G as f}from"./gauge.js";import{S as _}from"./shield-check.js";import{L as T}from"./lock-keyhole.js";import{T as H}from"./triangle-alert.js";import"./locale-zh-Hans.js";import"./dashboardRouter.js";import"./router.js";/** +import{r as j,j as t}from"./dashboard-react.js";import{P as A}from"./Panel.js";import{S as v}from"./StatusBadge.js";import{c as W,H as h,G as B,al as M,R as $}from"./App.js";import{G as f}from"./gauge.js";import{S as _}from"./shield-check.js";import{L as T}from"./lock-keyhole.js";import{T as H}from"./triangle-alert.js";import"./locale-zh-Hans.js";import"./dashboardRouter.js";import"./router.js";/** * @license lucide-react v0.468.0 - ISC * * This source code is licensed under the ISC license. diff --git a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/contextEvidenceState.js b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/contextEvidenceState.js index 40efac0b..7472afcf 100644 --- a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/contextEvidenceState.js +++ b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/contextEvidenceState.js @@ -1 +1 @@ -import{j as n}from"./dashboard-react.js";import{j as s,p,u as G,f as N,m as q,a as J}from"./App.js";function ye({call:e,calls:t}){const i=[...t,e].filter((u,l,g)=>g.findIndex(c=>c.id===u.id)===l).sort(te),a=i.findIndex(u=>u.id===e.id),r=a>0?i[a-1]:null,o=r?Z(e,r):null,h=V(e,r);return n.jsxs("div",{className:"call-cache-delta-card composition-card",children:[n.jsxs("div",{className:"composition-head",children:[n.jsx("strong",{children:"Cache Accounting Delta"}),n.jsx("span",{children:r?`Call ${a+1} of ${i.length}`:"No previous call"})]}),n.jsx(K,{call:e,delta:o,diagnostic:h,previous:r,selectedIndex:a,totalCalls:i.length}),r&&o?n.jsxs(n.Fragment,{children:[n.jsx("p",{className:"diagnostic-interpretation",children:ee(o)}),n.jsxs("div",{className:"cache-delta-grid",children:[n.jsx(f,{label:"Last call input",value:x(o.input),detail:`${s(r.input)} -> ${s(e.input)}`}),n.jsx(f,{label:"Cached input",value:x(o.cached),detail:`${s(r.cachedInput)} -> ${s(e.cachedInput)}`}),n.jsx(f,{label:"Uncached input",value:x(o.uncached),detail:`${s(r.uncachedInput)} -> ${s(e.uncachedInput)}`}),n.jsx(f,{label:"Output",value:x(o.output),detail:`${s(r.output)} -> ${s(e.output)}`}),n.jsx(f,{label:"Reasoning output",value:x(o.reasoning),detail:`${s(r.reasoningOutput)} -> ${s(e.reasoningOutput)}`}),n.jsx(f,{label:"Cache ratio",value:M(o.cacheRatio),detail:`${p(r.cachedPct)} -> ${p(e.cachedPct)}`})]})]}):n.jsx("p",{className:"empty-state",children:"No previous aggregate call available for cache delta accounting."})]})}function K({call:e,delta:t,diagnostic:i,previous:a,selectedIndex:r,totalCalls:o}){return n.jsxs("div",{className:`cache-verdict-card cache-verdict-${i.key}`,children:[n.jsxs("div",{className:"cache-verdict-main",children:[n.jsx("span",{className:"cache-diagnostic-pill",children:i.label}),n.jsx("p",{children:i.body})]}),n.jsxs("div",{className:"cache-verdict-meta",children:[n.jsxs("span",{children:["Cache ratio: ",p(e.cachedPct)]}),n.jsx("span",{children:t?Q(t):"No previous call loaded for delta comparison."}),n.jsxs("span",{children:["Position: ",r+1," of ",o]}),n.jsxs("span",{children:["Next: ",Y(e,i,a)]})]})]})}function V(e,t){const i=X(e,t);return i==="spike"?{key:"spike",label:"Uncached spike",body:"Fresh input rose sharply compared with the previous call in this resolved thread."}:i==="warm"?{key:"warm",label:"Warm cache reuse",body:"Most input tokens reused prompt cache. The uncached portion is the most likely investigation target."}:i==="partial"?{key:"partial",label:"Partial cache miss",body:"Some prefix reused cache, but a meaningful share of input was fresh or reserialized."}:{key:"cold",label:"Cold resume / stale cache",body:"Conversation-specific cache likely expired or missed; remaining cache is probably stable Codex scaffolding or tool schema prefix."}}function X(e,t){const i=$(e),a=t?$(t):null,r=(t==null?void 0:t.uncachedInput)??0,o=.05,h=.85,u=.8,l=1e3;return t&&a!==null&&a>=u&&i<=o&&e.input>=l?"cold":t&&e.uncachedInput>Math.max(r*2,l)?"spike":i>=h?"warm":i>o?"partial":"cold"}function $(e){return Number.isFinite(e.cachedPct)?e.cachedPct/100:0}function Q(e){return`Uncached input: ${x(e.uncached)}. Cached input: ${x(e.cached)}. Cache ratio: ${M(e.cacheRatio)}.`}function Y(e,t,i){return t.key==="cold"?"Compare the previous call, then inspect loaded evidence to see what fresh context was sent after the cache miss.":t.key==="spike"?"Inspect the most recent evidence entries first; the spike is in fresh uncached input, not cached history.":t.key==="warm"?`Cache reuse is healthy; focus on ${s(e.uncachedInput)} uncached tokens that were still billed as fresh input.`:i?"Use delta cards to locate whether the change came from cached input, uncached input, or output/reasoning.":"Use loaded evidence if aggregate totals are not enough to understand this isolated call."}function Z(e,t){return{input:e.input-t.input,cached:e.cachedInput-t.cachedInput,uncached:e.uncachedInput-t.uncachedInput,output:e.output-t.output,reasoning:e.reasoningOutput-t.reasoningOutput,cacheRatio:e.cachedPct-t.cachedPct}}function ee(e){return e.uncached>0&&e.cached<0?`Uncached input rose by ${s(e.uncached)} while cached input fell by ${s(Math.abs(e.cached))}.`:e.uncached>0?`Uncached input increased by ${s(e.uncached)} versus the previous aggregate call.`:e.uncached<0&&e.cached>=0?`Uncached input decreased by ${s(Math.abs(e.uncached))} while cached input increased.`:"Cache accounting is stable versus the previous aggregate call."}function f({label:e,value:t,detail:i}){return n.jsxs("span",{className:"cache-delta-metric",children:[n.jsx("small",{children:e}),n.jsx("strong",{children:t}),n.jsx("em",{children:i})]})}function x(e){return e===0?"0":`${e>0?"+":"-"}${s(Math.abs(e))}`}function M(e){return e===0?"0.0pp":`${e>0?"+":"-"}${Math.abs(e).toFixed(1)}pp`}function te(e,t){return S(e)-S(t)}function S(e){const t=Date.parse(e.rawTime||e.time);return Number.isFinite(t)?t:0}function we({call:e}){const t=G(),i=ne(e);return n.jsxs("div",{className:"call-decision-card",children:[n.jsxs("div",{className:"section-heading compact",children:[n.jsx("h3",{children:"Call Decision"}),n.jsx("span",{children:i.nextAction})]}),n.jsxs("dl",{className:"detail-list compact",children:[n.jsxs("div",{children:[n.jsx("dt",{children:"Pricing status"}),n.jsx("dd",{children:i.pricingStatus})]}),n.jsxs("div",{children:[n.jsx("dt",{children:t.t("detail.next_action","Next action")}),n.jsx("dd",{children:i.nextAction})]}),n.jsxs("div",{children:[n.jsx("dt",{children:"Why flagged"}),n.jsx("dd",{children:i.whyFlagged})]}),n.jsxs("div",{children:[n.jsx("dt",{children:"Allowance impact"}),n.jsx("dd",{children:i.allowanceImpact})]}),n.jsxs("div",{children:[n.jsx("dt",{children:"Context use"}),n.jsx("dd",{children:i.contextUse})]})]})]})}function ne(e){return{allowanceImpact:`${oe(e.credits)} counted`,contextUse:e.contextWindowPct==null?"Not reported":p(e.contextWindowPct),nextAction:re(e),pricingStatus:ie(e),whyFlagged:ae(e)}}function ie(e){return e.cost<=0?"No configured price":e.pricingEstimated?"Best-guess estimate":"Configured price"}function re(e){return e.recommendation?e.recommendation:e.cost<=0?"Configure pricing":e.cachedPct<30&&e.input>0?"Compare fresh input":(e.contextWindowPct??0)>=60?"Inspect thread timeline":e.reasoningOutput>e.output?"Review reasoning effort":"Use aggregate first"}function ae(e){return e.recommendation?e.recommendation:e.signal&&e.signal!=="aggregate"?`${e.signal} aggregate signal`:(e.contextWindowPct??0)>=60?"High reported context use.":e.cachedPct<30&&e.input>0?`${N(e.uncachedInput)} uncached input with weak cache reuse.`:e.reasoningOutput>e.output?"Reasoning output exceeds visible output.":e.cost>0||e.credits>0?"Review cost and credit impact before loading raw context.":"No aggregate efficiency flag on this row."}function oe(e){return`${new Intl.NumberFormat("en-US",{maximumFractionDigits:2}).format(e)} credits`}function _e(e){return e.cachedPct<25?"cold or weak cache":e.cachedPct<50?"partial cache reuse":"healthy cache reuse"}function P(e){return e.sourceFile?`${e.sourceFile}${e.lineNumber?`:${e.lineNumber}`:""}`:"Not available"}function ke(e){if(e.contextWindowPct===null||e.contextWindowPct===void 0)return"Not reported";const t=e.modelContextWindow?` of ${N(e.modelContextWindow)}`:"";return`${p(e.contextWindowPct)}${t}`}function $e(e,{limit:t=2,emptyLabel:i="No related calls",unknownLabel:a="Unknown",style:r="parenthetical"}={}){const o=new Map;for(const u of e){const l=u||a;o.set(l,(o.get(l)??0)+1)}const h=[...o.entries()].sort((u,l)=>l[1]-u[1]||u[0].localeCompare(l[0])).slice(0,t).map(([u,l])=>r==="x"?`${u} x${l}`:`${u} (${l})`);return h.length?h.join(", "):i}function Se({call:e}){return n.jsxs("div",{className:"call-source-card composition-card",children:[n.jsxs("div",{className:"composition-head",children:[n.jsx("strong",{children:"Call Source"}),n.jsx("span",{children:P(e)})]}),n.jsxs("dl",{className:"detail-list compact",children:[n.jsx(d,{label:"Project",value:e.project||"Unknown"}),n.jsx(d,{label:"Project path",value:e.projectRelativeCwd||e.cwd||"."}),n.jsx(d,{label:"Project tags",value:e.projectTags.length?e.projectTags.join(", "):"None"}),n.jsx(d,{label:"Thread attachment",value:e.threadAttachmentLabel||"Direct thread"}),n.jsx(d,{label:"Thread source",value:e.threadSource||"user"}),n.jsx(d,{label:"Subagent type",value:e.subagentType||"None"}),n.jsx(d,{label:"Agent role",value:e.agentRole||"None"}),n.jsx(d,{label:"Agent nickname",value:e.agentNickname||"None"}),n.jsx(d,{label:"Source line",value:P(e)}),n.jsx(d,{label:"Initiated by",value:e.initiator||"unknown"}),n.jsx(d,{label:"Initiator reason",value:e.initiatorReason||"Not reported"}),n.jsx(d,{label:"Session",value:e.sessionId||"Not available"}),n.jsx(d,{label:"Turn",value:e.turnId||"None"}),n.jsx(d,{label:"Parent thread",value:e.parentThread||"None"}),n.jsx(d,{label:"Parent session",value:e.parentSessionId||"None"}),n.jsx(d,{label:"Parent updated",value:se(e.parentSessionUpdatedAt)}),n.jsx(d,{label:"Working directory",value:e.cwd||"Not available"}),n.jsx(d,{label:"Git branch",value:e.gitBranch||"Not available"}),n.jsx(d,{label:"Git remote",value:e.gitRemoteLabel||"Not available"}),n.jsx(d,{label:"Git hash",value:e.gitRemoteHash||"Not available"}),n.jsx(d,{label:"Credit note",value:e.usageCreditNote||"None"})]})]})}function se(e){if(!e)return"None";const t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString([],{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}function d({label:e,value:t}){return n.jsxs("div",{children:[n.jsx("dt",{children:e}),n.jsx("dd",{children:t})]})}function Pe({call:e}){return n.jsxs("dl",{className:"detail-list",children:[n.jsx(m,{label:"Last call total",value:s(e.totalTokens)}),n.jsx(m,{label:"Last call input",value:s(e.input)}),n.jsx(m,{label:"Cached input",value:s(e.cachedInput)}),n.jsx(m,{label:"Output tokens",value:s(e.output)}),n.jsx(m,{label:"Reasoning output",value:s(e.reasoningOutput)}),n.jsx(m,{label:"Session cumulative",value:e.cumulativeTotalTokens?s(e.cumulativeTotalTokens):"Not reported"}),n.jsx(m,{label:"Pricing model",value:ce(e)}),n.jsx(m,{label:"Credit model",value:e.usageCreditModel||"No mapped rate"}),n.jsx(m,{label:"Credit confidence",value:e.usageCreditConfidence||"unknown"}),n.jsx(m,{label:"Credit source",value:e.usageCreditSource||"None"}),n.jsx(m,{label:"Credit source fetched",value:e.usageCreditFetchedAt||"Unknown"}),n.jsx(m,{label:"Credit tier",value:e.usageCreditTier||"Unknown"}),n.jsx(m,{label:"Cache savings",value:q(e.estimatedCacheSavings)}),n.jsx(m,{label:"Efficiency signals",value:de(e)})]})}function m({label:e,value:t}){return n.jsxs("div",{children:[n.jsx("dt",{children:e}),n.jsx("dd",{children:t})]})}function ce(e){return e.pricingModel?e.pricingModel:e.cost<=0?"No configured price":e.pricingEstimated?"Best-guess estimate":"Configured price"}function de(e){return e.efficiencyFlags.length?e.efficiencyFlags.join(", "):e.signal&&e.signal!=="aggregate"?e.signal:e.recommendation?"recommendation":"None"}function Te({selectedCall:e,calls:t,onOpenInvestigator:i,onCopyCallLink:a,className:r,copyAriaContext:o,copyLabel:h="Copy link"}){const u=t.length?t:[e],l=ue(u,e.id),g=["thread-mini-timeline",r].filter(Boolean).join(" ");return n.jsx("ol",{className:g,children:l.map(c=>n.jsxs("li",{className:c.id===e.id?"active":"",children:[n.jsx("span",{children:c.time}),n.jsxs("strong",{children:[c.model," / ",c.effort]}),n.jsxs("em",{children:[N(c.totalTokens)," tokens - ",p(c.cachedPct)," cache"]}),n.jsxs("div",{className:"thread-call-meta",children:[n.jsx("span",{children:c.initiator||"unknown"}),n.jsx("span",{children:c.duration}),n.jsxs("span",{children:["prev ",c.previousCallGap]})]}),n.jsxs("div",{className:"thread-call-flags",children:[n.jsxs("span",{children:["Context ",le(c)]}),n.jsx("span",{children:me(c)})]}),c.recommendation?n.jsx("p",{className:"thread-call-recommendation",children:c.recommendation}):null,n.jsx("div",{className:"thread-context-bar",title:`Context window ${he(c)}`,children:n.jsx("span",{className:pe(c.contextWindowPct),style:{width:xe(c.contextWindowPct)}})}),n.jsxs("div",{className:"thread-call-actions table-action-group",children:[n.jsx("button",{className:"table-action-button",type:"button",onClick:()=>i(c.id),disabled:c.id===e.id,children:"Open"}),n.jsxs("button",{className:"table-action-button",type:"button","aria-label":`${h} for ${o} ${c.thread} ${c.model}`,onClick:()=>a(c.id),children:[n.jsx(J,{size:14})," ",h]})]})]},c.id))})}function ue(e,t){if(e.length<=5)return e;const i=e.findIndex(r=>r.id===t);if(i<0)return e.slice(0,5);const a=Math.max(0,Math.min(i-2,e.length-5));return e.slice(a,a+5)}function le(e){return e.contextWindowPct===null?"Not reported":p(e.contextWindowPct)}function he(e){if(e.contextWindowPct===null)return"Not reported";const t=e.modelContextWindow?` of ${N(e.modelContextWindow)}`:"";return`${p(e.contextWindowPct)}${t}`}function me(e){return e.cost<=0?"No configured price":e.pricingEstimated?"Best-guess estimate":"Configured price"}function pe(e){const t=Number(e??0);return t>=75?"high":t>=50?"medium":"low"}function xe(e){const t=Number(e??0),i=Math.max(0,Math.min(100,Number.isFinite(t)?t:0));return`${Math.round(i)}%`}async function ze(e){I(e);const t=new URLSearchParams({enabled:"1",_:String(Date.now())}),i=await fetch(`/api/context-settings?${t.toString()}`,{headers:A(e),cache:"no-store"});return!!(await O(i,"Context settings")).context_api_enabled}async function Ee(e,t,i){if(I(t),!t.contextApiEnabled)throw new Error("Context API is not enabled.");const a=new URLSearchParams({record_id:e,mode:i.mode,include_tool_output:i.includeToolOutput?"1":"0",include_compaction_history:i.includeCompactionHistory?"1":"0",max_chars:String(i.maxChars),max_entries:String(i.maxEntries),_:String(Date.now())});return await fetch(`/api/context?${a.toString()}`,{headers:A(t),cache:"no-store"}).then(o=>O(o,"Context"))}function I(e){if(e.fileMode)throw new Error("Context loading requires the localhost dashboard server.");if(!e.apiToken)throw new Error("Context loading requires a localhost dashboard API token.")}function A(e){return{Accept:"application/json","X-Codex-Usage-Token":e.apiToken}}async function O(e,t){let i={};try{i=await e.json()}catch{i={}}if(!e.ok){const a=typeof i.error=="string"?i.error:`${t} API returned HTTP ${e.status}.`;throw new Error(a)}if(typeof i.error=="string")throw new Error(i.error);return i}function Me({call:e,payload:t,onRunFullAnalysis:i,showHeading:a=!0}){const r=ge(e,t);return n.jsxs("div",{className:"context-attribution-module",children:[a?n.jsxs("div",{className:"serialized-breakdown-heading context-attribution-heading",children:[n.jsx("strong",{children:"Context Attribution"}),n.jsx("span",{children:"Estimated from visible log volume"})]}):null,n.jsxs("div",{className:"drilldown-metric-grid wide context-attribution-grid",children:[n.jsx(b,{label:"Uncached input",value:s(e.uncachedInput),detail:"exact aggregate row"}),n.jsx(b,{label:"Visible new context estimate",value:r?`~${s(r.visibleTokenEstimate)}`:"Not loaded yet",detail:r?`${s(r.visibleChars)} analyzed chars`:"Runtime evidence"}),n.jsx(b,{label:"Serialized local upper bound",value:r!=null&&r.serializedTokens?`~${s(r.serializedBound)}`:"Not loaded yet",detail:r!=null&&r.serializedTokens?be(r):"Runtime evidence"}),n.jsx(b,{label:"Unexplained hidden/serialized input estimate",value:r?`~${s(r.visibleGap)}`:"Not loaded yet",detail:r?"uncached input minus visible estimate":"Runtime evidence"}),n.jsx(b,{label:"Possible serialized overhead",value:r?`~${s(r.serializedCandidate)}`:"Not loaded yet",detail:r?"serialized upper bound minus visible estimate":"Runtime evidence"}),n.jsx(b,{label:"Remaining after serialized bound",value:r?`~${s(r.remainingAfterSerialized)}`:"Not loaded yet",detail:r?"not covered by serialized upper bound":"Runtime evidence"})]}),n.jsx(fe,{stats:r,onRunFullAnalysis:(t==null?void 0:t.context_mode)==="full"?void 0:i}),n.jsx("p",{className:"privacy-note",children:"Compare exact uncached input with tokenizer-counted visible log evidence. Treat the gap as hidden scaffolding, serialization, or tokenizer estimate error."})]})}function b({label:e,value:t,detail:i}){return n.jsxs("span",{className:"drilldown-metric",children:[n.jsx("small",{children:e}),n.jsx("strong",{children:t}),n.jsx("em",{children:i})]})}function fe({stats:e,onRunFullAnalysis:t}){if(!(e!=null&&e.serializedTokens))return null;if(e.serializedDeferred)return n.jsxs("div",{className:"serialized-breakdown deferred",children:[n.jsxs("div",{className:"serialized-breakdown-heading",children:[n.jsx("strong",{children:"Serialized evidence groups"}),n.jsx("span",{children:"Fast estimate loaded; full serialized grouping is deferred."})]}),t?n.jsx("div",{className:"context-followup-actions",children:n.jsx("button",{className:"toolbar-button serialized-action",type:"button",onClick:t,children:"Run full serialized analysis"})}):null]});const i=e.serializedBuckets.slice(0,6);return n.jsxs("div",{className:"serialized-breakdown",children:[n.jsxs("div",{className:"serialized-breakdown-heading",children:[n.jsx("strong",{children:"Serialized evidence groups"}),n.jsx("span",{children:"Upper-bound local JSONL structure; not exact prompt text."})]}),n.jsx("div",{className:"serialized-bucket-grid",children:i.length?i.map(a=>n.jsxs("div",{className:"serialized-bucket",children:[n.jsx("span",{children:a.label||a.key||"Unknown"}),n.jsx("strong",{children:s(Number(a.token_estimate??0))}),n.jsxs("small",{children:[s(Number(a.count??0))," fields · ",s(Number(a.char_count??0))," chars"]}),a.note?n.jsx("small",{children:a.note}):null]},a.key??a.label??String(a.token_estimate??0))):n.jsx("p",{className:"empty-state",children:"No serialized evidence groups returned."})})]})}function be(e){const t=[`${s(e.serializedChars)} raw JSON chars`,e.serializedEstimator];return e.serializedDeferred&&t.push("fast estimate"),e.serializedLineCount&&t.push(`${s(e.serializedLineCount)} raw lines`),t.join(" · ")}function ge(e,t){if(!t)return null;const i=t.entries??[],a=Number(t.visible_char_count??i.reduce((B,H)=>B+String(H.text??"").length,0)),r=Number(t.visible_token_estimate??Math.ceil(a/4)),o=t.serialized_evidence??{},h=Number(o.raw_json_token_estimate??o.token_estimate??0),u=Number(o.raw_json_char_count??o.total_chars??0),l=Number(o.raw_line_count??0),g=o.token_estimator||t.visible_token_estimator||"chars_per_4_fallback",c=Array.isArray(o.buckets)?o.buckets:[],v=h>0?Math.min(h,e.uncachedInput):0,k=Math.max(e.uncachedInput-r,0),F=v>r?v-r:0,L=h>0?Math.max(e.uncachedInput-Math.max(r,v),0):k;return{visibleChars:a,visibleTokenEstimate:r,serializedTokens:h,serializedChars:u,serializedLineCount:l,serializedEstimator:g,serializedBound:v,visibleGap:k,serializedCandidate:F,remainingAfterSerialized:L,serializedDeferred:!!(o.deferred||o.deferred_buckets),serializedBuckets:c}}function Ie({entry:e}){const t=je(e);return t.length?n.jsx("div",{className:"context-entry-chips","aria-label":"Evidence entry metadata",children:t.map(i=>n.jsxs("span",{title:i.title,children:[i.label,": ",i.value]},`${i.label}-${i.value}`))}):null}function je(e){var o,h,u,l;const t=[],i=e.action_timing;(i==null?void 0:i.since_turn_start_ms)!==void 0&&t.push({label:"T+",value:y(i.since_turn_start_ms),title:"Elapsed since selected turn start"}),(i==null?void 0:i.since_previous_entry_ms)!==void 0&&t.push({label:"Gap",value:y(i.since_previous_entry_ms),title:"Gap since previous evidence entry"}),(i==null?void 0:i.reported_duration_ms)!==void 0&&t.push({label:"Duration",value:y(i.reported_duration_ms),title:i.duration_source||"Duration reported by this event"});const a=(o=e.token_usage)==null?void 0:o.last_token_usage;a&&t.push({label:"Entry tokens",value:T(a),title:"Token usage reported for this evidence entry"});const r=(h=e.token_usage)==null?void 0:h.total_token_usage;return r&&t.push({label:"Session tokens",value:T(r),title:"Cumulative token usage reported by this evidence entry"}),(u=e.compaction)!=null&&u.replacement_history_available&&t.push({label:"Compaction",value:`${s(((l=e.compaction.replacement_history)==null?void 0:l.length)??0)} replacement entries`,title:"Compaction replacement history is available for this entry"}),e.tool_output_omitted&&t.push({label:"Tool output",value:"omitted",title:"Tool output omitted until explicitly requested"}),t}function T(e){const t=Number((e==null?void 0:e.input_tokens)??0),i=Number((e==null?void 0:e.cached_input_tokens)??0),a=Number((e==null?void 0:e.uncached_input_tokens)??Math.max(t-i,0)),r=Number((e==null?void 0:e.output_tokens)??0),o=Number((e==null?void 0:e.total_tokens)??t+r);return`${s(o)} total · ${s(a)} uncached`}function y(e){if(!Number.isFinite(e))return"0ms";if(e<1e3)return`${Math.round(e)}ms`;const t=e/1e3;return t>=10?`${t.toFixed(0)}s`:`${t.toFixed(1)}s`}const R=new Map,U=new Map,w=new Map,_=new Map,D=new Map;function Ae(e,t){return R.get(W(e,t))??null}function Oe(e,t,i){R.set(W(e,t),i)}function Re(e){const t=U.get(e);return t?{...t}:null}function Ue(e,t){U.set(e,{...t})}function ve(e,t){return`${e.type??"entry"}-${e.line_number??e.timestamp??t}`}function De(e,t){const i=w.get(e);return i?new Set(i):t.length?new Set([ve(t[0],0)]):new Set}function We(e,t,i){const a=w.get(e)??new Set;i?a.add(t):a.delete(t),w.set(e,a)}function Fe(e,t){var i;return((i=_.get(e))==null?void 0:i.get(t))??0}function Le(e,t,i){const a=_.get(e)??new Map;i>0?a.set(t,i):a.delete(t),_.set(e,a)}function Be(e){return D.get(e)??!1}function He(e,t){D.set(e,t)}function W(e,t){return[e,t.mode,t.includeToolOutput?"tool-output":"no-tool-output",t.includeCompactionHistory?"compaction-history":"no-compaction-history",String(t.maxChars),String(t.maxEntries)].join("|")}const C={includeToolOutput:!1,includeCompactionHistory:!1,maxChars:8e3,maxEntries:20,mode:"quick"};function Ge(e=window.location.search,t=C){const i=new URLSearchParams(e);return{includeToolOutput:z(i.get("include_tool_output"),t.includeToolOutput),includeCompactionHistory:z(i.get("include_compaction_history"),t.includeCompactionHistory),maxChars:E(i.get("max_chars"),t.maxChars),maxEntries:E(i.get("max_entries"),t.maxEntries),mode:i.get("mode")==="full"?"full":t.mode}}function qe(e,t,i=C){j(e,"mode",t.mode,i.mode),j(e,"max_entries",String(t.maxEntries),String(i.maxEntries)),j(e,"max_chars",String(t.maxChars),String(i.maxChars)),j(e,"include_tool_output",t.includeToolOutput?"1":"0",i.includeToolOutput?"1":"0"),j(e,"include_compaction_history",t.includeCompactionHistory?"1":"0",i.includeCompactionHistory?"1":"0")}function Je(e){return e.fileMode?"Static file mode cannot read local JSONL context. Use serve-dashboard with the context API enabled.":e.apiToken?e.contextApiEnabled?"Context API is enabled. Load selected-turn evidence from the local JSONL source only when needed.":"Context API is available but off. Enable it here before loading selected-turn evidence.":"Context loading requires the localhost dashboard server API token."}function z(e,t){return e==="1"||e==="true"?!0:e==="0"||e==="false"?!1:t}function E(e,t){if(e===null||e.trim()==="")return t;const i=Number(e);return Number.isFinite(i)&&i>=0?Math.floor(i):t}function j(e,t,i,a){i===a?e.searchParams.delete(t):e.searchParams.set(t,i)}function Ke(e){return e instanceof Error?e.message:String(e)}function Ve(e,t){var o;const i=Number(((o=e.omitted)==null?void 0:o.max_entries)??t.maxEntries??C.maxEntries),a=C.maxEntries||20,r=i>0?Math.max(i+a,i*2):0;return{...t,maxEntries:r}}function Xe(e){const t=e.omitted??{},i=e.source??{},a=["Local JSONL context loaded on demand.",e.include_tool_output?"Tool output included with redaction and size limits.":"Tool output hidden for this view."];i.file&&a.push(`Source: ${i.file}${i.line_number?`:${i.line_number}`:""}.`);const r=Number(t.older_entries??0);r>0&&a.push(`${s(r)} older entries omitted.`);const o=Number(t.over_budget_chars??0);return o>0&&a.push(`${s(o)} chars over budget omitted.`),Number(t.max_chars??NaN)===0&&a.push("No character limit applied."),a}export{P as A,Ge as B,Me as C,qe as D,Pe as T,Ae as a,Je as b,Re as c,C as d,ze as e,Ke as f,Oe as g,Xe as h,Be as i,De as j,ve as k,Ee as l,Ie as m,Le as n,Fe as o,Ve as p,We as q,Ue as r,He as s,_e as t,we as u,Se as v,ye as w,$e as x,Te as y,ke as z}; +import{j as n}from"./dashboard-react.js";import{j as o,p as x,u as G,f as w,ak as $,m as q,a as J}from"./App.js";function ye({call:e,calls:t}){const i=[...t,e].filter((h,m,b)=>b.findIndex(c=>c.id===h.id)===m).sort(te),a=i.findIndex(h=>h.id===e.id),r=a>0?i[a-1]:null,s=r?Z(e,r):null,l=V(e,r);return n.jsxs("div",{className:"call-cache-delta-card composition-card",children:[n.jsxs("div",{className:"composition-head",children:[n.jsx("strong",{children:"Cache Accounting Delta"}),n.jsx("span",{children:r?`Call ${a+1} of ${i.length}`:"No previous call"})]}),n.jsx(K,{call:e,delta:s,diagnostic:l,previous:r,selectedIndex:a,totalCalls:i.length}),r&&s?n.jsxs(n.Fragment,{children:[n.jsx("p",{className:"diagnostic-interpretation",children:ee(s)}),n.jsxs("div",{className:"cache-delta-grid",children:[n.jsx(f,{label:"Last call input",value:p(s.input),detail:`${o(r.input)} -> ${o(e.input)}`}),n.jsx(f,{label:"Cached input",value:p(s.cached),detail:`${o(r.cachedInput)} -> ${o(e.cachedInput)}`}),n.jsx(f,{label:"Uncached input",value:p(s.uncached),detail:`${o(r.uncachedInput)} -> ${o(e.uncachedInput)}`}),n.jsx(f,{label:"Output",value:p(s.output),detail:`${o(r.output)} -> ${o(e.output)}`}),n.jsx(f,{label:"Reasoning output",value:p(s.reasoning),detail:`${o(r.reasoningOutput)} -> ${o(e.reasoningOutput)}`}),n.jsx(f,{label:"Cache ratio",value:M(s.cacheRatio),detail:`${x(r.cachedPct)} -> ${x(e.cachedPct)}`})]})]}):n.jsx("p",{className:"empty-state",children:"No previous aggregate call available for cache delta accounting."})]})}function K({call:e,delta:t,diagnostic:i,previous:a,selectedIndex:r,totalCalls:s}){return n.jsxs("div",{className:`cache-verdict-card cache-verdict-${i.key}`,children:[n.jsxs("div",{className:"cache-verdict-main",children:[n.jsx("span",{className:"cache-diagnostic-pill",children:i.label}),n.jsx("p",{children:i.body})]}),n.jsxs("div",{className:"cache-verdict-meta",children:[n.jsxs("span",{children:["Cache ratio: ",x(e.cachedPct)]}),n.jsx("span",{children:t?Q(t):"No previous call loaded for delta comparison."}),n.jsxs("span",{children:["Position: ",r+1," of ",s]}),n.jsxs("span",{children:["Next: ",Y(e,i,a)]})]})]})}function V(e,t){const i=X(e,t);return i==="spike"?{key:"spike",label:"Uncached spike",body:"Fresh input rose sharply compared with the previous call in this resolved thread."}:i==="warm"?{key:"warm",label:"Warm cache reuse",body:"Most input tokens reused prompt cache. The uncached portion is the most likely investigation target."}:i==="partial"?{key:"partial",label:"Partial cache miss",body:"Some prefix reused cache, but a meaningful share of input was fresh or reserialized."}:{key:"cold",label:"Cold resume / stale cache",body:"Conversation-specific cache likely expired or missed; remaining cache is probably stable Codex scaffolding or tool schema prefix."}}function X(e,t){const i=S(e),a=t?S(t):null,r=(t==null?void 0:t.uncachedInput)??0,s=.05,l=.85,h=.8,m=1e3;return t&&a!==null&&a>=h&&i<=s&&e.input>=m?"cold":t&&e.uncachedInput>Math.max(r*2,m)?"spike":i>=l?"warm":i>s?"partial":"cold"}function S(e){return Number.isFinite(e.cachedPct)?e.cachedPct/100:0}function Q(e){return`Uncached input: ${p(e.uncached)}. Cached input: ${p(e.cached)}. Cache ratio: ${M(e.cacheRatio)}.`}function Y(e,t,i){return t.key==="cold"?"Compare the previous call, then inspect loaded evidence to see what fresh context was sent after the cache miss.":t.key==="spike"?"Inspect the most recent evidence entries first; the spike is in fresh uncached input, not cached history.":t.key==="warm"?`Cache reuse is healthy; focus on ${o(e.uncachedInput)} uncached tokens that were still billed as fresh input.`:i?"Use delta cards to locate whether the change came from cached input, uncached input, or output/reasoning.":"Use loaded evidence if aggregate totals are not enough to understand this isolated call."}function Z(e,t){return{input:e.input-t.input,cached:e.cachedInput-t.cachedInput,uncached:e.uncachedInput-t.uncachedInput,output:e.output-t.output,reasoning:e.reasoningOutput-t.reasoningOutput,cacheRatio:e.cachedPct-t.cachedPct}}function ee(e){return e.uncached>0&&e.cached<0?`Uncached input rose by ${o(e.uncached)} while cached input fell by ${o(Math.abs(e.cached))}.`:e.uncached>0?`Uncached input increased by ${o(e.uncached)} versus the previous aggregate call.`:e.uncached<0&&e.cached>=0?`Uncached input decreased by ${o(Math.abs(e.uncached))} while cached input increased.`:"Cache accounting is stable versus the previous aggregate call."}function f({label:e,value:t,detail:i}){return n.jsxs("span",{className:"cache-delta-metric",children:[n.jsx("small",{children:e}),n.jsx("strong",{children:t}),n.jsx("em",{children:i})]})}function p(e){return e===0?"0":`${e>0?"+":"-"}${o(Math.abs(e))}`}function M(e){return e===0?"0.0pp":`${e>0?"+":"-"}${Math.abs(e).toFixed(1)}pp`}function te(e,t){return T(e)-T(t)}function T(e){const t=Date.parse(e.rawTime||e.time);return Number.isFinite(t)?t:0}function _e({call:e}){const t=G(),i=ne(e);return n.jsxs("div",{className:"call-decision-card",children:[n.jsxs("div",{className:"section-heading compact",children:[n.jsx("h3",{children:"Call Decision"}),n.jsx("span",{children:i.nextAction})]}),n.jsxs("dl",{className:"detail-list compact",children:[n.jsxs("div",{children:[n.jsx("dt",{children:"Pricing status"}),n.jsx("dd",{children:i.pricingStatus})]}),n.jsxs("div",{children:[n.jsx("dt",{children:t.t("detail.next_action","Next action")}),n.jsx("dd",{children:i.nextAction})]}),n.jsxs("div",{children:[n.jsx("dt",{children:"Why flagged"}),n.jsx("dd",{children:i.whyFlagged})]}),n.jsxs("div",{children:[n.jsx("dt",{children:"Allowance impact"}),n.jsx("dd",{children:i.allowanceImpact})]}),n.jsxs("div",{children:[n.jsx("dt",{children:"Context use"}),n.jsx("dd",{children:i.contextUse})]})]})]})}function ne(e){return{allowanceImpact:`${se(e.credits)} counted`,contextUse:e.contextWindowPct==null?"Not reported":x(e.contextWindowPct),nextAction:re(e),pricingStatus:ie(e),whyFlagged:ae(e)}}function ie(e){return e.cost<=0?"No configured price":e.pricingEstimated?"Best-guess estimate":"Configured price"}function re(e){return e.recommendation?e.recommendation:e.cost<=0?"Configure pricing":e.cachedPct<30&&e.input>0?"Compare fresh input":(e.contextWindowPct??0)>=60?"Inspect thread timeline":e.reasoningOutput>e.output?"Review reasoning effort":"Use aggregate first"}function ae(e){return e.recommendation?e.recommendation:e.signal&&e.signal!=="aggregate"?`${e.signal} aggregate signal`:(e.contextWindowPct??0)>=60?"High reported context use.":e.cachedPct<30&&e.input>0?`${w(e.uncachedInput)} uncached input with weak cache reuse.`:e.reasoningOutput>e.output?"Reasoning output exceeds visible output.":e.cost>0||e.credits>0?"Review cost and credit impact before loading raw context.":"No aggregate efficiency flag on this row."}function se(e){return`${new Intl.NumberFormat("en-US",{maximumFractionDigits:2}).format(e)} credits`}function we({call:e}){return n.jsxs("div",{className:"call-source-card composition-card",children:[n.jsxs("div",{className:"composition-head",children:[n.jsx("strong",{children:"Call Source"}),n.jsx("span",{children:$(e)})]}),n.jsxs("dl",{className:"detail-list compact",children:[n.jsx(d,{label:"Project",value:e.project||"Unknown"}),n.jsx(d,{label:"Project path",value:e.projectRelativeCwd||e.cwd||"."}),n.jsx(d,{label:"Project tags",value:e.projectTags.length?e.projectTags.join(", "):"None"}),n.jsx(d,{label:"Thread attachment",value:e.threadAttachmentLabel||"Direct thread"}),n.jsx(d,{label:"Thread source",value:e.threadSource||"user"}),n.jsx(d,{label:"Subagent type",value:e.subagentType||"None"}),n.jsx(d,{label:"Agent role",value:e.agentRole||"None"}),n.jsx(d,{label:"Agent nickname",value:e.agentNickname||"None"}),n.jsx(d,{label:"Source line",value:$(e)}),n.jsx(d,{label:"Initiated by",value:e.initiator||"unknown"}),n.jsx(d,{label:"Initiator reason",value:e.initiatorReason||"Not reported"}),n.jsx(d,{label:"Session",value:e.sessionId||"Not available"}),n.jsx(d,{label:"Turn",value:e.turnId||"None"}),n.jsx(d,{label:"Parent thread",value:e.parentThread||"None"}),n.jsx(d,{label:"Parent session",value:e.parentSessionId||"None"}),n.jsx(d,{label:"Parent updated",value:oe(e.parentSessionUpdatedAt)}),n.jsx(d,{label:"Working directory",value:e.cwd||"Not available"}),n.jsx(d,{label:"Git branch",value:e.gitBranch||"Not available"}),n.jsx(d,{label:"Git remote",value:e.gitRemoteLabel||"Not available"}),n.jsx(d,{label:"Git hash",value:e.gitRemoteHash||"Not available"}),n.jsx(d,{label:"Credit note",value:e.usageCreditNote||"None"})]})]})}function oe(e){if(!e)return"None";const t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString([],{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}function d({label:e,value:t}){return n.jsxs("div",{children:[n.jsx("dt",{children:e}),n.jsx("dd",{children:t})]})}function ke({call:e}){return n.jsxs("dl",{className:"detail-list",children:[n.jsx(u,{label:"Last call total",value:o(e.totalTokens)}),n.jsx(u,{label:"Last call input",value:o(e.input)}),n.jsx(u,{label:"Cached input",value:o(e.cachedInput)}),n.jsx(u,{label:"Output tokens",value:o(e.output)}),n.jsx(u,{label:"Reasoning output",value:o(e.reasoningOutput)}),n.jsx(u,{label:"Session cumulative",value:e.cumulativeTotalTokens?o(e.cumulativeTotalTokens):"Not reported"}),n.jsx(u,{label:"Pricing model",value:ce(e)}),n.jsx(u,{label:"Credit model",value:e.usageCreditModel||"No mapped rate"}),n.jsx(u,{label:"Credit confidence",value:e.usageCreditConfidence||"unknown"}),n.jsx(u,{label:"Credit source",value:e.usageCreditSource||"None"}),n.jsx(u,{label:"Credit source fetched",value:e.usageCreditFetchedAt||"Unknown"}),n.jsx(u,{label:"Credit tier",value:e.usageCreditTier||"Unknown"}),n.jsx(u,{label:"Cache savings",value:q(e.estimatedCacheSavings)}),n.jsx(u,{label:"Efficiency signals",value:de(e)})]})}function u({label:e,value:t}){return n.jsxs("div",{children:[n.jsx("dt",{children:e}),n.jsx("dd",{children:t})]})}function ce(e){return e.pricingModel?e.pricingModel:e.cost<=0?"No configured price":e.pricingEstimated?"Best-guess estimate":"Configured price"}function de(e){return e.efficiencyFlags.length?e.efficiencyFlags.join(", "):e.signal&&e.signal!=="aggregate"?e.signal:e.recommendation?"recommendation":"None"}function $e({selectedCall:e,calls:t,onOpenInvestigator:i,onCopyCallLink:a,className:r,copyAriaContext:s,copyLabel:l="Copy link"}){const h=t.length?t:[e],m=le(h,e.id),b=["thread-mini-timeline",r].filter(Boolean).join(" ");return n.jsx("ol",{className:b,children:m.map(c=>n.jsxs("li",{className:c.id===e.id?"active":"",children:[n.jsx("span",{children:c.time}),n.jsxs("strong",{children:[c.model," / ",c.effort]}),n.jsxs("em",{children:[w(c.totalTokens)," tokens - ",x(c.cachedPct)," cache"]}),n.jsxs("div",{className:"thread-call-meta",children:[n.jsx("span",{children:c.initiator||"unknown"}),n.jsx("span",{children:c.duration}),n.jsxs("span",{children:["prev ",c.previousCallGap]})]}),n.jsxs("div",{className:"thread-call-flags",children:[n.jsxs("span",{children:["Context ",ue(c)]}),n.jsx("span",{children:me(c)})]}),c.recommendation?n.jsx("p",{className:"thread-call-recommendation",children:c.recommendation}):null,n.jsx("div",{className:"thread-context-bar",title:`Context window ${he(c)}`,children:n.jsx("span",{className:pe(c.contextWindowPct),style:{width:xe(c.contextWindowPct)}})}),n.jsxs("div",{className:"thread-call-actions table-action-group",children:[n.jsx("button",{className:"table-action-button",type:"button",onClick:()=>i(c.id),disabled:c.id===e.id,children:"Open"}),n.jsxs("button",{className:"table-action-button",type:"button","aria-label":`${l} for ${s} ${c.thread} ${c.model}`,onClick:()=>a(c.id),children:[n.jsx(J,{size:14})," ",l]})]})]},c.id))})}function le(e,t){if(e.length<=5)return e;const i=e.findIndex(r=>r.id===t);if(i<0)return e.slice(0,5);const a=Math.max(0,Math.min(i-2,e.length-5));return e.slice(a,a+5)}function ue(e){return e.contextWindowPct===null?"Not reported":x(e.contextWindowPct)}function he(e){if(e.contextWindowPct===null)return"Not reported";const t=e.modelContextWindow?` of ${w(e.modelContextWindow)}`:"";return`${x(e.contextWindowPct)}${t}`}function me(e){return e.cost<=0?"No configured price":e.pricingEstimated?"Best-guess estimate":"Configured price"}function pe(e){const t=Number(e??0);return t>=75?"high":t>=50?"medium":"low"}function xe(e){const t=Number(e??0),i=Math.max(0,Math.min(100,Number.isFinite(t)?t:0));return`${Math.round(i)}%`}async function Se(e){I(e);const t=new URLSearchParams({enabled:"1",_:String(Date.now())}),i=await fetch(`/api/context-settings?${t.toString()}`,{headers:A(e),cache:"no-store"});return!!(await O(i,"Context settings")).context_api_enabled}async function Te(e,t,i){if(I(t),!t.contextApiEnabled)throw new Error("Context API is not enabled.");const a=new URLSearchParams({record_id:e,mode:i.mode,include_tool_output:i.includeToolOutput?"1":"0",include_compaction_history:i.includeCompactionHistory?"1":"0",max_chars:String(i.maxChars),max_entries:String(i.maxEntries),_:String(Date.now())});return await fetch(`/api/context?${a.toString()}`,{headers:A(t),cache:"no-store"}).then(s=>O(s,"Context"))}function I(e){if(e.fileMode)throw new Error("Context loading requires the localhost dashboard server.");if(!e.apiToken)throw new Error("Context loading requires a localhost dashboard API token.")}function A(e){return{Accept:"application/json","X-Codex-Usage-Token":e.apiToken}}async function O(e,t){let i={};try{i=await e.json()}catch{i={}}if(!e.ok){const a=typeof i.error=="string"?i.error:`${t} API returned HTTP ${e.status}.`;throw new Error(a)}if(typeof i.error=="string")throw new Error(i.error);return i}function ze({call:e,payload:t,onRunFullAnalysis:i,showHeading:a=!0}){const r=be(e,t);return n.jsxs("div",{className:"context-attribution-module",children:[a?n.jsxs("div",{className:"serialized-breakdown-heading context-attribution-heading",children:[n.jsx("strong",{children:"Context Attribution"}),n.jsx("span",{children:"Estimated from visible log volume"})]}):null,n.jsxs("div",{className:"drilldown-metric-grid wide context-attribution-grid",children:[n.jsx(g,{label:"Uncached input",value:o(e.uncachedInput),detail:"exact aggregate row"}),n.jsx(g,{label:"Visible new context estimate",value:r?`~${o(r.visibleTokenEstimate)}`:"Not loaded yet",detail:r?`${o(r.visibleChars)} analyzed chars`:"Runtime evidence"}),n.jsx(g,{label:"Serialized local upper bound",value:r!=null&&r.serializedTokens?`~${o(r.serializedBound)}`:"Not loaded yet",detail:r!=null&&r.serializedTokens?ge(r):"Runtime evidence"}),n.jsx(g,{label:"Unexplained hidden/serialized input estimate",value:r?`~${o(r.visibleGap)}`:"Not loaded yet",detail:r?"uncached input minus visible estimate":"Runtime evidence"}),n.jsx(g,{label:"Possible serialized overhead",value:r?`~${o(r.serializedCandidate)}`:"Not loaded yet",detail:r?"serialized upper bound minus visible estimate":"Runtime evidence"}),n.jsx(g,{label:"Remaining after serialized bound",value:r?`~${o(r.remainingAfterSerialized)}`:"Not loaded yet",detail:r?"not covered by serialized upper bound":"Runtime evidence"})]}),n.jsx(fe,{stats:r,onRunFullAnalysis:(t==null?void 0:t.context_mode)==="full"?void 0:i}),n.jsx("p",{className:"privacy-note",children:"Compare exact uncached input with tokenizer-counted visible log evidence. Treat the gap as hidden scaffolding, serialization, or tokenizer estimate error."})]})}function g({label:e,value:t,detail:i}){return n.jsxs("span",{className:"drilldown-metric",children:[n.jsx("small",{children:e}),n.jsx("strong",{children:t}),n.jsx("em",{children:i})]})}function fe({stats:e,onRunFullAnalysis:t}){if(!(e!=null&&e.serializedTokens))return null;if(e.serializedDeferred)return n.jsxs("div",{className:"serialized-breakdown deferred",children:[n.jsxs("div",{className:"serialized-breakdown-heading",children:[n.jsx("strong",{children:"Serialized evidence groups"}),n.jsx("span",{children:"Fast estimate loaded; full serialized grouping is deferred."})]}),t?n.jsx("div",{className:"context-followup-actions",children:n.jsx("button",{className:"toolbar-button serialized-action",type:"button",onClick:t,children:"Run full serialized analysis"})}):null]});const i=e.serializedBuckets.slice(0,6);return n.jsxs("div",{className:"serialized-breakdown",children:[n.jsxs("div",{className:"serialized-breakdown-heading",children:[n.jsx("strong",{children:"Serialized evidence groups"}),n.jsx("span",{children:"Upper-bound local JSONL structure; not exact prompt text."})]}),n.jsx("div",{className:"serialized-bucket-grid",children:i.length?i.map(a=>n.jsxs("div",{className:"serialized-bucket",children:[n.jsx("span",{children:a.label||a.key||"Unknown"}),n.jsx("strong",{children:o(Number(a.token_estimate??0))}),n.jsxs("small",{children:[o(Number(a.count??0))," fields · ",o(Number(a.char_count??0))," chars"]}),a.note?n.jsx("small",{children:a.note}):null]},a.key??a.label??String(a.token_estimate??0))):n.jsx("p",{className:"empty-state",children:"No serialized evidence groups returned."})})]})}function ge(e){const t=[`${o(e.serializedChars)} raw JSON chars`,e.serializedEstimator];return e.serializedDeferred&&t.push("fast estimate"),e.serializedLineCount&&t.push(`${o(e.serializedLineCount)} raw lines`),t.join(" · ")}function be(e,t){if(!t)return null;const i=t.entries??[],a=Number(t.visible_char_count??i.reduce((B,H)=>B+String(H.text??"").length,0)),r=Number(t.visible_token_estimate??Math.ceil(a/4)),s=t.serialized_evidence??{},l=Number(s.raw_json_token_estimate??s.token_estimate??0),h=Number(s.raw_json_char_count??s.total_chars??0),m=Number(s.raw_line_count??0),b=s.token_estimator||t.visible_token_estimator||"chars_per_4_fallback",c=Array.isArray(s.buckets)?s.buckets:[],v=l>0?Math.min(l,e.uncachedInput):0,k=Math.max(e.uncachedInput-r,0),F=v>r?v-r:0,W=l>0?Math.max(e.uncachedInput-Math.max(r,v),0):k;return{visibleChars:a,visibleTokenEstimate:r,serializedTokens:l,serializedChars:h,serializedLineCount:m,serializedEstimator:b,serializedBound:v,visibleGap:k,serializedCandidate:F,remainingAfterSerialized:W,serializedDeferred:!!(s.deferred||s.deferred_buckets),serializedBuckets:c}}function Pe({entry:e}){const t=je(e);return t.length?n.jsx("div",{className:"context-entry-chips","aria-label":"Evidence entry metadata",children:t.map(i=>n.jsxs("span",{title:i.title,children:[i.label,": ",i.value]},`${i.label}-${i.value}`))}):null}function je(e){var s,l,h,m;const t=[],i=e.action_timing;(i==null?void 0:i.since_turn_start_ms)!==void 0&&t.push({label:"T+",value:N(i.since_turn_start_ms),title:"Elapsed since selected turn start"}),(i==null?void 0:i.since_previous_entry_ms)!==void 0&&t.push({label:"Gap",value:N(i.since_previous_entry_ms),title:"Gap since previous evidence entry"}),(i==null?void 0:i.reported_duration_ms)!==void 0&&t.push({label:"Duration",value:N(i.reported_duration_ms),title:i.duration_source||"Duration reported by this event"});const a=(s=e.token_usage)==null?void 0:s.last_token_usage;a&&t.push({label:"Entry tokens",value:z(a),title:"Token usage reported for this evidence entry"});const r=(l=e.token_usage)==null?void 0:l.total_token_usage;return r&&t.push({label:"Session tokens",value:z(r),title:"Cumulative token usage reported by this evidence entry"}),(h=e.compaction)!=null&&h.replacement_history_available&&t.push({label:"Compaction",value:`${o(((m=e.compaction.replacement_history)==null?void 0:m.length)??0)} replacement entries`,title:"Compaction replacement history is available for this entry"}),e.tool_output_omitted&&t.push({label:"Tool output",value:"omitted",title:"Tool output omitted until explicitly requested"}),t}function z(e){const t=Number((e==null?void 0:e.input_tokens)??0),i=Number((e==null?void 0:e.cached_input_tokens)??0),a=Number((e==null?void 0:e.uncached_input_tokens)??Math.max(t-i,0)),r=Number((e==null?void 0:e.output_tokens)??0),s=Number((e==null?void 0:e.total_tokens)??t+r);return`${o(s)} total · ${o(a)} uncached`}function N(e){if(!Number.isFinite(e))return"0ms";if(e<1e3)return`${Math.round(e)}ms`;const t=e/1e3;return t>=10?`${t.toFixed(0)}s`:`${t.toFixed(1)}s`}const R=new Map,U=new Map,y=new Map,_=new Map,D=new Map;function Ee(e,t){return R.get(L(e,t))??null}function Me(e,t,i){R.set(L(e,t),i)}function Ie(e){const t=U.get(e);return t?{...t}:null}function Ae(e,t){U.set(e,{...t})}function ve(e,t){return`${e.type??"entry"}-${e.line_number??e.timestamp??t}`}function Oe(e,t){const i=y.get(e);return i?new Set(i):t.length?new Set([ve(t[0],0)]):new Set}function Re(e,t,i){const a=y.get(e)??new Set;i?a.add(t):a.delete(t),y.set(e,a)}function Ue(e,t){var i;return((i=_.get(e))==null?void 0:i.get(t))??0}function De(e,t,i){const a=_.get(e)??new Map;i>0?a.set(t,i):a.delete(t),_.set(e,a)}function Le(e){return D.get(e)??!1}function Fe(e,t){D.set(e,t)}function L(e,t){return[e,t.mode,t.includeToolOutput?"tool-output":"no-tool-output",t.includeCompactionHistory?"compaction-history":"no-compaction-history",String(t.maxChars),String(t.maxEntries)].join("|")}const C={includeToolOutput:!1,includeCompactionHistory:!1,maxChars:8e3,maxEntries:20,mode:"quick"};function We(e=window.location.search,t=C){const i=new URLSearchParams(e);return{includeToolOutput:P(i.get("include_tool_output"),t.includeToolOutput),includeCompactionHistory:P(i.get("include_compaction_history"),t.includeCompactionHistory),maxChars:E(i.get("max_chars"),t.maxChars),maxEntries:E(i.get("max_entries"),t.maxEntries),mode:i.get("mode")==="full"?"full":t.mode}}function Be(e,t,i=C){j(e,"mode",t.mode,i.mode),j(e,"max_entries",String(t.maxEntries),String(i.maxEntries)),j(e,"max_chars",String(t.maxChars),String(i.maxChars)),j(e,"include_tool_output",t.includeToolOutput?"1":"0",i.includeToolOutput?"1":"0"),j(e,"include_compaction_history",t.includeCompactionHistory?"1":"0",i.includeCompactionHistory?"1":"0")}function He(e){return e.fileMode?"Static file mode cannot read local JSONL context. Use serve-dashboard with the context API enabled.":e.apiToken?e.contextApiEnabled?"Context API is enabled. Load selected-turn evidence from the local JSONL source only when needed.":"Context API is available but off. Enable it here before loading selected-turn evidence.":"Context loading requires the localhost dashboard server API token."}function P(e,t){return e==="1"||e==="true"?!0:e==="0"||e==="false"?!1:t}function E(e,t){if(e===null||e.trim()==="")return t;const i=Number(e);return Number.isFinite(i)&&i>=0?Math.floor(i):t}function j(e,t,i,a){i===a?e.searchParams.delete(t):e.searchParams.set(t,i)}function Ge(e){return e instanceof Error?e.message:String(e)}function qe(e,t){var s;const i=Number(((s=e.omitted)==null?void 0:s.max_entries)??t.maxEntries??C.maxEntries),a=C.maxEntries||20,r=i>0?Math.max(i+a,i*2):0;return{...t,maxEntries:r}}function Je(e){const t=e.omitted??{},i=e.source??{},a=["Local JSONL context loaded on demand.",e.include_tool_output?"Tool output included with redaction and size limits.":"Tool output hidden for this view."];i.file&&a.push(`Source: ${i.file}${i.line_number?`:${i.line_number}`:""}.`);const r=Number(t.older_entries??0);r>0&&a.push(`${o(r)} older entries omitted.`);const s=Number(t.over_budget_chars??0);return s>0&&a.push(`${o(s)} chars over budget omitted.`),Number(t.max_chars??NaN)===0&&a.push("No character limit applied."),a}export{ze as C,ke as T,Ee as a,He as b,Ie as c,C as d,Se as e,Ge as f,Me as g,Je as h,Le as i,Oe as j,ve as k,Te as l,Pe as m,De as n,Ue as o,qe as p,Re as q,Ae as r,Fe as s,_e as t,we as u,ye as v,$e as w,We as x,Be as y}; diff --git a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/dashboardRouter.js b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/dashboardRouter.js index 71b8574a..1d729ec3 100644 --- a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/dashboardRouter.js +++ b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/dashboardRouter.js @@ -1,2 +1,2 @@ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/App.js","assets/dashboard-react.js","assets/index.css","assets/locale-zh-Hans.js","assets/router.js"])))=>i.map(i=>d[i]); -import{j as o,_ as u}from"./dashboard-react.js";import{Y as h,$ as f,T as g,a9 as b}from"./router.js";const p=["overview","investigator","compression-lab","calls","call","threads","usage-drain","cache-context","diagnostics","reports","settings"],m=new Set(p);function y(e){return typeof e=="string"&&m.has(e)}function d(e,r="overview"){const t=s(e);return t==="insights"?"overview":y(t)?t:r}function R(e){const r={...e,view:d(e.view)};i(r,"record",e.record),i(r,"q",e.q),i(r,"preset",e.preset);const t=d(e.return,"calls");s(e.return)&&t!=="call"?r.return=t:delete r.return;const n=s(e.history);n==="active"||n==="all"?r.history=n:delete r.history;const a=w(e.finding);return a!==null&&a>0?r.finding=a:delete r.finding,r}function i(e,r,t){const n=s(t);n?e[r]=n:delete e[r]}function s(e){return Array.isArray(e)?s(e[0]):typeof e=="string"?e.trim():""}function w(e){const r=Array.isArray(e)?e[0]:e,t=typeof r=="number"?r:Number.parseInt(s(r),10);return Number.isFinite(t)?Math.trunc(t):null}const _=b(()=>u(()=>import("./App.js").then(e=>e.ag),__vite__mapDeps([0,1,2,3,4])),"RoutedApp");function c(e={}){const r=h({validateSearch:R,component:_,pendingComponent:x,errorComponent:v});return f({routeTree:r,history:e.history??g(),basepath:e.basepath??l(),defaultPreload:"intent",defaultPendingMs:120,defaultPendingMinMs:180,scrollRestoration:!0,search:{strict:!1}})}const j=c();function l(){return"/"}function x(){return o.jsxs("main",{"aria-busy":"true","aria-live":"polite",className:"route-state",role:"status",children:[o.jsx("strong",{children:"Loading dashboard"}),o.jsx("span",{children:"Restoring the local usage workspace..."})]})}function v({error:e,reset:r}){return o.jsxs("main",{className:"route-state",role:"alert",children:[o.jsx("strong",{children:"Dashboard route could not load"}),o.jsx("span",{children:e instanceof Error?e.message:String(e)}),o.jsx("button",{type:"button",onClick:r,children:"Retry"})]})}const A=Object.freeze(Object.defineProperty({__proto__:null,createDashboardRouter:c,dashboardBasepath:l,dashboardRouter:j},Symbol.toStringTag,{value:"Module"}));export{A as d,y as i}; +import{j as o,_ as u}from"./dashboard-react.js";import{Y as h,$ as f,T as g,a9 as b}from"./router.js";const m=["overview","investigator","compression-lab","calls","call","threads","usage-drain","cache-context","diagnostics","reports","settings"],p=new Set(m);function y(e){return typeof e=="string"&&p.has(e)}function d(e,r="overview"){const t=s(e);return t==="insights"?"overview":y(t)?t:r}function R(e){const r={...e,view:d(e.view)};i(r,"record",e.record),i(r,"q",e.q),i(r,"preset",e.preset);const t=d(e.return,"calls");s(e.return)&&t!=="call"?r.return=t:delete r.return;const n=s(e.history);n==="active"||n==="all"?r.history=n:delete r.history;const a=w(e.finding);return a!==null&&a>0?r.finding=a:delete r.finding,r}function i(e,r,t){const n=s(t);n?e[r]=n:delete e[r]}function s(e){return Array.isArray(e)?s(e[0]):typeof e=="string"?e.trim():""}function w(e){const r=Array.isArray(e)?e[0]:e,t=typeof r=="number"?r:Number.parseInt(s(r),10);return Number.isFinite(t)?Math.trunc(t):null}const _=b(()=>u(()=>import("./App.js").then(e=>e.am),__vite__mapDeps([0,1,2,3,4])),"RoutedApp");function c(e={}){const r=h({validateSearch:R,component:_,pendingComponent:x,errorComponent:v});return f({routeTree:r,history:e.history??g(),basepath:e.basepath??l(),defaultPreload:"intent",defaultPendingMs:120,defaultPendingMinMs:180,scrollRestoration:!0,search:{strict:!1}})}const j=c();function l(){return"/"}function x(){return o.jsxs("main",{"aria-busy":"true","aria-live":"polite",className:"route-state",role:"status",children:[o.jsx("strong",{children:"Loading dashboard"}),o.jsx("span",{children:"Restoring the local usage workspace..."})]})}function v({error:e,reset:r}){return o.jsxs("main",{className:"route-state",role:"alert",children:[o.jsx("strong",{children:"Dashboard route could not load"}),o.jsx("span",{children:e instanceof Error?e.message:String(e)}),o.jsx("button",{type:"button",onClick:r,children:"Retry"})]})}const A=Object.freeze(Object.defineProperty({__proto__:null,createDashboardRouter:c,dashboardBasepath:l,dashboardRouter:j},Symbol.toStringTag,{value:"Module"}));export{A as d,y as i}; diff --git a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/useBaseQuery.js b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/useBaseQuery.js index e0ae489b..901b4dc6 100644 --- a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/useBaseQuery.js +++ b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/useBaseQuery.js @@ -1 +1 @@ -var bt=s=>{throw TypeError(s)};var $=(s,t,e)=>t.has(s)||bt("Cannot "+e);var i=(s,t,e)=>($(s,t,"read from private field"),e?e.call(s):t.get(s)),p=(s,t,e)=>t.has(s)?bt("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(s):t.set(s,e),l=(s,t,e,r)=>($(s,t,"write to private field"),r?r.call(s,e):t.set(s,e),e),d=(s,t,e)=>($(s,t,"access private method"),e);import{J as Tt,a1 as gt,a2 as R,L as q,a3 as W,N as tt,a4 as et,a5 as mt,a6 as Mt,a7 as X,a8 as Qt,a9 as xt,aa as vt,K as Ct,ab as Ot,l as _t}from"./App.js";import{r as C}from"./dashboard-react.js";var m,a,z,g,x,U,E,w,V,D,P,_,F,T,L,n,H,st,it,rt,at,nt,ht,ot,It,Et,Gt=(Et=class extends Tt{constructor(t,e){super();p(this,n);p(this,m);p(this,a);p(this,z);p(this,g);p(this,x);p(this,U);p(this,E);p(this,w);p(this,V);p(this,D);p(this,P);p(this,_);p(this,F);p(this,T);p(this,L,new Set);this.options=e,l(this,m,t),l(this,w,null),l(this,E,gt()),this.bindMethods(),this.setOptions(e)}bindMethods(){this.refetch=this.refetch.bind(this)}onSubscribe(){this.listeners.size===1&&(i(this,a).addObserver(this),Rt(i(this,a),this.options)?d(this,n,H).call(this):this.updateResult(),d(this,n,at).call(this))}onUnsubscribe(){this.hasListeners()||this.destroy()}shouldFetchOnReconnect(){return ct(i(this,a),this.options,this.options.refetchOnReconnect)}shouldFetchOnWindowFocus(){return ct(i(this,a),this.options,this.options.refetchOnWindowFocus)}destroy(){this.listeners=new Set,d(this,n,nt).call(this),d(this,n,ht).call(this),i(this,a).removeObserver(this)}setOptions(t){const e=this.options,r=i(this,a);if(this.options=i(this,m).defaultQueryOptions(t),this.options.enabled!==void 0&&typeof this.options.enabled!="boolean"&&typeof this.options.enabled!="function"&&typeof R(this.options.enabled,i(this,a))!="boolean")throw new Error("Expected enabled to be a boolean or a callback that returns a boolean");d(this,n,ot).call(this),i(this,a).setOptions(this.options),e._defaulted&&!q(this.options,e)&&i(this,m).getQueryCache().notify({type:"observerOptionsUpdated",query:i(this,a),observer:this});const h=this.hasListeners();h&&St(i(this,a),r,this.options,e)&&d(this,n,H).call(this),this.updateResult(),h&&(i(this,a)!==r||R(this.options.enabled,i(this,a))!==R(e.enabled,i(this,a))||W(this.options.staleTime,i(this,a))!==W(e.staleTime,i(this,a)))&&d(this,n,st).call(this);const o=d(this,n,it).call(this);h&&(i(this,a)!==r||R(this.options.enabled,i(this,a))!==R(e.enabled,i(this,a))||o!==i(this,T))&&d(this,n,rt).call(this,o)}getOptimisticResult(t){const e=i(this,m).getQueryCache().build(i(this,m),t),r=this.createResult(e,t);return Ut(this,r)&&(l(this,g,r),l(this,U,this.options),l(this,x,i(this,a).state)),r}getCurrentResult(){return i(this,g)}trackResult(t,e){return new Proxy(t,{get:(r,h)=>(this.trackProp(h),e==null||e(h),h==="promise"&&(this.trackProp("data"),!this.options.experimental_prefetchInRender&&i(this,E).status==="pending"&&i(this,E).reject(new Error("experimental_prefetchInRender feature flag is not enabled"))),Reflect.get(r,h))})}trackProp(t){i(this,L).add(t)}getCurrentQuery(){return i(this,a)}refetch({...t}={}){return this.fetch({...t})}fetchOptimistic(t){const e=i(this,m).defaultQueryOptions(t),r=i(this,m).getQueryCache().build(i(this,m),e);return r.fetch().then(()=>this.createResult(r,e))}fetch(t){return d(this,n,H).call(this,{...t,cancelRefetch:t.cancelRefetch??!0}).then(()=>(this.updateResult(),i(this,g)))}createResult(t,e){var ft;const r=i(this,a),h=this.options,o=i(this,g),c=i(this,x),S=i(this,U),N=t!==r?t.state:i(this,z),{state:b}=t;let u={...b},B=!1,f;if(e._optimisticResults){const v=this.hasListeners(),j=!v&&Rt(t,e),K=v&&St(t,r,e,h);(j||K)&&(u={...u,...xt(b.data,t.options)}),e._optimisticResults==="isRestoring"&&(u.fetchStatus="idle")}let{error:k,errorUpdatedAt:Q,status:y}=u;f=u.data;let O=!1;if(e.placeholderData!==void 0&&f===void 0&&y==="pending"){let v;o!=null&&o.isPlaceholderData&&e.placeholderData===(S==null?void 0:S.placeholderData)?(v=o.data,O=!0):v=typeof e.placeholderData=="function"?e.placeholderData((ft=i(this,P))==null?void 0:ft.state.data,i(this,P)):e.placeholderData,v!==void 0&&(y="success",f=vt(o==null?void 0:o.data,v,e),B=!0)}if(e.select&&f!==void 0&&!O)if(o&&f===(c==null?void 0:c.data)&&e.select===i(this,V))f=i(this,D);else try{l(this,V,e.select),f=e.select(f),f=vt(o==null?void 0:o.data,f,e),l(this,D,f),l(this,w,null)}catch(v){l(this,w,v)}i(this,w)&&(k=i(this,w),f=i(this,D),Q=Date.now(),y="error");const A=u.fetchStatus==="fetching",Y=y==="pending",Z=y==="error",ut=Y&&A,dt=f!==void 0,I={status:y,fetchStatus:u.fetchStatus,isPending:Y,isSuccess:y==="success",isError:Z,isInitialLoading:ut,isLoading:ut,data:f,dataUpdatedAt:u.dataUpdatedAt,error:k,errorUpdatedAt:Q,failureCount:u.fetchFailureCount,failureReason:u.fetchFailureReason,errorUpdateCount:u.errorUpdateCount,isFetched:t.isFetched(),isFetchedAfterMount:u.dataUpdateCount>N.dataUpdateCount||u.errorUpdateCount>N.errorUpdateCount,isFetching:A,isRefetching:A&&!Y,isLoadingError:Z&&!dt,isPaused:u.fetchStatus==="paused",isPlaceholderData:B,isRefetchError:Z&&dt,isStale:lt(t,e),refetch:this.refetch,promise:i(this,E),isEnabled:R(e.enabled,t)!==!1};if(this.options.experimental_prefetchInRender){const v=I.data!==void 0,j=I.status==="error"&&!v,K=G=>{j?G.reject(I.error):v&&G.resolve(I.data)},pt=()=>{const G=l(this,E,I.promise=gt());K(G)},J=i(this,E);switch(J.status){case"pending":t.queryHash===r.queryHash&&K(J);break;case"fulfilled":(j||I.data!==J.value)&&pt();break;case"rejected":(!j||I.error!==J.reason)&&pt();break}}return I}updateResult(){const t=i(this,g),e=this.createResult(i(this,a),this.options);if(l(this,x,i(this,a).state),l(this,U,this.options),i(this,x).data!==void 0&&l(this,P,i(this,a)),q(e,t))return;l(this,g,e);const r=()=>{if(!t)return!0;const{notifyOnChangeProps:h}=this.options,o=typeof h=="function"?h():h;if(o==="all"||!o&&!i(this,L).size)return!0;const c=new Set(o??i(this,L));return this.options.throwOnError&&c.add("error"),Object.keys(i(this,g)).some(S=>{const M=S;return i(this,g)[M]!==t[M]&&c.has(M)})};d(this,n,It).call(this,{listeners:r()})}onQueryUpdate(){this.updateResult(),this.hasListeners()&&d(this,n,at).call(this)}},m=new WeakMap,a=new WeakMap,z=new WeakMap,g=new WeakMap,x=new WeakMap,U=new WeakMap,E=new WeakMap,w=new WeakMap,V=new WeakMap,D=new WeakMap,P=new WeakMap,_=new WeakMap,F=new WeakMap,T=new WeakMap,L=new WeakMap,n=new WeakSet,H=function(t){d(this,n,ot).call(this);let e=i(this,a).fetch(this.options,t);return t!=null&&t.throwOnError||(e=e.catch(tt)),e},st=function(){d(this,n,nt).call(this);const t=W(this.options.staleTime,i(this,a));if(et.isServer()||i(this,g).isStale||!mt(t))return;const r=Mt(i(this,g).dataUpdatedAt,t)+1;l(this,_,X.setTimeout(()=>{i(this,g).isStale||this.updateResult()},r))},it=function(){return(typeof this.options.refetchInterval=="function"?this.options.refetchInterval(i(this,a)):this.options.refetchInterval)??!1},rt=function(t){d(this,n,ht).call(this),l(this,T,t),!(et.isServer()||R(this.options.enabled,i(this,a))===!1||!mt(i(this,T))||i(this,T)===0)&&l(this,F,X.setInterval(()=>{(this.options.refetchIntervalInBackground||Qt.isFocused())&&d(this,n,H).call(this)},i(this,T)))},at=function(){d(this,n,st).call(this),d(this,n,rt).call(this,d(this,n,it).call(this))},nt=function(){i(this,_)!==void 0&&(X.clearTimeout(i(this,_)),l(this,_,void 0))},ht=function(){i(this,F)!==void 0&&(X.clearInterval(i(this,F)),l(this,F,void 0))},ot=function(){const t=i(this,m).getQueryCache().build(i(this,m),this.options);if(t===i(this,a))return;const e=i(this,a);l(this,a,t),l(this,z,t.state),this.hasListeners()&&(e==null||e.removeObserver(this),t.addObserver(this))},It=function(t){Ct.batch(()=>{t.listeners&&this.listeners.forEach(e=>{e(i(this,g))}),i(this,m).getQueryCache().notify({query:i(this,a),type:"observerResultsUpdated"})})},Et);function Ft(s,t){return R(t.enabled,s)!==!1&&s.state.data===void 0&&!(s.state.status==="error"&&R(t.retryOnMount,s)===!1)}function Rt(s,t){return Ft(s,t)||s.state.data!==void 0&&ct(s,t,t.refetchOnMount)}function ct(s,t,e){if(R(t.enabled,s)!==!1&&W(t.staleTime,s)!=="static"){const r=typeof e=="function"?e(s):e;return r==="always"||r!==!1&<(s,t)}return!1}function St(s,t,e,r){return(s!==t||R(r.enabled,s)===!1)&&(!e.suspense||s.state.status!=="error")&<(s,e)}function lt(s,t){return R(t.enabled,s)!==!1&&s.isStaleByTime(W(t.staleTime,s))}function Ut(s,t){return!q(s.getCurrentResult(),t)}var wt=C.createContext(!1),Dt=()=>C.useContext(wt);wt.Provider;function Pt(){let s=!1;return{clearReset:()=>{s=!1},reset:()=>{s=!0},isReset:()=>s}}var Lt=C.createContext(Pt()),Nt=()=>C.useContext(Lt),Bt=(s,t,e)=>{const r=e!=null&&e.state.error&&typeof s.throwOnError=="function"?Ot(s.throwOnError,[e.state.error,e]):s.throwOnError;(s.suspense||s.experimental_prefetchInRender||r)&&(t.isReset()||(s.retryOnMount=!1))},kt=s=>{C.useEffect(()=>{s.clearReset()},[s])},At=({result:s,errorResetBoundary:t,throwOnError:e,query:r,suspense:h})=>s.isError&&!t.isReset()&&!s.isFetching&&r&&(h&&s.data===void 0||Ot(e,[s.error,r])),jt=s=>{if(s.suspense){const e=h=>h==="static"?h:Math.max(h??1e3,1e3),r=s.staleTime;s.staleTime=typeof r=="function"?(...h)=>e(r(...h)):e(r),typeof s.gcTime=="number"&&(s.gcTime=Math.max(s.gcTime,1e3))}},Ht=(s,t)=>s.isLoading&&s.isFetching&&!t,Wt=(s,t)=>(s==null?void 0:s.suspense)&&t.isPending,yt=(s,t,e)=>t.fetchOptimistic(s).catch(()=>{e.clearReset()});function Xt(s,t,e){var f,k,Q,y;const r=Dt(),h=Nt(),o=_t(),c=o.defaultQueryOptions(s);(k=(f=o.getDefaultOptions().queries)==null?void 0:f._experimental_beforeQuery)==null||k.call(f,c);const S=o.getQueryCache().get(c.queryHash),M=s.subscribed!==!1;c._optimisticResults=r?"isRestoring":M?"optimistic":void 0,jt(c),Bt(c,h,S),kt(h);const N=!o.getQueryCache().get(c.queryHash),[b]=C.useState(()=>new t(o,c)),u=b.getOptimisticResult(c),B=!r&&M;if(C.useSyncExternalStore(C.useCallback(O=>{const A=B?b.subscribe(Ct.batchCalls(O)):tt;return b.updateResult(),A},[b,B]),()=>b.getCurrentResult(),()=>b.getCurrentResult()),C.useEffect(()=>{b.setOptions(c)},[c,b]),Wt(c,u))throw yt(c,b,h);if(At({result:u,errorResetBoundary:h,throwOnError:c.throwOnError,query:S,suspense:c.suspense}))throw u.error;if((y=(Q=o.getDefaultOptions().queries)==null?void 0:Q._experimental_afterQuery)==null||y.call(Q,c,u),c.experimental_prefetchInRender&&!et.isServer()&&Ht(u,r)){const O=N?yt(c,b,h):S==null?void 0:S.promise;O==null||O.catch(tt).finally(()=>{b.updateResult()})}return c.notifyOnChangeProps?u:b.trackResult(u)}export{Gt as Q,Nt as a,Bt as b,kt as c,Xt as d,jt as e,yt as f,At as g,Wt as s,Dt as u}; +var bt=s=>{throw TypeError(s)};var $=(s,t,e)=>t.has(s)||bt("Cannot "+e);var i=(s,t,e)=>($(s,t,"read from private field"),e?e.call(s):t.get(s)),p=(s,t,e)=>t.has(s)?bt("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(s):t.set(s,e),l=(s,t,e,r)=>($(s,t,"write to private field"),r?r.call(s,e):t.set(s,e),e),d=(s,t,e)=>($(s,t,"access private method"),e);import{J as Tt,a5 as gt,a6 as R,L as q,a7 as W,N as tt,a8 as et,a9 as mt,aa as Mt,ab as X,ac as Qt,ad as xt,ae as vt,K as Ct,af as Ot,l as _t}from"./App.js";import{r as C}from"./dashboard-react.js";var m,a,z,g,x,U,E,w,V,D,P,_,F,T,L,n,H,st,it,rt,at,nt,ht,ot,It,Et,Gt=(Et=class extends Tt{constructor(t,e){super();p(this,n);p(this,m);p(this,a);p(this,z);p(this,g);p(this,x);p(this,U);p(this,E);p(this,w);p(this,V);p(this,D);p(this,P);p(this,_);p(this,F);p(this,T);p(this,L,new Set);this.options=e,l(this,m,t),l(this,w,null),l(this,E,gt()),this.bindMethods(),this.setOptions(e)}bindMethods(){this.refetch=this.refetch.bind(this)}onSubscribe(){this.listeners.size===1&&(i(this,a).addObserver(this),Rt(i(this,a),this.options)?d(this,n,H).call(this):this.updateResult(),d(this,n,at).call(this))}onUnsubscribe(){this.hasListeners()||this.destroy()}shouldFetchOnReconnect(){return ct(i(this,a),this.options,this.options.refetchOnReconnect)}shouldFetchOnWindowFocus(){return ct(i(this,a),this.options,this.options.refetchOnWindowFocus)}destroy(){this.listeners=new Set,d(this,n,nt).call(this),d(this,n,ht).call(this),i(this,a).removeObserver(this)}setOptions(t){const e=this.options,r=i(this,a);if(this.options=i(this,m).defaultQueryOptions(t),this.options.enabled!==void 0&&typeof this.options.enabled!="boolean"&&typeof this.options.enabled!="function"&&typeof R(this.options.enabled,i(this,a))!="boolean")throw new Error("Expected enabled to be a boolean or a callback that returns a boolean");d(this,n,ot).call(this),i(this,a).setOptions(this.options),e._defaulted&&!q(this.options,e)&&i(this,m).getQueryCache().notify({type:"observerOptionsUpdated",query:i(this,a),observer:this});const h=this.hasListeners();h&&St(i(this,a),r,this.options,e)&&d(this,n,H).call(this),this.updateResult(),h&&(i(this,a)!==r||R(this.options.enabled,i(this,a))!==R(e.enabled,i(this,a))||W(this.options.staleTime,i(this,a))!==W(e.staleTime,i(this,a)))&&d(this,n,st).call(this);const o=d(this,n,it).call(this);h&&(i(this,a)!==r||R(this.options.enabled,i(this,a))!==R(e.enabled,i(this,a))||o!==i(this,T))&&d(this,n,rt).call(this,o)}getOptimisticResult(t){const e=i(this,m).getQueryCache().build(i(this,m),t),r=this.createResult(e,t);return Ut(this,r)&&(l(this,g,r),l(this,U,this.options),l(this,x,i(this,a).state)),r}getCurrentResult(){return i(this,g)}trackResult(t,e){return new Proxy(t,{get:(r,h)=>(this.trackProp(h),e==null||e(h),h==="promise"&&(this.trackProp("data"),!this.options.experimental_prefetchInRender&&i(this,E).status==="pending"&&i(this,E).reject(new Error("experimental_prefetchInRender feature flag is not enabled"))),Reflect.get(r,h))})}trackProp(t){i(this,L).add(t)}getCurrentQuery(){return i(this,a)}refetch({...t}={}){return this.fetch({...t})}fetchOptimistic(t){const e=i(this,m).defaultQueryOptions(t),r=i(this,m).getQueryCache().build(i(this,m),e);return r.fetch().then(()=>this.createResult(r,e))}fetch(t){return d(this,n,H).call(this,{...t,cancelRefetch:t.cancelRefetch??!0}).then(()=>(this.updateResult(),i(this,g)))}createResult(t,e){var ft;const r=i(this,a),h=this.options,o=i(this,g),c=i(this,x),S=i(this,U),N=t!==r?t.state:i(this,z),{state:b}=t;let u={...b},B=!1,f;if(e._optimisticResults){const v=this.hasListeners(),j=!v&&Rt(t,e),K=v&&St(t,r,e,h);(j||K)&&(u={...u,...xt(b.data,t.options)}),e._optimisticResults==="isRestoring"&&(u.fetchStatus="idle")}let{error:k,errorUpdatedAt:Q,status:y}=u;f=u.data;let O=!1;if(e.placeholderData!==void 0&&f===void 0&&y==="pending"){let v;o!=null&&o.isPlaceholderData&&e.placeholderData===(S==null?void 0:S.placeholderData)?(v=o.data,O=!0):v=typeof e.placeholderData=="function"?e.placeholderData((ft=i(this,P))==null?void 0:ft.state.data,i(this,P)):e.placeholderData,v!==void 0&&(y="success",f=vt(o==null?void 0:o.data,v,e),B=!0)}if(e.select&&f!==void 0&&!O)if(o&&f===(c==null?void 0:c.data)&&e.select===i(this,V))f=i(this,D);else try{l(this,V,e.select),f=e.select(f),f=vt(o==null?void 0:o.data,f,e),l(this,D,f),l(this,w,null)}catch(v){l(this,w,v)}i(this,w)&&(k=i(this,w),f=i(this,D),Q=Date.now(),y="error");const A=u.fetchStatus==="fetching",Y=y==="pending",Z=y==="error",ut=Y&&A,dt=f!==void 0,I={status:y,fetchStatus:u.fetchStatus,isPending:Y,isSuccess:y==="success",isError:Z,isInitialLoading:ut,isLoading:ut,data:f,dataUpdatedAt:u.dataUpdatedAt,error:k,errorUpdatedAt:Q,failureCount:u.fetchFailureCount,failureReason:u.fetchFailureReason,errorUpdateCount:u.errorUpdateCount,isFetched:t.isFetched(),isFetchedAfterMount:u.dataUpdateCount>N.dataUpdateCount||u.errorUpdateCount>N.errorUpdateCount,isFetching:A,isRefetching:A&&!Y,isLoadingError:Z&&!dt,isPaused:u.fetchStatus==="paused",isPlaceholderData:B,isRefetchError:Z&&dt,isStale:lt(t,e),refetch:this.refetch,promise:i(this,E),isEnabled:R(e.enabled,t)!==!1};if(this.options.experimental_prefetchInRender){const v=I.data!==void 0,j=I.status==="error"&&!v,K=G=>{j?G.reject(I.error):v&&G.resolve(I.data)},pt=()=>{const G=l(this,E,I.promise=gt());K(G)},J=i(this,E);switch(J.status){case"pending":t.queryHash===r.queryHash&&K(J);break;case"fulfilled":(j||I.data!==J.value)&&pt();break;case"rejected":(!j||I.error!==J.reason)&&pt();break}}return I}updateResult(){const t=i(this,g),e=this.createResult(i(this,a),this.options);if(l(this,x,i(this,a).state),l(this,U,this.options),i(this,x).data!==void 0&&l(this,P,i(this,a)),q(e,t))return;l(this,g,e);const r=()=>{if(!t)return!0;const{notifyOnChangeProps:h}=this.options,o=typeof h=="function"?h():h;if(o==="all"||!o&&!i(this,L).size)return!0;const c=new Set(o??i(this,L));return this.options.throwOnError&&c.add("error"),Object.keys(i(this,g)).some(S=>{const M=S;return i(this,g)[M]!==t[M]&&c.has(M)})};d(this,n,It).call(this,{listeners:r()})}onQueryUpdate(){this.updateResult(),this.hasListeners()&&d(this,n,at).call(this)}},m=new WeakMap,a=new WeakMap,z=new WeakMap,g=new WeakMap,x=new WeakMap,U=new WeakMap,E=new WeakMap,w=new WeakMap,V=new WeakMap,D=new WeakMap,P=new WeakMap,_=new WeakMap,F=new WeakMap,T=new WeakMap,L=new WeakMap,n=new WeakSet,H=function(t){d(this,n,ot).call(this);let e=i(this,a).fetch(this.options,t);return t!=null&&t.throwOnError||(e=e.catch(tt)),e},st=function(){d(this,n,nt).call(this);const t=W(this.options.staleTime,i(this,a));if(et.isServer()||i(this,g).isStale||!mt(t))return;const r=Mt(i(this,g).dataUpdatedAt,t)+1;l(this,_,X.setTimeout(()=>{i(this,g).isStale||this.updateResult()},r))},it=function(){return(typeof this.options.refetchInterval=="function"?this.options.refetchInterval(i(this,a)):this.options.refetchInterval)??!1},rt=function(t){d(this,n,ht).call(this),l(this,T,t),!(et.isServer()||R(this.options.enabled,i(this,a))===!1||!mt(i(this,T))||i(this,T)===0)&&l(this,F,X.setInterval(()=>{(this.options.refetchIntervalInBackground||Qt.isFocused())&&d(this,n,H).call(this)},i(this,T)))},at=function(){d(this,n,st).call(this),d(this,n,rt).call(this,d(this,n,it).call(this))},nt=function(){i(this,_)!==void 0&&(X.clearTimeout(i(this,_)),l(this,_,void 0))},ht=function(){i(this,F)!==void 0&&(X.clearInterval(i(this,F)),l(this,F,void 0))},ot=function(){const t=i(this,m).getQueryCache().build(i(this,m),this.options);if(t===i(this,a))return;const e=i(this,a);l(this,a,t),l(this,z,t.state),this.hasListeners()&&(e==null||e.removeObserver(this),t.addObserver(this))},It=function(t){Ct.batch(()=>{t.listeners&&this.listeners.forEach(e=>{e(i(this,g))}),i(this,m).getQueryCache().notify({query:i(this,a),type:"observerResultsUpdated"})})},Et);function Ft(s,t){return R(t.enabled,s)!==!1&&s.state.data===void 0&&!(s.state.status==="error"&&R(t.retryOnMount,s)===!1)}function Rt(s,t){return Ft(s,t)||s.state.data!==void 0&&ct(s,t,t.refetchOnMount)}function ct(s,t,e){if(R(t.enabled,s)!==!1&&W(t.staleTime,s)!=="static"){const r=typeof e=="function"?e(s):e;return r==="always"||r!==!1&<(s,t)}return!1}function St(s,t,e,r){return(s!==t||R(r.enabled,s)===!1)&&(!e.suspense||s.state.status!=="error")&<(s,e)}function lt(s,t){return R(t.enabled,s)!==!1&&s.isStaleByTime(W(t.staleTime,s))}function Ut(s,t){return!q(s.getCurrentResult(),t)}var wt=C.createContext(!1),Dt=()=>C.useContext(wt);wt.Provider;function Pt(){let s=!1;return{clearReset:()=>{s=!1},reset:()=>{s=!0},isReset:()=>s}}var Lt=C.createContext(Pt()),Nt=()=>C.useContext(Lt),Bt=(s,t,e)=>{const r=e!=null&&e.state.error&&typeof s.throwOnError=="function"?Ot(s.throwOnError,[e.state.error,e]):s.throwOnError;(s.suspense||s.experimental_prefetchInRender||r)&&(t.isReset()||(s.retryOnMount=!1))},kt=s=>{C.useEffect(()=>{s.clearReset()},[s])},At=({result:s,errorResetBoundary:t,throwOnError:e,query:r,suspense:h})=>s.isError&&!t.isReset()&&!s.isFetching&&r&&(h&&s.data===void 0||Ot(e,[s.error,r])),jt=s=>{if(s.suspense){const e=h=>h==="static"?h:Math.max(h??1e3,1e3),r=s.staleTime;s.staleTime=typeof r=="function"?(...h)=>e(r(...h)):e(r),typeof s.gcTime=="number"&&(s.gcTime=Math.max(s.gcTime,1e3))}},Ht=(s,t)=>s.isLoading&&s.isFetching&&!t,Wt=(s,t)=>(s==null?void 0:s.suspense)&&t.isPending,yt=(s,t,e)=>t.fetchOptimistic(s).catch(()=>{e.clearReset()});function Xt(s,t,e){var f,k,Q,y;const r=Dt(),h=Nt(),o=_t(),c=o.defaultQueryOptions(s);(k=(f=o.getDefaultOptions().queries)==null?void 0:f._experimental_beforeQuery)==null||k.call(f,c);const S=o.getQueryCache().get(c.queryHash),M=s.subscribed!==!1;c._optimisticResults=r?"isRestoring":M?"optimistic":void 0,jt(c),Bt(c,h,S),kt(h);const N=!o.getQueryCache().get(c.queryHash),[b]=C.useState(()=>new t(o,c)),u=b.getOptimisticResult(c),B=!r&&M;if(C.useSyncExternalStore(C.useCallback(O=>{const A=B?b.subscribe(Ct.batchCalls(O)):tt;return b.updateResult(),A},[b,B]),()=>b.getCurrentResult(),()=>b.getCurrentResult()),C.useEffect(()=>{b.setOptions(c)},[c,b]),Wt(c,u))throw yt(c,b,h);if(At({result:u,errorResetBoundary:h,throwOnError:c.throwOnError,query:S,suspense:c.suspense}))throw u.error;if((y=(Q=o.getDefaultOptions().queries)==null?void 0:Q._experimental_afterQuery)==null||y.call(Q,c,u),c.experimental_prefetchInRender&&!et.isServer()&&Ht(u,r)){const O=N?yt(c,b,h):S==null?void 0:S.promise;O==null||O.catch(tt).finally(()=>{b.updateResult()})}return c.notifyOnChangeProps?u:b.trackResult(u)}export{Gt as Q,Nt as a,Bt as b,kt as c,Xt as d,jt as e,yt as f,At as g,Wt as s,Dt as u}; diff --git a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/useInfiniteQuery.js b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/useInfiniteQuery.js index bc2b5c7c..f8eab391 100644 --- a/src/codex_usage_tracker/plugin_data/dashboard/react/assets/useInfiniteQuery.js +++ b/src/codex_usage_tracker/plugin_data/dashboard/react/assets/useInfiniteQuery.js @@ -1 +1 @@ -import{Q as v,d as p}from"./useBaseQuery.js";import{$ as x,a0 as b}from"./App.js";var l=class extends v{constructor(e,t){super(e,t)}bindMethods(){super.bindMethods(),this.fetchNextPage=this.fetchNextPage.bind(this),this.fetchPreviousPage=this.fetchPreviousPage.bind(this)}setOptions(e){e._type="infinite",super.setOptions(e)}getOptimisticResult(e){return e._type="infinite",super.getOptimisticResult(e)}fetchNextPage(e){return this.fetch({...e,meta:{fetchMore:{direction:"forward"}}})}fetchPreviousPage(e){return this.fetch({...e,meta:{fetchMore:{direction:"backward"}}})}createResult(e,t){var f,P;const{state:s}=e,i=super.createResult(e,t),{isFetching:a,isRefetching:g,isError:c,isRefetchError:d}=i,r=(P=(f=s.fetchMeta)==null?void 0:f.fetchMore)==null?void 0:P.direction,h=c&&r==="forward",n=a&&r==="forward",o=c&&r==="backward",u=a&&r==="backward";return{...i,fetchNextPage:this.fetchNextPage,fetchPreviousPage:this.fetchPreviousPage,hasNextPage:b(t,s.data),hasPreviousPage:x(t,s.data),isFetchNextPageError:h,isFetchingNextPage:n,isFetchPreviousPageError:o,isFetchingPreviousPage:u,isRefetchError:d&&!h&&!o,isRefetching:g&&!n&&!u}}};function w(e,t){return p(e,l)}export{w as u}; +import{Q as v,d as p}from"./useBaseQuery.js";import{a3 as x,a4 as b}from"./App.js";var l=class extends v{constructor(e,t){super(e,t)}bindMethods(){super.bindMethods(),this.fetchNextPage=this.fetchNextPage.bind(this),this.fetchPreviousPage=this.fetchPreviousPage.bind(this)}setOptions(e){e._type="infinite",super.setOptions(e)}getOptimisticResult(e){return e._type="infinite",super.getOptimisticResult(e)}fetchNextPage(e){return this.fetch({...e,meta:{fetchMore:{direction:"forward"}}})}fetchPreviousPage(e){return this.fetch({...e,meta:{fetchMore:{direction:"backward"}}})}createResult(e,t){var f,P;const{state:s}=e,i=super.createResult(e,t),{isFetching:a,isRefetching:g,isError:c,isRefetchError:d}=i,r=(P=(f=s.fetchMeta)==null?void 0:f.fetchMore)==null?void 0:P.direction,h=c&&r==="forward",n=a&&r==="forward",o=c&&r==="backward",u=a&&r==="backward";return{...i,fetchNextPage:this.fetchNextPage,fetchPreviousPage:this.fetchPreviousPage,hasNextPage:b(t,s.data),hasPreviousPage:x(t,s.data),isFetchNextPageError:h,isFetchingNextPage:n,isFetchPreviousPageError:o,isFetchingPreviousPage:u,isRefetchError:d&&!h&&!o,isRefetching:g&&!n&&!u}}};function w(e,t){return p(e,l)}export{w as u}; diff --git a/src/codex_usage_tracker/plugin_data/rate_cards/codex-credit-rates.json b/src/codex_usage_tracker/plugin_data/rate_cards/codex-credit-rates.json index 85768797..b35d41de 100644 --- a/src/codex_usage_tracker/plugin_data/rate_cards/codex-credit-rates.json +++ b/src/codex_usage_tracker/plugin_data/rate_cards/codex-credit-rates.json @@ -83,6 +83,29 @@ "tier": "standard" } }, + "fast_multipliers": { + "gpt-5.6": { + "multiplier": 2.5, + "source_name": "OpenAI Codex Fast mode docs", + "source_url": "https://learn.chatgpt.com/docs/agent-configuration/speed", + "fetched_at": "2026-07-16", + "confidence": "exact" + }, + "gpt-5.5": { + "multiplier": 2.5, + "source_name": "OpenAI Codex Fast mode docs", + "source_url": "https://learn.chatgpt.com/docs/agent-configuration/speed", + "fetched_at": "2026-07-16", + "confidence": "exact" + }, + "gpt-5.4": { + "multiplier": 2.0, + "source_name": "OpenAI Codex Fast mode docs", + "source_url": "https://learn.chatgpt.com/docs/agent-configuration/speed", + "fetched_at": "2026-07-16", + "confidence": "exact" + } + }, "aliases": { "gpt-5.6": { "model": "gpt-5.6-sol", diff --git a/src/codex_usage_tracker/pricing/allowance.py b/src/codex_usage_tracker/pricing/allowance.py index c4d18cf3..25b26c4c 100644 --- a/src/codex_usage_tracker/pricing/allowance.py +++ b/src/codex_usage_tracker/pricing/allowance.py @@ -18,17 +18,20 @@ CODEX_RATE_CARD_URL, DEFAULT_SOURCE, RATE_CARD_SCHEMA, + FastMultiplierRate, RateCardUpdateResult, load_bundled_rate_card, parse_alias_metadata, parse_aliases, parse_credit_rate_metadata, parse_credit_rates, + parse_fast_multipliers, parse_rate_card_source, update_rate_card, ) from codex_usage_tracker.pricing.allowance_usage import ( annotate_rows_with_allowance, + estimate_standard_usage_credits, estimate_usage_credits, resolve_credit_rate, summarize_allowance_usage, @@ -39,12 +42,14 @@ "CODEX_PRICING_URL", "CODEX_RATE_CARD_URL", "DEFAULT_SOURCE", + "FastMultiplierRate", "RATE_CARD_SCHEMA", "ALLOWANCE_TEMPLATE", "AllowanceWindow", "UsageAllowanceConfig", "RateCardUpdateResult", "annotate_rows_with_allowance", + "estimate_standard_usage_credits", "estimate_usage_credits", "load_allowance_config", "load_bundled_rate_card", @@ -53,6 +58,7 @@ "parse_allowance_text", "parse_credit_rate_metadata", "parse_credit_rates", + "parse_fast_multipliers", "parse_rate_card_source", "parse_windows", "resolve_credit_rate", diff --git a/src/codex_usage_tracker/pricing/allowance_config.py b/src/codex_usage_tracker/pricing/allowance_config.py index bcdc185a..23deb9ad 100644 --- a/src/codex_usage_tracker/pricing/allowance_config.py +++ b/src/codex_usage_tracker/pricing/allowance_config.py @@ -3,13 +3,14 @@ from __future__ import annotations import json -from dataclasses import asdict, dataclass +from dataclasses import asdict, dataclass, field from datetime import datetime, timezone from pathlib import Path from typing import Any from codex_usage_tracker.core.paths import DEFAULT_ALLOWANCE_PATH, DEFAULT_RATE_CARD_PATH from codex_usage_tracker.pricing.allowance_rate_card import ( + FastMultiplierRate, load_bundled_rate_card, load_json_file, optional_positive_number, @@ -18,6 +19,7 @@ parse_aliases, parse_credit_rate_metadata, parse_credit_rates, + parse_fast_multipliers, parse_rate_card_source, ) from codex_usage_tracker.pricing.allowance_text import allowance_line_matches @@ -67,6 +69,7 @@ ], "credit_rates": {}, "aliases": {}, + "fast_multipliers": {}, } @@ -97,6 +100,7 @@ class UsageAllowanceConfig: loaded: bool rate_card_loaded: bool source: dict[str, Any] + fast_multipliers: dict[str, FastMultiplierRate] = field(default_factory=dict) error: str | None = None rate_card_error: str | None = None @@ -108,7 +112,8 @@ def load_allowance_config( ) -> UsageAllowanceConfig: """Load optional allowance settings while always keeping bundled rate-card data.""" - base_card = load_bundled_rate_card() + bundled_card = load_bundled_rate_card() + base_card = bundled_card rate_card_loaded = False rate_card_error = None if rate_card_path.expanduser().exists(): @@ -125,6 +130,13 @@ def load_allowance_config( base_card.get("credit_rates", {}), source=source, default_confidence="exact" ) alias_metadata = parse_alias_metadata(base_card.get("aliases", {}), source=source) + bundled_source = parse_rate_card_source(bundled_card) + fast_multipliers = parse_fast_multipliers( + bundled_card.get("fast_multipliers", {}), source=bundled_source + ) + fast_multipliers.update( + parse_fast_multipliers(base_card.get("fast_multipliers", {}), source=source) + ) windows: list[AllowanceWindow] = [] if not path.exists(): return UsageAllowanceConfig( @@ -138,6 +150,7 @@ def load_allowance_config( loaded=False, rate_card_loaded=rate_card_loaded, source=source, + fast_multipliers=fast_multipliers, rate_card_error=rate_card_error, ) @@ -168,6 +181,17 @@ def load_allowance_config( }, ) ) + fast_multipliers.update( + parse_fast_multipliers( + raw.get("fast_multipliers", {}), + source={ + "name": "Local allowance override", + "url": str(path.expanduser()), + "fetched_at": None, + }, + default_confidence="user_override", + ) + ) windows = parse_windows(raw.get("windows", [])) except (OSError, ValueError, TypeError, json.JSONDecodeError) as exc: return UsageAllowanceConfig( @@ -181,6 +205,7 @@ def load_allowance_config( loaded=False, rate_card_loaded=rate_card_loaded, source=source, + fast_multipliers=fast_multipliers, error=str(exc), rate_card_error=rate_card_error, ) @@ -196,6 +221,7 @@ def load_allowance_config( loaded=True, rate_card_loaded=rate_card_loaded, source=source, + fast_multipliers=fast_multipliers, rate_card_error=rate_card_error, ) diff --git a/src/codex_usage_tracker/pricing/allowance_rate_card.py b/src/codex_usage_tracker/pricing/allowance_rate_card.py index 1859a2f1..e83afd6d 100644 --- a/src/codex_usage_tracker/pricing/allowance_rate_card.py +++ b/src/codex_usage_tracker/pricing/allowance_rate_card.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import math import shutil from dataclasses import dataclass from datetime import datetime, timezone @@ -39,6 +40,17 @@ class RateCardUpdateResult: backup_path: Path | None = None +@dataclass(frozen=True) +class FastMultiplierRate: + """One source-stamped ChatGPT Fast credit multiplier.""" + + multiplier: float + source_name: str + source_url: str | None + fetched_at: str | None + confidence: str + + def load_bundled_rate_card() -> dict[str, Any]: """Load the package-bundled Codex credit rate-card snapshot.""" @@ -68,10 +80,17 @@ def update_rate_card( source = parse_rate_card_source(raw) credit_rates = parse_credit_rates(raw.get("credit_rates", {})) aliases = parse_aliases(raw.get("aliases", {})) + fast_multipliers = parse_fast_multipliers( + raw.get("fast_multipliers", {}), source=source + ) if not credit_rates: raise ValueError("rate card must contain at least one credit rate") parse_credit_rate_metadata(raw.get("credit_rates", {}), source=source) parse_alias_metadata(raw.get("aliases", {}), source=source) + if isinstance(raw.get("fast_multipliers"), dict) and len(fast_multipliers) != len( + raw["fast_multipliers"] + ): + raise ValueError("rate card contains an invalid Fast multiplier") path = path.expanduser() path.parent.mkdir(parents=True, exist_ok=True) @@ -107,6 +126,56 @@ def parse_credit_rates(raw: object) -> dict[str, dict[str, float]]: return parsed +def parse_fast_multipliers( + raw: object, + *, + source: dict[str, Any], + default_confidence: str = "exact", +) -> dict[str, FastMultiplierRate]: + """Parse valid source-stamped Fast multipliers, skipping malformed entries.""" + + if not isinstance(raw, dict): + return {} + parsed: dict[str, FastMultiplierRate] = {} + for family, value in raw.items(): + normalized = normalize_model(family) + if not normalized: + continue + entry = value if isinstance(value, dict) else {"multiplier": value} + multiplier = _fast_multiplier_number(entry.get("multiplier")) + if multiplier is None: + continue + confidence = ( + "user_override" + if default_confidence == "user_override" + else optional_str(entry.get("confidence")) or default_confidence + ) + parsed[normalized] = FastMultiplierRate( + multiplier=multiplier, + source_name=optional_str(entry.get("source_name")) + or optional_str(source.get("name")) + or "Codex Fast multiplier", + source_url=optional_str(entry.get("source_url")) + or optional_str(source.get("url")), + fetched_at=optional_str(entry.get("fetched_at")) + or optional_str(source.get("fetched_at")), + confidence=confidence, + ) + return parsed + + +def _fast_multiplier_number(value: object) -> float | None: + if isinstance(value, bool): + return None + try: + multiplier = number_value(value) + except (TypeError, ValueError): + return None + if not math.isfinite(multiplier) or multiplier < 1.0: + return None + return multiplier + + def parse_aliases(raw: object) -> dict[str, dict[str, str]]: if not isinstance(raw, dict): return {} diff --git a/src/codex_usage_tracker/pricing/allowance_usage.py b/src/codex_usage_tracker/pricing/allowance_usage.py index 2209f604..311c4fee 100644 --- a/src/codex_usage_tracker/pricing/allowance_usage.py +++ b/src/codex_usage_tracker/pricing/allowance_usage.py @@ -17,9 +17,11 @@ number_value, optional_str, ) +from codex_usage_tracker.pricing.fast_tier import credit_multiplier_for_row __all__ = ( "annotate_rows_with_allowance", + "estimate_standard_usage_credits", "estimate_usage_credits", "resolve_credit_rate", "summarize_allowance_usage", @@ -41,10 +43,32 @@ def annotate_rows_with_allowance( copy = dict(row) model = copy.get(model_field) match = resolve_credit_rate(model, resolved) + multiplier, multiplier_match, multiplier_fallback = credit_multiplier_for_row( + copy, resolved.fast_multipliers + ) + multiplier_source = ( + multiplier_match.source_name if multiplier_match else multiplier_fallback + ) + multiplier_source_url = ( + multiplier_match.source_url if multiplier_match else None + ) + multiplier_fetched_at = ( + multiplier_match.fetched_at if multiplier_match else None + ) + multiplier_confidence = ( + multiplier_match.confidence if multiplier_match else None + ) if match is None: copy.update( { "usage_credits": None, + "standard_usage_credits": None, + "fast_usage_credits": None, + "usage_credit_multiplier": multiplier, + "usage_credit_multiplier_source": multiplier_source, + "usage_credit_multiplier_source_url": multiplier_source_url, + "usage_credit_multiplier_fetched_at": multiplier_fetched_at, + "usage_credit_multiplier_confidence": multiplier_confidence, "usage_credit_model": None, "usage_credit_confidence": "unpriced", "usage_credit_source": "No Codex credit rate", @@ -56,9 +80,22 @@ def annotate_rows_with_allowance( ) else: rated_model, rates, confidence, note, metadata = match + standard_credits = estimate_standard_usage_credits(copy, rates) + fast_credits = ( + standard_credits * multiplier_match.multiplier + if multiplier_match is not None + else None + ) copy.update( { - "usage_credits": estimate_usage_credits(copy, rates), + "usage_credits": standard_credits * multiplier, + "standard_usage_credits": standard_credits, + "fast_usage_credits": fast_credits, + "usage_credit_multiplier": multiplier, + "usage_credit_multiplier_source": multiplier_source, + "usage_credit_multiplier_source_url": multiplier_source_url, + "usage_credit_multiplier_fetched_at": multiplier_fetched_at, + "usage_credit_multiplier_confidence": multiplier_confidence, "usage_credit_model": rated_model, "usage_credit_confidence": confidence, "usage_credit_source": metadata.get("source_name") @@ -185,8 +222,10 @@ def _resolve_alias_credit_rate( return target, rates, confidence, note, metadata -def estimate_usage_credits(row: dict[str, Any], rates: dict[str, float]) -> float: - """Estimate Codex credits from aggregate token counters.""" +def estimate_standard_usage_credits( + row: dict[str, Any], rates: dict[str, float] +) -> float: + """Estimate Standard-tier Codex credits from aggregate token counters.""" input_rate = rates["input_per_million"] cached_rate = rates["cached_input_per_million"] @@ -199,3 +238,11 @@ def estimate_usage_credits(row: dict[str, Any], rates: dict[str, float]) -> floa return ( (uncached_input * input_rate) + (cached_input * cached_rate) + (output_tokens * output_rate) ) / 1_000_000 + + +def estimate_usage_credits(row: dict[str, Any], rates: dict[str, float]) -> float: + """Estimate effective Codex credits, including confirmed Fast multipliers.""" + + standard = estimate_standard_usage_credits(row, rates) + multiplier, _match, _source = credit_multiplier_for_row(row) + return standard * multiplier diff --git a/src/codex_usage_tracker/pricing/api.py b/src/codex_usage_tracker/pricing/api.py index 75329594..bca3964a 100644 --- a/src/codex_usage_tracker/pricing/api.py +++ b/src/codex_usage_tracker/pricing/api.py @@ -29,6 +29,7 @@ VALID_PRICING_TIERS, PricingParseError, PricingUpdateResult, + parse_all_openai_pricing_tiers, parse_openai_latest_model_id, parse_openai_pricing_markdown, update_pricing_from_openai_docs, @@ -52,6 +53,7 @@ "estimate_cache_savings_usd", "estimate_cost_usd", "load_pricing_config", + "parse_all_openai_pricing_tiers", "parse_openai_latest_model_id", "parse_openai_pricing_markdown", "pin_pricing_snapshot", diff --git a/src/codex_usage_tracker/pricing/config.py b/src/codex_usage_tracker/pricing/config.py index f731cce9..6c482734 100644 --- a/src/codex_usage_tracker/pricing/config.py +++ b/src/codex_usage_tracker/pricing/config.py @@ -10,13 +10,24 @@ from codex_usage_tracker.core.paths import DEFAULT_PRICING_PATH -PRICING_SCHEMA = "codex-usage-tracker-pricing-v1" +PRICING_SCHEMA = "codex-usage-tracker-pricing-v2" +BILLING_BASES = ("unknown", "chatgpt_credits", "api_tokens") +API_SERVICE_TIER_ALIASES = { + "fast": "priority", + "priority": "priority", + "default": "standard", + "standard": "standard", + "batch": "batch", + "flex": "flex", +} PRICING_TEMPLATE = { "_comment": ( "Fill in current prices in USD per 1 million tokens. The tracker does " "not fetch pricing during normal reports. Prefer update-pricing when " "you want to cache current OpenAI-published rates locally." ), + "_schema": PRICING_SCHEMA, + "billing_basis": "unknown", "models": { "replace-with-model-name": { "input_per_million": 0.0, @@ -41,17 +52,28 @@ class PricingConfig: estimated_models: set[str] | None = None source: dict[str, Any] | None = None error: str | None = None - - def rates_for(self, model: object) -> dict[str, float] | None: - if not isinstance(model, str) or not model: - return None - direct = self.models.get(model) - if direct is not None: - return direct - alias_target = (self.aliases or {}).get(model) - if not alias_target: - return None - return self.models.get(alias_target) + api_service_tiers: dict[str, dict[str, dict[str, float]]] | None = None + billing_basis: str = "unknown" + + def rates_for( + self, model: object, *, service_tier: object | None = None + ) -> dict[str, float] | None: + normalized_tier = normalize_api_service_tier(service_tier) + tier_tables = self.api_service_tiers or {} + if normalized_tier and tier_tables: + tier_models = tier_tables.get(normalized_tier) + if tier_models is None: + return None + return _rates_for_models(tier_models, self.aliases, model) + return _rates_for_models(self.models, self.aliases, model) + + def pricing_tier_for(self, service_tier: object | None) -> str | None: + normalized = normalize_api_service_tier(service_tier) + tier_tables = self.api_service_tiers or {} + if normalized and tier_tables: + return normalized if normalized in tier_tables else None + source_tier = normalize_api_service_tier((self.source or {}).get("tier")) + return source_tier if source_tier else None def priced_as(self, model: object) -> str | None: if not isinstance(model, str) or not model: @@ -76,6 +98,8 @@ def load_pricing_config(path: Path = DEFAULT_PRICING_PATH) -> PricingConfig: try: raw = json.loads(path.read_text(encoding="utf-8")) models = parse_models(raw) + api_service_tiers = parse_api_service_tiers(raw) + billing_basis = parse_billing_basis(raw) except (OSError, ValueError, TypeError, json.JSONDecodeError) as exc: return PricingConfig(path=path, models={}, loaded=False, error=str(exc)) source = raw.get("_source") if isinstance(raw, dict) else None @@ -87,6 +111,8 @@ def load_pricing_config(path: Path = DEFAULT_PRICING_PATH) -> PricingConfig: aliases=aliases, estimated_models=parse_estimated_models(raw), source=source if isinstance(source, dict) else None, + api_service_tiers=api_service_tiers, + billing_basis=billing_basis, ) @@ -147,6 +173,69 @@ def parse_models(raw: object) -> dict[str, dict[str, float]]: return models +def parse_api_service_tiers( + raw: object, +) -> dict[str, dict[str, dict[str, float]]]: + if not isinstance(raw, dict): + return {} + tier_payload = raw.get("api_service_tiers") + if tier_payload is None: + return {} + if not isinstance(tier_payload, dict): + raise ValueError("pricing config 'api_service_tiers' must be an object") + parsed: dict[str, dict[str, dict[str, float]]] = {} + for raw_tier, models in tier_payload.items(): + tier = normalize_api_service_tier(raw_tier) + if tier is None or not isinstance(models, dict): + raise ValueError(f"invalid API service tier pricing entry: {raw_tier!r}") + parsed[tier] = _parse_model_payload(models) + return parsed + + +def parse_billing_basis(raw: object) -> str: + if not isinstance(raw, dict): + return "unknown" + value = raw.get("billing_basis", "unknown") + if not isinstance(value, str) or value not in BILLING_BASES: + raise ValueError( + f"unknown billing_basis {value!r}; expected one of {', '.join(BILLING_BASES)}" + ) + return value + + +def normalize_api_service_tier(value: object) -> str | None: + if not isinstance(value, str): + return None + return API_SERVICE_TIER_ALIASES.get(value.strip().lower()) + + +def _rates_for_models( + models: dict[str, dict[str, float]], + aliases: dict[str, str] | None, + model: object, +) -> dict[str, float] | None: + if not isinstance(model, str) or not model: + return None + direct = models.get(model) + if direct is not None: + return direct + alias_target = (aliases or {}).get(model) + return models.get(alias_target) if alias_target else None + + +def _parse_model_payload( + model_payload: dict[object, object], +) -> dict[str, dict[str, float]]: + models: dict[str, dict[str, float]] = {} + for model, rates in model_payload.items(): + if not _valid_model_name(model): + continue + parsed_rates = _parsed_model_rates(model, rates) + if parsed_rates is not None: + models[model] = parsed_rates + return models + + def _required_model_payload(raw: object) -> dict[object, object]: if not isinstance(raw, dict): raise ValueError("pricing config must be a JSON object") @@ -240,6 +329,15 @@ def load_existing_aliases(path: Path) -> dict[str, str]: return {} +def load_existing_billing_basis(path: Path) -> str: + if not path.exists(): + return "unknown" + try: + return parse_billing_basis(json.loads(path.read_text(encoding="utf-8"))) + except (OSError, ValueError, TypeError, json.JSONDecodeError): + return "unknown" + + def _required_rate(rates: dict[str, Any], key: str, model: str) -> float: value = _optional_rate(rates, key) if value is None: diff --git a/src/codex_usage_tracker/pricing/costing.py b/src/codex_usage_tracker/pricing/costing.py index 8fdcc0e0..1d4e1bc3 100644 --- a/src/codex_usage_tracker/pricing/costing.py +++ b/src/codex_usage_tracker/pricing/costing.py @@ -96,6 +96,17 @@ def annotate_rows_with_efficiency( savings = estimate_cache_savings_usd(copy, config, model=model) copy["estimated_cost_usd"] = cost copy["estimated_cache_savings_usd"] = savings + copy["standard_cost_usd"] = _estimate_cost_usd_for_tier( + copy, config, model=model, service_tier="standard" + ) + copy["priority_cost_usd"] = _estimate_cost_usd_for_tier( + copy, config, model=model, service_tier="priority" + ) + copy["pricing_service_tier"] = config.pricing_tier_for( + copy.get("service_tier") + ) + copy["billing_basis"] = config.billing_basis + copy["cost_semantics"] = "api_token_estimate" copy["pricing_model"] = config.priced_as(model) copy["pricing_estimated"] = config.is_estimated_model(model) copy["efficiency_flags"] = efficiency_flags(copy) @@ -113,7 +124,25 @@ def estimate_cost_usd( ) -> float | None: """Estimate call cost from aggregate tokens and local model rates.""" - rates = pricing.rates_for(model if model is not None else row.get("model")) + return _estimate_cost_usd_for_tier( + row, + pricing, + model=model, + service_tier=row.get("service_tier"), + ) + + +def _estimate_cost_usd_for_tier( + row: dict[str, Any], + pricing: PricingConfig, + *, + model: object | None, + service_tier: object | None, +) -> float | None: + rates = pricing.rates_for( + model if model is not None else row.get("model"), + service_tier=service_tier, + ) if not rates: return None @@ -144,7 +173,10 @@ def estimate_cache_savings_usd( ) -> float | None: """Estimate local cache savings when cached input has a lower configured rate.""" - rates = pricing.rates_for(model if model is not None else row.get("model")) + rates = pricing.rates_for( + model if model is not None else row.get("model"), + service_tier=row.get("service_tier"), + ) if not rates: return None input_rate = rates.get("input_per_million") diff --git a/src/codex_usage_tracker/pricing/fast_tier.py b/src/codex_usage_tracker/pricing/fast_tier.py new file mode 100644 index 00000000..d3194b31 --- /dev/null +++ b/src/codex_usage_tracker/pricing/fast_tier.py @@ -0,0 +1,88 @@ +"""Documented Codex Fast credit multipliers for exact-tier accounting.""" + +from __future__ import annotations + +from collections.abc import Mapping +from dataclasses import dataclass +from functools import lru_cache + +from codex_usage_tracker.pricing.allowance_rate_card import ( + FastMultiplierRate, + load_bundled_rate_card, + parse_fast_multipliers, + parse_rate_card_source, +) + + +@dataclass(frozen=True) +class FastMultiplierMatch: + """A model-family multiplier match with independent numeric provenance.""" + + multiplier: float + source_name: str + source_url: str | None + fetched_at: str | None + confidence: str + + +def documented_fast_credit_multiplier( + model: object, + multipliers: Mapping[str, FastMultiplierRate] | None = None, +) -> float | None: + """Return the documented Fast multiplier for a model family label.""" + + match = match_fast_credit_multiplier(model, multipliers) + return match.multiplier if match is not None else None + + +def match_fast_credit_multiplier( + model: object, + multipliers: Mapping[str, FastMultiplierRate] | None = None, +) -> FastMultiplierMatch | None: + """Resolve the longest configured model-family prefix.""" + + normalized = str(model or "").strip().lower().replace("_", "-") + configured = multipliers if multipliers is not None else _bundled_fast_multipliers() + for family in sorted(configured, key=len, reverse=True): + if ( + normalized == family + or normalized.startswith(f"{family}-") + or normalized.startswith(f"{family} ") + ): + rate = configured[family] + return FastMultiplierMatch( + multiplier=rate.multiplier, + source_name=rate.source_name, + source_url=rate.source_url, + fetched_at=rate.fetched_at, + confidence=rate.confidence, + ) + return None + + +def credit_multiplier_for_row( + row: Mapping[str, object], + multipliers: Mapping[str, FastMultiplierRate] | None = None, +) -> tuple[float, FastMultiplierMatch | None, str]: + """Return the applied multiplier, available scenario match, and fallback label.""" + + match = match_fast_credit_multiplier(row.get("model"), multipliers) + if row.get("fast") != 1: + fallback = "confirmed_standard" if row.get("fast") == 0 else "tier_unknown" + return 1.0, match, str(row.get("service_tier_source") or fallback) + if match is None: + return 1.0, None, "no_documented_fast_multiplier" + return match.multiplier, match, match.source_name + + +@lru_cache(maxsize=1) +def _bundled_fast_multipliers() -> dict[str, FastMultiplierRate]: + raw = load_bundled_rate_card() + return parse_fast_multipliers( + raw.get("fast_multipliers", {}), source=parse_rate_card_source(raw) + ) + + +DOCUMENTED_FAST_CREDIT_MULTIPLIERS = { + family: rate.multiplier for family, rate in _bundled_fast_multipliers().items() +} diff --git a/src/codex_usage_tracker/pricing/openai.py b/src/codex_usage_tracker/pricing/openai.py index dfc349dd..c0b0cc17 100644 --- a/src/codex_usage_tracker/pricing/openai.py +++ b/src/codex_usage_tracker/pricing/openai.py @@ -16,7 +16,11 @@ from codex_usage_tracker import __version__ from codex_usage_tracker.core.paths import DEFAULT_PRICING_PATH -from codex_usage_tracker.pricing.config import PRICING_SCHEMA, load_existing_aliases +from codex_usage_tracker.pricing.config import ( + PRICING_SCHEMA, + load_existing_aliases, + load_existing_billing_basis, +) from codex_usage_tracker.pricing.estimates import ESTIMATED_MODEL_PRICES, estimated_model_prices OPENAI_PRICING_MD_URL = "https://developers.openai.com/api/docs/pricing.md" @@ -65,7 +69,8 @@ def update_pricing_from_openai_docs( ) fetcher = fetch_text or _fetch_text text = fetcher(source_url) - parsed_models = parse_openai_pricing_markdown(text, tier=tier) + parsed_tiers = parse_all_openai_pricing_tiers(text) + parsed_models = parsed_tiers[tier] if not parsed_models: raise PricingParseError( f"pricing source schema changed: no text-token pricing rows were parsed " @@ -74,12 +79,18 @@ def update_pricing_from_openai_docs( models: dict[str, dict[str, Any]] = { model: dict(rates) for model, rates in parsed_models.items() } + available_official_models = { + model for tier_models in parsed_tiers.values() for model in tier_models + } aliases = { **{ - alias: target for alias, target in _OFFICIAL_PRICING_ALIASES.items() if target in models + alias: target + for alias, target in _OFFICIAL_PRICING_ALIASES.items() + if target in available_official_models }, **load_existing_aliases(path), } + billing_basis = load_existing_billing_basis(path) estimated_model_count = 0 if include_estimates: models.update(estimated_model_prices()) @@ -92,12 +103,18 @@ def update_pricing_from_openai_docs( "name": "OpenAI Developers pricing docs", "url": source_url, "tier": tier, + "tiers": list(VALID_PRICING_TIERS), + "tier_model_counts": { + name: len(tier_models) for name, tier_models in parsed_tiers.items() + }, "fetched_at": fetched_at, "model_count": len(models), "official_model_count": len(models) - estimated_model_count, "estimated_model_count": estimated_model_count, }, + "billing_basis": billing_basis, "models": models, + "api_service_tiers": parsed_tiers, } if aliases: payload["aliases"] = aliases @@ -159,6 +176,17 @@ def parse_openai_pricing_markdown( return models +def parse_all_openai_pricing_tiers( + markdown: str, +) -> dict[str, dict[str, dict[str, float]]]: + """Parse every published API service-tier table from one source document.""" + + return { + tier: parse_openai_pricing_markdown(markdown, tier=tier) + for tier in VALID_PRICING_TIERS + } + + def parse_openai_latest_model_id(markdown: str) -> str: """Return the canonical latest model ID from OpenAI's model guide front matter.""" diff --git a/src/codex_usage_tracker/reports/support.py b/src/codex_usage_tracker/reports/support.py index b7f214a7..9525594a 100644 --- a/src/codex_usage_tracker/reports/support.py +++ b/src/codex_usage_tracker/reports/support.py @@ -17,6 +17,7 @@ DEFAULT_DASHBOARD_PATH, DEFAULT_DB_PATH, DEFAULT_MARKETPLACE_PATH, + DEFAULT_OTEL_COMPLETIONS_DIR, DEFAULT_PLUGIN_LINK, DEFAULT_PRICING_PATH, DEFAULT_PROJECTS_PATH, @@ -41,6 +42,7 @@ "paths", "database", "refresh", + "otel", "pricing", "allowance", "thresholds", @@ -58,6 +60,7 @@ "paths.sessions_dir_exists", "database", "refresh", + "otel", "pricing.loaded", "pricing.error", "pricing.model_count", @@ -162,6 +165,7 @@ def support_bundle_payload( allowance = load_allowance_config(allowance_path, rate_card_path=rate_card_path) thresholds = load_threshold_config(thresholds_path) projects = load_project_config(projects_path) + refresh = refresh_metadata(db_path) payload = { "bundle_version": 1, "generated_at": datetime.now(timezone.utc) @@ -197,11 +201,19 @@ def support_bundle_payload( }, "issue_report": support_bundle_issue_guidance(privacy_mode), "database": schema_state(db_path), - "refresh": refresh_metadata(db_path), + "refresh": refresh, + "otel": { + "completion_directory_exists": DEFAULT_OTEL_COMPLETIONS_DIR.is_dir(), + "refresh_counts": { + key: value for key, value in refresh.items() if key.startswith("otel_") + }, + }, "pricing": { "loaded": pricing.loaded, "error": pricing.error, "model_count": len(pricing.models), + "api_service_tier_count": len(pricing.api_service_tiers or {}), + "billing_basis": pricing.billing_basis, "source": pricing.source, }, "allowance": { @@ -213,6 +225,7 @@ def support_bundle_payload( "rate_card_error": allowance.rate_card_error, "credit_rate_count": len(allowance.credit_rates), "alias_count": len(allowance.aliases), + "fast_multiplier_count": len(allowance.fast_multipliers), }, "thresholds": { "loaded": thresholds.loaded, diff --git a/src/codex_usage_tracker/store/api.py b/src/codex_usage_tracker/store/api.py index 2e272d40..d8c0d131 100644 --- a/src/codex_usage_tracker/store/api.py +++ b/src/codex_usage_tracker/store/api.py @@ -12,12 +12,16 @@ from typing import Any from codex_usage_tracker.core.models import DiagnosticFact, RefreshResult, UsageEvent -from codex_usage_tracker.core.paths import DEFAULT_CODEX_HOME, DEFAULT_DB_PATH +from codex_usage_tracker.core.paths import ( + DEFAULT_CODEX_HOME, + DEFAULT_DB_PATH, +) from codex_usage_tracker.core.schema import ( DIAGNOSTIC_FACT_COLUMN_NAMES, USAGE_EVENT_COLUMN_NAMES, USAGE_EVENT_SCHEMA_CHECKSUM, ) +from codex_usage_tracker.parser.otel import OTEL_DIAGNOSTIC_KEYS from codex_usage_tracker.parser.state import ( PARSER_ADAPTER_VERSION, PARSER_DIAGNOSTIC_KEYS, @@ -104,9 +108,18 @@ from codex_usage_tracker.store.source_records import ( sync_source_records, ) +from codex_usage_tracker.store.source_replacement import ( + OTEL_ENRICHMENT_COLUMNS, +) +from codex_usage_tracker.store.source_replacement import ( + capture_otel_enrichment_for_source_files as _capture_otel_enrichment_for_source_files, +) from codex_usage_tracker.store.source_replacement import ( delete_usage_events_for_source_files as _delete_usage_events_for_source_files, ) +from codex_usage_tracker.store.source_replacement import ( + restore_otel_enrichment as _restore_otel_enrichment, +) from codex_usage_tracker.store.source_replacement import ( source_file_strings as _source_file_strings, ) @@ -143,6 +156,16 @@ EVENT_COLUMNS = list(USAGE_EVENT_COLUMN_NAMES) DIAGNOSTIC_FACT_COLUMNS = list(DIAGNOSTIC_FACT_COLUMN_NAMES) +OTEL_REFRESH_COUNTER_KEYS = ( + "otel_files_scanned", + "otel_imported", + "otel_duplicates", + "otel_matched", + "otel_pending", + "otel_ambiguous", + "otel_conflicts", + *OTEL_DIAGNOSTIC_KEYS, +) __all__ = ["EVENT_COLUMNS", "SCHEMA_VERSION", "SchemaMigrationError", "init_db"] SQLITE_VARIABLE_BATCH_SIZE = 500 RefreshProgressCallback = Callable[[dict[str, object]], None] @@ -153,6 +176,7 @@ def refresh_usage_index( db_path: Path = DEFAULT_DB_PATH, include_archived: bool = False, aggregate_only: bool = False, + otel_dir: Path | None = None, progress_callback: RefreshProgressCallback | None = None, derived_fact_sync: DerivedFactSyncCallback | None = None, ) -> RefreshResult: @@ -167,6 +191,7 @@ def refresh_usage_index( db_path=db_path, include_archived=include_archived, aggregate_only=aggregate_only, + otel_dir=otel_dir, progress_callback=progress_callback, derived_fact_sync=derived_fact_sync, ) @@ -177,6 +202,7 @@ def rebuild_usage_index( db_path: Path = DEFAULT_DB_PATH, include_archived: bool = False, aggregate_only: bool = False, + otel_dir: Path | None = None, derived_fact_sync: DerivedFactSyncCallback | None = None, ) -> RefreshResult: """Drop and rebuild the usage index from all selected Codex logs.""" @@ -190,6 +216,7 @@ def rebuild_usage_index( db_path=db_path, include_archived=include_archived, aggregate_only=aggregate_only, + otel_dir=otel_dir, derived_fact_sync=derived_fact_sync, ) @@ -207,6 +234,8 @@ def reset_usage_database(db_path: Path = DEFAULT_DB_PATH) -> dict[str, Any]: conn.execute("DELETE FROM call_diagnostic_facts") conn.execute("DELETE FROM diagnostic_snapshots") conn.execute("DELETE FROM allowance_observations") + conn.execute("DELETE FROM otel_completion_events") + conn.execute("DELETE FROM otel_completion_sources") conn.execute("DELETE FROM source_records") conn.execute("DELETE FROM usage_events") conn.execute("DELETE FROM thread_summaries") @@ -339,6 +368,7 @@ def record_refresh_metadata( skipped_events: int, inserted_or_updated_events: int, parser_diagnostics: dict[str, int] | None = None, + otel_diagnostics: dict[str, int] | None = None, parsed_source_files: int | None = None, skipped_source_files: int | None = None, ) -> None: @@ -361,6 +391,9 @@ def record_refresh_metadata( diagnostics = parser_diagnostics or {} for key in PARSER_DIAGNOSTIC_KEYS: values[f"parser_{key}"] = str(int(diagnostics.get(key, 0))) + otel_counters = otel_diagnostics or {} + for key in OTEL_REFRESH_COUNTER_KEYS: + values[key] = str(int(otel_counters.get(key, 0))) with connect(db_path) as conn: init_db(conn) conn.executemany( @@ -571,6 +604,9 @@ def _upsert_usage_events_in_connection( source_files_to_replace = _source_file_strings(replace_source_files) affected_thread_keys = _thread_keys_for_source_files(conn, source_files_to_replace) replaced_fingerprints = fingerprints_for_source_files(conn, source_files_to_replace) + preserved_otel_enrichment = _capture_otel_enrichment_for_source_files( + conn, source_files_to_replace + ) _delete_usage_events_for_source_files( conn, source_files_to_replace, @@ -603,6 +639,7 @@ def _upsert_usage_events_in_connection( _delete_diagnostic_facts_for_record_ids(conn, inserted_record_ids) with _deferred_usage_event_indexes(conn, enabled=defer_usage_indexes): _insert_usage_event_rows(conn, rows) + _restore_otel_enrichment(conn, preserved_otel_enrichment) if maintain_allowance_observations: sync_allowance_observations_for_record_ids(conn, record_ids) if maintain_source_records: @@ -693,7 +730,11 @@ def _usage_event_record_ids(rows: list[dict[str, object]]) -> list[str]: def _usage_event_upsert_sql() -> str: placeholders = ", ".join("?" for _column in EVENT_COLUMNS) update_clause = ", ".join( - f"{column}=excluded.{column}" for column in EVENT_COLUMNS if column != "record_id" + f"{column}=COALESCE(usage_events.{column}, excluded.{column})" + if column in OTEL_ENRICHMENT_COLUMNS + else f"{column}=excluded.{column}" + for column in EVENT_COLUMNS + if column != "record_id" ) return ( f"INSERT INTO usage_events ({', '.join(EVENT_COLUMNS)}) " diff --git a/src/codex_usage_tracker/store/otel_ingest.py b/src/codex_usage_tracker/store/otel_ingest.py new file mode 100644 index 00000000..467ff661 --- /dev/null +++ b/src/codex_usage_tracker/store/otel_ingest.py @@ -0,0 +1,291 @@ +"""Incremental ingestion for aggregate-only Codex OTel completion files.""" + +from __future__ import annotations + +import hashlib +import hmac +import os +import sqlite3 +from collections import Counter +from dataclasses import dataclass, field +from datetime import datetime, timezone +from pathlib import Path +from typing import BinaryIO + +from codex_usage_tracker.parser.otel import OtelCompletion, parse_otlp_json_line + +_CURSOR_ANCHOR_BYTES = 4096 + + +@dataclass(frozen=True) +class OtelIngestResult: + files_scanned: int = 0 + imported: int = 0 + duplicates: int = 0 + diagnostics: dict[str, int] = field(default_factory=dict) + + +@dataclass +class _MutableIngestTotals: + files_scanned: int = 0 + imported: int = 0 + duplicates: int = 0 + diagnostics: Counter[str] = field(default_factory=Counter) + + def freeze(self) -> OtelIngestResult: + return OtelIngestResult( + files_scanned=self.files_scanned, + imported=self.imported, + duplicates=self.duplicates, + diagnostics=dict(self.diagnostics), + ) + + +@dataclass(frozen=True) +class _SourceState: + device: int + inode: int + size: int + parsed_offset: int + parsed_line: int + resume_anchor: str | None + + +def discover_otel_sources(directory: Path) -> list[Path]: + """Return current and rotated completion files in deterministic path order.""" + + if not directory.is_dir(): + return [] + return sorted( + path + for path in directory.glob("codex-completions*.jsonl") + if path.is_file() + ) + + +def ingest_otel_completion_files( + conn: sqlite3.Connection, directory: Path +) -> OtelIngestResult: + """Resume every completion source at its last fully handled line.""" + + totals = _MutableIngestTotals() + for path in discover_otel_sources(directory): + source_path = str(path.resolve()) + try: + state = _source_state(conn, source_path) + final_stat, next_offset, next_line, resume_anchor = _ingest_complete_lines( + conn, + path, + source_path, + state, + totals, + ) + except FileNotFoundError: + continue + _upsert_source_cursor( + conn, + source_path, + final_stat, + next_offset, + next_line, + resume_anchor, + ) + totals.files_scanned += 1 + return totals.freeze() + + +def _source_state(conn: sqlite3.Connection, source_path: str) -> _SourceState | None: + row = conn.execute( + """ + SELECT device, inode, size, parsed_offset, parsed_line, resume_anchor + FROM otel_completion_sources + WHERE source_path = ? + """, + (source_path,), + ).fetchone() + if row is None: + return None + return _SourceState( + device=int(row["device"]), + inode=int(row["inode"]), + size=int(row["size"]), + parsed_offset=int(row["parsed_offset"]), + parsed_line=int(row["parsed_line"]), + resume_anchor=str(row["resume_anchor"]) if row["resume_anchor"] else None, + ) + + +def _resume_position( + state: _SourceState | None, + stat: os.stat_result, + handle: BinaryIO, +) -> tuple[int, int]: + if state is None: + return 0, 0 + unchanged_file = state.device == stat.st_dev and state.inode == stat.st_ino + has_not_shrunk = stat.st_size >= state.size and stat.st_size >= state.parsed_offset + anchor_matches = state.resume_anchor is not None and hmac.compare_digest( + state.resume_anchor, + _cursor_anchor(handle, state.parsed_offset), + ) + if unchanged_file and has_not_shrunk and anchor_matches: + return state.parsed_offset, state.parsed_line + return 0, 0 + + +def _ingest_complete_lines( + conn: sqlite3.Connection, + path: Path, + source_path: str, + state: _SourceState | None, + totals: _MutableIngestTotals, +) -> tuple[os.stat_result, int, int, str]: + for attempt in range(2): + result = _read_source_descriptor( + conn, + path, + source_path, + state if attempt == 0 else None, + totals, + ) + initial_stat, final_stat, next_offset, next_line, resume_anchor = result + if ( + initial_stat.st_dev == final_stat.st_dev + and initial_stat.st_ino == final_stat.st_ino + ): + return final_stat, next_offset, next_line, resume_anchor + raise FileNotFoundError("OTel source descriptor identity changed during retry") + + +def _read_source_descriptor( + conn: sqlite3.Connection, + path: Path, + source_path: str, + state: _SourceState | None, + totals: _MutableIngestTotals, +) -> tuple[os.stat_result, os.stat_result, int, int, str]: + with path.open("rb") as handle: + initial_stat = os.fstat(handle.fileno()) + offset, line_number = _resume_position(state, initial_stat, handle) + next_offset = offset + next_line = line_number + handle.seek(offset) + while line := handle.readline(): + if not line.endswith(b"\n"): + break + next_offset = handle.tell() + next_line += 1 + try: + raw = line.decode("utf-8") + except UnicodeDecodeError: + totals.diagnostics["otel_invalid_json"] += 1 + continue + result = parse_otlp_json_line(raw) + totals.diagnostics.update(result.diagnostics) + for completion in result.completions: + if _insert_completion(conn, completion, source_path, next_line): + totals.imported += 1 + else: + totals.duplicates += 1 + final_stat = os.fstat(handle.fileno()) + resume_anchor = _cursor_anchor(handle, next_offset) + return initial_stat, final_stat, next_offset, next_line, resume_anchor + + +def _cursor_anchor(handle: BinaryIO, parsed_offset: int) -> str: + """Hash a bounded suffix of committed bytes without changing the read position.""" + + anchor_start = max(parsed_offset - _CURSOR_ANCHOR_BYTES, 0) + original_position = handle.tell() + handle.seek(anchor_start) + payload = handle.read(parsed_offset - anchor_start) + handle.seek(original_position) + return hashlib.sha256(payload).hexdigest() + + +def _insert_completion( + conn: sqlite3.Connection, + completion: OtelCompletion, + source_path: str, + source_line: int, +) -> bool: + cursor = conn.execute( + """ + INSERT INTO otel_completion_events ( + fingerprint, + conversation_id, + event_timestamp, + input_tokens, + cached_input_tokens, + output_tokens, + reasoning_output_tokens, + model, + effort, + service_tier, + fast, + service_tier_source, + service_tier_confidence, + app_version, + source_path, + source_line, + match_status + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(fingerprint) DO NOTHING + """, + ( + completion.fingerprint, + completion.conversation_id, + completion.event_timestamp, + completion.input_tokens, + completion.cached_input_tokens, + completion.output_tokens, + completion.reasoning_output_tokens, + completion.model, + completion.effort, + completion.service_tier, + completion.fast, + completion.service_tier_source, + completion.service_tier_confidence, + completion.app_version, + source_path, + source_line, + completion.match_status, + ), + ) + return cursor.rowcount == 1 + + +def _upsert_source_cursor( + conn: sqlite3.Connection, + source_path: str, + stat: os.stat_result, + parsed_offset: int, + parsed_line: int, + resume_anchor: str, +) -> None: + conn.execute( + """ + INSERT INTO otel_completion_sources ( + source_path, device, inode, size, parsed_offset, parsed_line, + resume_anchor, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(source_path) DO UPDATE SET + device = excluded.device, + inode = excluded.inode, + size = excluded.size, + parsed_offset = excluded.parsed_offset, + parsed_line = excluded.parsed_line, + resume_anchor = excluded.resume_anchor, + updated_at = excluded.updated_at + """, + ( + source_path, + stat.st_dev, + stat.st_ino, + stat.st_size, + parsed_offset, + parsed_line, + resume_anchor, + datetime.now(timezone.utc).isoformat(), + ), + ) diff --git a/src/codex_usage_tracker/store/otel_reconciliation.py b/src/codex_usage_tracker/store/otel_reconciliation.py new file mode 100644 index 00000000..9f820a39 --- /dev/null +++ b/src/codex_usage_tracker/store/otel_reconciliation.py @@ -0,0 +1,182 @@ +"""Conservative correlation of staged OTel tiers to canonical usage calls.""" + +from __future__ import annotations + +import sqlite3 +from dataclasses import dataclass + +_TIER_COLUMNS = ( + "service_tier", + "fast", + "service_tier_source", + "service_tier_confidence", +) + + +@dataclass(frozen=True) +class OtelReconciliationResult: + matched: int = 0 + pending: int = 0 + ambiguous: int = 0 + conflicts: int = 0 + updated_usage_rows: int = 0 + + +@dataclass +class _MutableReconciliationTotals: + matched: int = 0 + pending: int = 0 + ambiguous: int = 0 + conflicts: int = 0 + updated_usage_rows: int = 0 + + def freeze(self) -> OtelReconciliationResult: + return OtelReconciliationResult( + matched=self.matched, + pending=self.pending, + ambiguous=self.ambiguous, + conflicts=self.conflicts, + updated_usage_rows=self.updated_usage_rows, + ) + + +def reconcile_otel_completions( + conn: sqlite3.Connection, +) -> OtelReconciliationResult: + """Enrich only completion identities that resolve to one canonical group.""" + + totals = _MutableReconciliationTotals() + rows = conn.execute( + """ + SELECT * + FROM otel_completion_events + WHERE match_status IN ('pending', 'ambiguous', 'matched') + ORDER BY source_path, source_line, fingerprint + """ + ).fetchall() + for completion in rows: + candidates = _candidate_rows(conn, completion) + group_ids = { + str(row["canonical_record_id"] or row["record_id"]) for row in candidates + } + if not candidates: + _set_match_state(conn, str(completion["fingerprint"]), "pending", None) + totals.pending += 1 + elif len(group_ids) != 1: + _set_match_state(conn, str(completion["fingerprint"]), "ambiguous", None) + totals.ambiguous += 1 + else: + _apply_to_canonical_group(conn, completion, group_ids.pop(), totals) + return totals.freeze() + + +def reset_otel_completion_matches(conn: sqlite3.Connection) -> None: + """Make every valid staged completion eligible for rebuild reconciliation.""" + + conn.execute( + """ + UPDATE otel_completion_events + SET match_status = 'pending', matched_record_id = NULL + WHERE match_status != 'invalid' + """ + ) + + +def _candidate_rows( + conn: sqlite3.Connection, completion: sqlite3.Row +) -> list[sqlite3.Row]: + return conn.execute( + """ + SELECT record_id, canonical_record_id + FROM usage_events + WHERE session_id = ? + AND input_tokens = ? + AND cached_input_tokens = ? + AND output_tokens = ? + AND reasoning_output_tokens = ? + AND (? IS NULL OR model IS NULL OR lower(model) = lower(?)) + AND (? IS NULL OR effort IS NULL OR lower(effort) = lower(?)) + """, + ( + completion["conversation_id"], + completion["input_tokens"], + completion["cached_input_tokens"], + completion["output_tokens"], + completion["reasoning_output_tokens"], + completion["model"], + completion["model"], + completion["effort"], + completion["effort"], + ), + ).fetchall() + + +def _apply_to_canonical_group( + conn: sqlite3.Connection, + completion: sqlite3.Row, + canonical_id: str, + totals: _MutableReconciliationTotals, +) -> None: + clones = conn.execute( + """ + SELECT record_id, service_tier, fast, + service_tier_source, service_tier_confidence + FROM usage_events + WHERE coalesce(nullif(canonical_record_id, ''), record_id) = ? + ORDER BY record_id + """, + (canonical_id,), + ).fetchall() + desired = tuple(completion[column] for column in _TIER_COLUMNS) + contradiction = any( + row[column] is not None and row[column] != expected + for row in clones + for column, expected in zip(_TIER_COLUMNS, desired, strict=True) + ) + if contradiction: + _set_match_state(conn, str(completion["fingerprint"]), "conflict", None) + totals.conflicts += 1 + return + + cursor = conn.execute( + """ + UPDATE usage_events + SET service_tier = coalesce(service_tier, ?), + fast = coalesce(fast, ?), + service_tier_source = coalesce(service_tier_source, ?), + service_tier_confidence = coalesce(service_tier_confidence, ?) + WHERE coalesce(nullif(canonical_record_id, ''), record_id) = ? + AND ( + service_tier IS NULL + OR fast IS NULL + OR service_tier_source IS NULL + OR service_tier_confidence IS NULL + ) + """, + (*desired, canonical_id), + ) + matched_record_id = str(clones[0]["record_id"]) + _set_match_state( + conn, + str(completion["fingerprint"]), + "matched", + matched_record_id, + ) + totals.matched += 1 + totals.updated_usage_rows += max(cursor.rowcount, 0) + + +def _set_match_state( + conn: sqlite3.Connection, + fingerprint: str, + status: str, + matched_record_id: str | None, +) -> None: + conn.execute( + """ + UPDATE otel_completion_events + SET match_status = ?, matched_record_id = ? + WHERE fingerprint = ? + """, + (status, matched_record_id, fingerprint), + ) diff --git a/src/codex_usage_tracker/store/otel_schema.py b/src/codex_usage_tracker/store/otel_schema.py new file mode 100644 index 00000000..0af8932a --- /dev/null +++ b/src/codex_usage_tracker/store/otel_schema.py @@ -0,0 +1,89 @@ +"""SQLite schema for aggregate OpenTelemetry completion enrichment.""" + +from __future__ import annotations + +import sqlite3 + +MIGRATION_NAMES = { + 30: "persist OTel completion tier enrichment", + 31: "persist OTel cursor continuity anchors", +} + +_USAGE_EVENT_TIER_COLUMNS = { + "service_tier": "TEXT", + "fast": "INTEGER", + "service_tier_source": "TEXT", + "service_tier_confidence": "TEXT", +} + + +def migrate_otel_completion_tiers(conn: sqlite3.Connection) -> None: + """Add nullable tier fields plus aggregate-only OTel staging tables.""" + + existing_usage_columns = { + str(row[1]) for row in conn.execute("PRAGMA table_info(usage_events)").fetchall() + } + for column, column_type in _USAGE_EVENT_TIER_COLUMNS.items(): + if column not in existing_usage_columns: + conn.execute( # nosec B608 - fixed migration column names + f"ALTER TABLE usage_events ADD COLUMN {column} {column_type}" + ) + conn.executescript( + """ + CREATE TABLE IF NOT EXISTS otel_completion_sources ( + source_path TEXT PRIMARY KEY, + device INTEGER NOT NULL, + inode INTEGER NOT NULL, + size INTEGER NOT NULL, + parsed_offset INTEGER NOT NULL, + parsed_line INTEGER NOT NULL, + resume_anchor TEXT, + updated_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS otel_completion_events ( + fingerprint TEXT PRIMARY KEY, + conversation_id TEXT, + event_timestamp TEXT, + input_tokens INTEGER, + cached_input_tokens INTEGER, + output_tokens INTEGER, + reasoning_output_tokens INTEGER, + model TEXT, + effort TEXT, + service_tier TEXT, + fast INTEGER, + service_tier_source TEXT, + service_tier_confidence TEXT, + app_version TEXT, + source_path TEXT NOT NULL, + source_line INTEGER NOT NULL, + match_status TEXT NOT NULL CHECK ( + match_status IN ('pending', 'matched', 'ambiguous', 'conflict', 'invalid') + ), + matched_record_id TEXT + ); + + CREATE INDEX IF NOT EXISTS idx_otel_completion_match_status + ON otel_completion_events(match_status); + CREATE INDEX IF NOT EXISTS idx_otel_completion_identity + ON otel_completion_events( + conversation_id, + input_tokens, + cached_input_tokens, + output_tokens, + reasoning_output_tokens + ); + """ + ) + + +def add_otel_cursor_resume_anchor(conn: sqlite3.Connection) -> None: + """Add a bounded-content continuity marker to existing OTel source cursors.""" + + source_columns = { + str(row[1]) + for row in conn.execute("PRAGMA table_info(otel_completion_sources)").fetchall() + } + if "resume_anchor" not in source_columns: + conn.execute("ALTER TABLE otel_completion_sources ADD COLUMN resume_anchor TEXT") diff --git a/src/codex_usage_tracker/store/refresh.py b/src/codex_usage_tracker/store/refresh.py index 1077874b..d3e427b8 100644 --- a/src/codex_usage_tracker/store/refresh.py +++ b/src/codex_usage_tracker/store/refresh.py @@ -6,15 +6,26 @@ from pathlib import Path from codex_usage_tracker.core.models import RefreshResult -from codex_usage_tracker.core.paths import DEFAULT_CODEX_HOME, DEFAULT_DB_PATH +from codex_usage_tracker.core.paths import ( + DEFAULT_CODEX_HOME, + DEFAULT_DB_PATH, + DEFAULT_OTEL_COMPLETIONS_DIR, +) from codex_usage_tracker.parser.api import find_session_logs, load_session_index from codex_usage_tracker.parser.state import compact_parser_diagnostics from codex_usage_tracker.store.api import ( + OTEL_REFRESH_COUNTER_KEYS, clear_content_index_rows, init_db, record_refresh_metadata, ) +from codex_usage_tracker.store.compression_revisions import touch_compression_revisions from codex_usage_tracker.store.connection import connect +from codex_usage_tracker.store.otel_ingest import ingest_otel_completion_files +from codex_usage_tracker.store.otel_reconciliation import ( + reconcile_otel_completions, + reset_otel_completion_matches, +) from codex_usage_tracker.store.refresh_callbacks import DerivedFactSyncCallback from codex_usage_tracker.store.refresh_parse import ( RefreshProgressCallback, @@ -29,6 +40,7 @@ def refresh_usage_index( db_path: Path = DEFAULT_DB_PATH, include_archived: bool = False, aggregate_only: bool = False, + otel_dir: Path | None = None, progress_callback: RefreshProgressCallback | None = None, derived_fact_sync: DerivedFactSyncCallback | None = None, ) -> RefreshResult: @@ -87,6 +99,27 @@ def refresh_usage_index( force_serial=True, derived_fact_sync=derived_fact_sync, ) + emit_refresh_progress( + progress_callback, + phase="otel", + status="running", + completed=0, + total=1, + message="Reconciling aggregate OTel completion tiers", + ) + resolved_otel_dir = otel_dir or db_path.parent / DEFAULT_OTEL_COMPLETIONS_DIR.name + otel_diagnostics = _refresh_otel_completions( + db_path=db_path, otel_dir=resolved_otel_dir + ) + emit_refresh_progress( + progress_callback, + phase="otel", + status="completed", + completed=1, + total=1, + message="Reconciled aggregate OTel completion tiers", + **otel_diagnostics, + ) emit_refresh_progress( progress_callback, phase="finalizing", @@ -102,6 +135,7 @@ def refresh_usage_index( stats=stream_result.stats, parsed_events=stream_result.parsed_events, inserted=stream_result.inserted_or_updated_events, + otel_diagnostics=otel_diagnostics, ) emit_refresh_progress( progress_callback, @@ -131,9 +165,13 @@ def _finalize_refresh_result( stats: dict[str, int], parsed_events: int, inserted: int, + otel_diagnostics: dict[str, int], ) -> RefreshResult: skipped_events = stats.get("skipped_events", 0) - diagnostics = compact_parser_diagnostics(stats) + diagnostics = { + **compact_parser_diagnostics(stats), + **{key: value for key, value in otel_diagnostics.items() if value}, + } record_refresh_metadata( db_path=db_path, scanned_files=scanned_files, @@ -141,6 +179,7 @@ def _finalize_refresh_result( skipped_events=skipped_events, inserted_or_updated_events=inserted, parser_diagnostics=diagnostics, + otel_diagnostics=otel_diagnostics, parsed_source_files=parsed_source_files, skipped_source_files=scanned_files - parsed_source_files, ) @@ -154,17 +193,39 @@ def _finalize_refresh_result( ) +def _refresh_otel_completions(*, db_path: Path, otel_dir: Path) -> dict[str, int]: + with connect(db_path) as conn: + init_db(conn) + ingest = ingest_otel_completion_files(conn, otel_dir) + reconciled = reconcile_otel_completions(conn) + if reconciled.updated_usage_rows: + touch_compression_revisions(conn, {"calls", "threads"}) + counters = { + "otel_files_scanned": ingest.files_scanned, + "otel_imported": ingest.imported, + "otel_duplicates": ingest.duplicates, + "otel_matched": reconciled.matched, + "otel_pending": reconciled.pending, + "otel_ambiguous": reconciled.ambiguous, + "otel_conflicts": reconciled.conflicts, + **ingest.diagnostics, + } + return {key: int(counters.get(key, 0)) for key in OTEL_REFRESH_COUNTER_KEYS} + + def rebuild_usage_index( codex_home: Path = DEFAULT_CODEX_HOME, db_path: Path = DEFAULT_DB_PATH, include_archived: bool = False, aggregate_only: bool = False, + otel_dir: Path | None = None, derived_fact_sync: DerivedFactSyncCallback | None = None, ) -> RefreshResult: """Clear aggregate rows and rescan local Codex logs.""" with connect(db_path) as conn: init_db(conn) + reset_otel_completion_matches(conn) clear_content_index_rows(conn) for table in ( "allowance_observations", @@ -183,5 +244,6 @@ def rebuild_usage_index( db_path=db_path, include_archived=include_archived, aggregate_only=aggregate_only, + otel_dir=otel_dir, derived_fact_sync=derived_fact_sync, ) diff --git a/src/codex_usage_tracker/store/schema.py b/src/codex_usage_tracker/store/schema.py index e0d1cddc..bff7d7ab 100644 --- a/src/codex_usage_tracker/store/schema.py +++ b/src/codex_usage_tracker/store/schema.py @@ -9,6 +9,7 @@ import codex_usage_tracker.store.allowance_schema as allowance_schema import codex_usage_tracker.store.compression_schema as compression_schema import codex_usage_tracker.store.deduplication_schema as deduplication_schema +import codex_usage_tracker.store.otel_schema as otel_schema import codex_usage_tracker.store.recommendation_schema as recommendation_schema import codex_usage_tracker.store.schema_query_indexes as schema_query_indexes from codex_usage_tracker.core.schema import ( @@ -18,7 +19,7 @@ USAGE_EVENT_SCHEMA_CHECKSUM, ) -SCHEMA_VERSION = 29 +SCHEMA_VERSION = 31 MIGRATION_NAMES = { 1: "create usage_events aggregate fact table", 2: "track schema migration checksum metadata", @@ -39,6 +40,7 @@ **schema_query_indexes.MIGRATION_NAMES, **deduplication_schema.MIGRATION_NAMES, **allowance_schema.MIGRATION_NAMES, + **otel_schema.MIGRATION_NAMES, } CALL_ORIGIN_REPAIR_COLUMNS: dict[str, str] = dict.fromkeys( ("call_initiator", "call_initiator_reason", "call_initiator_confidence"), "TEXT" @@ -116,6 +118,8 @@ def _schema_migrations() -> tuple[tuple[int, Callable[[sqlite3.Connection], None (27, allowance_schema.migrate_allowance_intelligence_v2), (28, allowance_schema.migrate_allowance_query_indexes_v3), (29, allowance_schema.add_allowance_plan_provenance), + (30, otel_schema.migrate_otel_completion_tiers), + (31, otel_schema.add_otel_cursor_resume_anchor), ) diff --git a/src/codex_usage_tracker/store/source_replacement.py b/src/codex_usage_tracker/store/source_replacement.py index 4eefa006..91c441c1 100644 --- a/src/codex_usage_tracker/store/source_replacement.py +++ b/src/codex_usage_tracker/store/source_replacement.py @@ -16,6 +16,10 @@ ) _SOURCE_FILE_SQL_BATCH_SIZE = 400 +OTEL_ENRICHMENT_COLUMNS = frozenset( + {"service_tier", "fast", "service_tier_source", "service_tier_confidence"} +) +OtelEnrichment = tuple[object | None, object | None, object | None, object | None] def source_file_strings(replace_source_files: Iterable[Path] | None) -> list[str]: @@ -38,6 +42,75 @@ def delete_usage_events_for_source_files( _rebuild_content_fts(conn) +def capture_otel_enrichment_for_source_files( + conn: sqlite3.Connection, + source_files_to_replace: list[str], +) -> dict[str, OtelEnrichment]: + """Capture one internally consistent non-null tier tuple per affected group.""" + + group_ids: set[str] = set() + for source_batch in _source_file_batches(source_files_to_replace): + placeholders = ", ".join("?" for _source in source_batch) + rows = conn.execute( + f""" + SELECT DISTINCT coalesce(nullif(canonical_record_id, ''), record_id) AS group_id + FROM usage_events + WHERE source_file IN ({placeholders}) + """, # nosec B608 - generated placeholders + source_batch, + ).fetchall() + group_ids.update(str(row["group_id"]) for row in rows) + + tuples_by_group: dict[str, set[OtelEnrichment]] = {} + for group_batch in _value_batches(sorted(group_ids)): + placeholders = ", ".join("?" for _group in group_batch) + rows = conn.execute( + f""" + SELECT coalesce(nullif(canonical_record_id, ''), record_id) AS group_id, + service_tier, fast, service_tier_source, service_tier_confidence + FROM usage_events + WHERE coalesce(nullif(canonical_record_id, ''), record_id) IN ({placeholders}) + """, # nosec B608 - generated placeholders + group_batch, + ).fetchall() + for row in rows: + enrichment: OtelEnrichment = ( + row["service_tier"], + row["fast"], + row["service_tier_source"], + row["service_tier_confidence"], + ) + tuples_by_group.setdefault(str(row["group_id"]), set()).add(enrichment) + + captured: dict[str, OtelEnrichment] = {} + for group_id, enrichments in tuples_by_group.items(): + if len(enrichments) != 1: + continue + enrichment = next(iter(enrichments)) + if any(value is not None for value in enrichment): + captured[group_id] = enrichment + return captured + + +def restore_otel_enrichment( + conn: sqlite3.Connection, + captured: Mapping[str, OtelEnrichment], +) -> None: + """Restore captured tiers to reparsed clones without overwriting fresh values.""" + + conn.executemany( + """ + UPDATE usage_events + SET service_tier = coalesce(service_tier, ?), + fast = coalesce(fast, ?), + service_tier_source = coalesce(service_tier_source, ?), + service_tier_confidence = coalesce(service_tier_confidence, ?) + WHERE coalesce(nullif(canonical_record_id, ''), record_id) = ? + """, + [(*enrichment, group_id) for group_id, enrichment in captured.items()], + ) + + def thread_keys_for_source_files( conn: sqlite3.Connection, source_files_to_replace: list[str], @@ -108,6 +181,11 @@ def _source_file_batches(source_files: list[str]) -> Iterator[list[str]]: yield source_files[start : start + _SOURCE_FILE_SQL_BATCH_SIZE] +def _value_batches(values: list[str]) -> Iterator[list[str]]: + for start in range(0, len(values), _SOURCE_FILE_SQL_BATCH_SIZE): + yield values[start : start + _SOURCE_FILE_SQL_BATCH_SIZE] + + def _usage_row_value( row: Mapping[str, object] | sqlite3.Row, key: str, diff --git a/src/codex_usage_tracker/usage_drain/spans.py b/src/codex_usage_tracker/usage_drain/spans.py index 6bd3d7d9..3b3f1023 100644 --- a/src/codex_usage_tracker/usage_drain/spans.py +++ b/src/codex_usage_tracker/usage_drain/spans.py @@ -337,7 +337,12 @@ def _span_from_rows( token_totals: dict[str, float] = dict.fromkeys(TOKEN_TOTAL_FIELDS, 0.0) timing_totals: dict[str, float] = dict.fromkeys(TIMING_TOTAL_FIELDS, 0.0) for row in rows: - credits = max(_span_number(row.get("usage_credits")), 0.0) + credit_field = ( + "standard_usage_credits" + if "standard_usage_credits" in row + else "usage_credits" + ) + credits = max(_span_number(row.get(credit_field)), 0.0) standard += credits _add_span_row_dimensions( row, diff --git a/src/codex_usage_tracker/usage_drain/types.py b/src/codex_usage_tracker/usage_drain/types.py index 87088c09..7f7a2e21 100644 --- a/src/codex_usage_tracker/usage_drain/types.py +++ b/src/codex_usage_tracker/usage_drain/types.py @@ -5,10 +5,12 @@ from dataclasses import asdict, dataclass, field from typing import Any -DOCUMENTED_FAST_CREDIT_MULTIPLIERS = { - "gpt-5.5": 2.5, - "gpt-5.4": 2.0, -} +from codex_usage_tracker.pricing.fast_tier import ( + DOCUMENTED_FAST_CREDIT_MULTIPLIERS as DOCUMENTED_FAST_CREDIT_MULTIPLIERS, +) +from codex_usage_tracker.pricing.fast_tier import ( + documented_fast_credit_multiplier as documented_fast_credit_multiplier, +) DEFAULT_PROXY_NAMES = ( "all_candidates", @@ -177,10 +179,3 @@ class PredictiveModelSpec: numeric_features: tuple[str, ...] categorical_features: tuple[str, ...] = () ridge_alpha: float = 1.0 - - -def documented_fast_credit_multiplier(model: object) -> float | None: - """Return the documented fast-mode credit multiplier for a model label.""" - - normalized = str(model or "").strip().lower() - return DOCUMENTED_FAST_CREDIT_MULTIPLIERS.get(normalized) diff --git a/tests/core/test_api_payloads.py b/tests/core/test_api_payloads.py new file mode 100644 index 00000000..35b444ca --- /dev/null +++ b/tests/core/test_api_payloads.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from codex_usage_tracker.core.api_payloads import refresh_result_payload +from codex_usage_tracker.core.models import RefreshResult + + +def test_refresh_payload_preserves_nonzero_otel_diagnostics() -> None: + result = RefreshResult( + scanned_files=1, + parsed_events=1, + inserted_or_updated_events=1, + db_path="/synthetic/usage.sqlite3", + parser_diagnostics={"otel_matched": 1}, + ) + + payload = refresh_result_payload(result, schema="synthetic-refresh-v1") + + assert payload["parser_diagnostics"] == {"otel_matched": 1} diff --git a/tests/core/test_schema.py b/tests/core/test_schema.py index a8b73ef8..74a36b21 100644 --- a/tests/core/test_schema.py +++ b/tests/core/test_schema.py @@ -51,3 +51,7 @@ def test_usage_event_schema_matches_persisted_row_shape() -> None: assert tuple(EVENT_COLUMNS) == USAGE_EVENT_COLUMN_NAMES assert tuple(event.to_row().keys()) == USAGE_EVENT_COLUMN_NAMES + assert event.to_row()["service_tier"] is None + assert event.to_row()["fast"] is None + assert event.to_row()["service_tier_source"] is None + assert event.to_row()["service_tier_confidence"] is None diff --git a/tests/dashboard/test_dashboard_payload.py b/tests/dashboard/test_dashboard_payload.py index 78108dd4..1ae5d656 100644 --- a/tests/dashboard/test_dashboard_payload.py +++ b/tests/dashboard/test_dashboard_payload.py @@ -10,7 +10,9 @@ connect, export_usage_csv, refresh_usage_index, + upsert_usage_events, ) +from tests.otel_helpers import synthetic_usage_event from tests.store_dashboard_helpers import ( _extract_js_function, _make_codex_home, @@ -658,6 +660,28 @@ def test_dashboard_and_csv_are_aggregate_only(tmp_path: Path) -> None: assert "Pricing snapshot changed since the previous dashboard render" in updated_dashboard +def test_dashboard_payload_keeps_tier_fields_aggregate_only(tmp_path: Path) -> None: + db_path = tmp_path / "usage.sqlite3" + upsert_usage_events( + [ + synthetic_usage_event( + "record-a", + "conversation-a", + (100, 40, 30, 10), + service_tier="priority", + fast=1, + ) + ], + db_path=db_path, + ) + + row = dashboard_payload(db_path=db_path)["rows"][0] + + assert row["service_tier"] == "priority" + assert row["fast"] == 1 + assert "otel_source_path" not in row + + def test_dashboard_payload_contract_includes_analysis_metadata(tmp_path: Path) -> None: codex_home = _make_codex_home(tmp_path) db_path = tmp_path / "usage.sqlite3" @@ -701,6 +725,15 @@ def test_dashboard_payload_contract_includes_analysis_metadata(tmp_path: Path) - "project_name", "project_key", "thread_attachment_label", + "standard_cost_usd", + "priority_cost_usd", + "pricing_service_tier", + "billing_basis", + "cost_semantics", + "fast_usage_credits", + "usage_credit_multiplier_source_url", + "usage_credit_multiplier_fetched_at", + "usage_credit_multiplier_confidence", } <= set(row) diff --git a/tests/otel_helpers.py b/tests/otel_helpers.py new file mode 100644 index 00000000..01dcd854 --- /dev/null +++ b/tests/otel_helpers.py @@ -0,0 +1,245 @@ +from __future__ import annotations + +import json +import sqlite3 +from collections.abc import Iterator +from contextlib import contextmanager +from pathlib import Path + +from codex_usage_tracker.core.models import UsageEvent +from codex_usage_tracker.store.connection import connect +from codex_usage_tracker.store.schema import init_db + + +def completion_attributes( + *, + conversation_id: str = "synthetic-conversation", + tokens: tuple[int, int, int, int] = (120, 40, 30, 10), + model: str = "gpt-5.6-sol", + effort: str = "high", + service_tier: str | None = "priority", + app_version: str = "0.143.0", +) -> dict[str, object]: + input_tokens, cached_tokens, output_tokens, reasoning_tokens = tokens + values: dict[str, object] = { + "event.name": "codex.sse_event", + "event.kind": "response.completed", + "conversation.id": conversation_id, + "input_token_count": input_tokens, + "cached_token_count": cached_tokens, + "output_token_count": output_tokens, + "reasoning_token_count": reasoning_tokens, + "model": model, + "model_reasoning_effort": effort, + "app.version": app_version, + } + if service_tier is not None: + values["service_tier"] = service_tier + return values + + +def _otlp_scalar(value: object) -> dict[str, object]: + if isinstance(value, bool): + return {"boolValue": value} + if isinstance(value, int): + return {"intValue": str(value)} + if isinstance(value, float): + return {"doubleValue": value} + return {"stringValue": str(value)} + + +def synthetic_otlp_line(*, attributes: dict[str, object], body: str = "synthetic body") -> str: + encoded = [ + {"key": key, "value": _otlp_scalar(value)} for key, value in attributes.items() + ] + return json.dumps( + { + "resourceLogs": [ + { + "scopeLogs": [ + { + "logRecords": [ + { + "timeUnixNano": "1784160000000000000", + "body": {"stringValue": body}, + "attributes": encoded, + } + ] + } + ] + } + ] + } + ) + + +def synthetic_fast_completion(conversation_id: str, input_tokens: int) -> str: + return synthetic_otlp_line( + attributes=completion_attributes( + conversation_id=conversation_id, + tokens=(input_tokens, 0, 20, 5), + service_tier="priority", + ) + ) + + +def synthetic_standard_completion(conversation_id: str, input_tokens: int) -> str: + return synthetic_otlp_line( + attributes=completion_attributes( + conversation_id=conversation_id, + tokens=(input_tokens, 0, 20, 5), + service_tier=None, + ) + ) + + +def synthetic_usage_event( + record_id: str, + conversation_id: str, + tokens: tuple[int, int, int, int], + *, + canonical: str = "canonical-a", + model: str = "gpt-5.6-sol", + effort: str = "high", + service_tier: str | None = None, + fast: int | None = None, + duplicate: int = 0, +) -> UsageEvent: + input_tokens, cached_tokens, output_tokens, reasoning_tokens = tokens + total_tokens = input_tokens + output_tokens + return UsageEvent( + record_id=record_id, + session_id=conversation_id, + thread_name="Synthetic thread", + session_updated_at="2026-07-16T00:00:00Z", + event_timestamp="2026-07-16T00:00:00Z", + source_file="/synthetic/session.jsonl", + line_number=1, + turn_id="synthetic-turn", + turn_timestamp="2026-07-16T00:00:00Z", + cwd="/synthetic/project", + model=model, + effort=effort, + current_date="2026-07-16", + timezone="UTC", + call_initiator="user", + call_initiator_reason="user_message", + call_initiator_confidence="high", + is_archived=0, + thread_key="thread:Synthetic", + thread_call_index=None, + previous_record_id=None, + next_record_id=None, + thread_source="user", + subagent_type=None, + agent_role=None, + agent_nickname=None, + parent_session_id=None, + parent_thread_name=None, + parent_session_updated_at=None, + model_context_window=258_400, + input_tokens=input_tokens, + cached_input_tokens=cached_tokens, + output_tokens=output_tokens, + reasoning_output_tokens=reasoning_tokens, + total_tokens=total_tokens, + cumulative_input_tokens=input_tokens, + cumulative_cached_input_tokens=cached_tokens, + cumulative_output_tokens=output_tokens, + cumulative_reasoning_output_tokens=reasoning_tokens, + cumulative_total_tokens=total_tokens, + usage_fingerprint=f"synthetic-fingerprint-{canonical}", + canonical_record_id=canonical, + is_duplicate=duplicate, + duplicate_reason="copied_usage_fingerprint" if duplicate else None, + service_tier=service_tier + or ("fast" if fast == 1 else "standard" if fast == 0 else None), + fast=fast, + service_tier_source="otel_response_completed" if fast is not None else None, + service_tier_confidence="exact" if fast is not None else None, + ) + + +def write_usage_session( + tmp_path: Path, + conversation_id: str, + tokens: tuple[int, int, int, int], +) -> Path: + input_tokens, cached_tokens, output_tokens, reasoning_tokens = tokens + total_tokens = input_tokens + output_tokens + codex_home = tmp_path / "codex" + log_path = codex_home / "sessions" / "2026" / "07" / "16" / "synthetic.jsonl" + rows = [ + { + "timestamp": "2026-07-16T00:00:00.000Z", + "type": "session_meta", + "payload": {"id": conversation_id}, + }, + { + "timestamp": "2026-07-16T00:00:01.000Z", + "type": "event_msg", + "payload": { + "type": "token_count", + "info": { + "total_token_usage": { + "input_tokens": input_tokens, + "cached_input_tokens": cached_tokens, + "output_tokens": output_tokens, + "reasoning_output_tokens": reasoning_tokens, + "total_tokens": total_tokens, + }, + "last_token_usage": { + "input_tokens": input_tokens, + "cached_input_tokens": cached_tokens, + "output_tokens": output_tokens, + "reasoning_output_tokens": reasoning_tokens, + "total_tokens": total_tokens, + }, + "model_context_window": 258_400, + }, + }, + }, + ] + log_path.parent.mkdir(parents=True, exist_ok=True) + log_path.write_text( + "".join(json.dumps(row) + "\n" for row in rows), encoding="utf-8" + ) + return codex_home + + +def write_otel_directory( + tmp_path: Path, + conversation_id: str, + tokens: tuple[int, int, int, int], +) -> Path: + directory = tmp_path / "otel" + write_lines( + directory / "codex-completions.jsonl", + [ + synthetic_otlp_line( + attributes=completion_attributes( + conversation_id=conversation_id, + tokens=tokens, + service_tier="priority", + ) + ) + ], + ) + return directory + + +def write_lines(path: Path, lines: list[str]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("".join(f"{line}\n" for line in lines), encoding="utf-8") + + +def append_text(path: Path, value: str) -> None: + with path.open("a", encoding="utf-8") as handle: + handle.write(value) + + +@contextmanager +def initialized_connection(tmp_path: Path) -> Iterator[sqlite3.Connection]: + with connect(tmp_path / "usage.sqlite3") as conn: + init_db(conn) + yield conn diff --git a/tests/parser/test_otel_parser.py b/tests/parser/test_otel_parser.py new file mode 100644 index 00000000..df801ab4 --- /dev/null +++ b/tests/parser/test_otel_parser.py @@ -0,0 +1,163 @@ +from __future__ import annotations + +import json + +import pytest + +from codex_usage_tracker.parser.otel import OTEL_DIAGNOSTIC_KEYS, parse_otlp_json_line +from tests.otel_helpers import completion_attributes, synthetic_otlp_line + + +def test_parse_otlp_batch_extracts_only_completion_allowlist() -> None: + raw = synthetic_otlp_line( + attributes={ + "event.name": "codex.sse_event", + "event.kind": "response.completed", + "conversation.id": "synthetic-conversation", + "input_token_count": 120, + "cached_token_count": 40, + "output_token_count": 30, + "reasoning_token_count": 10, + "model": "gpt-5.6-sol", + "model_reasoning_effort": "high", + "service_tier": "priority", + "app.version": "0.143.0", + "secret.attribute": "must-not-survive", + }, + body="synthetic private body that must not survive", + ) + + result = parse_otlp_json_line(raw) + + assert len(result.completions) == 1 + completion = result.completions[0] + assert completion.conversation_id == "synthetic-conversation" + assert completion.service_tier == "priority" + assert completion.fast == 1 + assert completion.service_tier_source == "otel_response_completed" + assert completion.service_tier_confidence == "exact" + assert completion.match_status == "pending" + assert "private body" not in repr(completion) + assert "secret.attribute" not in repr(completion) + + +@pytest.mark.parametrize( + ("version", "tier", "fast", "confidence", "status"), + [ + ("0.143.0", "standard", 0, "protocol", "pending"), + ("0.142.9", None, None, None, "invalid"), + ("bad", None, None, None, "invalid"), + ], +) +def test_missing_service_tier_uses_versioned_protocol_semantics( + version: str, + tier: str | None, + fast: int | None, + confidence: str | None, + status: str, +) -> None: + result = parse_otlp_json_line( + synthetic_otlp_line( + attributes=completion_attributes(app_version=version, service_tier=None) + ) + ) + + completion = result.completions[0] + assert (completion.service_tier, completion.fast, completion.service_tier_confidence) == ( + tier, + fast, + confidence, + ) + assert completion.match_status == status + + +@pytest.mark.parametrize( + ("raw_tier", "normalized_tier", "fast"), + [ + ("priority", "priority", 1), + ("fast", "fast", 1), + ("default", "default", 0), + ("standard", "standard", 0), + ("flex", "flex", 0), + ("batch", "batch", 0), + ], +) +def test_explicit_tier_names_are_preserved( + raw_tier: str, normalized_tier: str, fast: int +) -> None: + attributes = completion_attributes(service_tier=raw_tier) + + completion = parse_otlp_json_line( + synthetic_otlp_line(attributes=attributes) + ).completions[0] + + assert (completion.service_tier, completion.fast) == (normalized_tier, fast) + + +def test_multiple_resource_and_scope_groups_are_traversed() -> None: + first = json.loads( + synthetic_otlp_line(attributes=completion_attributes(conversation_id="a")) + ) + second = json.loads( + synthetic_otlp_line(attributes=completion_attributes(conversation_id="b")) + ) + first["resourceLogs"].extend(second["resourceLogs"]) + + result = parse_otlp_json_line(json.dumps(first)) + + assert [item.conversation_id for item in result.completions] == ["a", "b"] + + +@pytest.mark.parametrize("raw", ["{", "[]", json.dumps({"resourceLogs": "bad"})]) +def test_malformed_payloads_return_bounded_diagnostics(raw: str) -> None: + result = parse_otlp_json_line(raw) + + assert not result.completions + assert sum(result.diagnostics.values()) >= 1 + assert set(result.diagnostics) <= set(OTEL_DIAGNOSTIC_KEYS) + + +def test_non_completion_and_missing_identity_never_become_pending_matches() -> None: + unrelated = completion_attributes() + unrelated["event.kind"] = "response.created" + missing_identity = completion_attributes() + missing_identity.pop("conversation.id") + + assert not parse_otlp_json_line( + synthetic_otlp_line(attributes=unrelated) + ).completions + completion = parse_otlp_json_line( + synthetic_otlp_line(attributes=missing_identity) + ).completions[0] + assert completion.match_status == "invalid" + + +def test_present_invalid_tier_is_not_treated_as_protocol_omission() -> None: + attributes = completion_attributes(service_tier=None) + attributes["service_tier"] = 7 + + result = parse_otlp_json_line(synthetic_otlp_line(attributes=attributes)) + + completion = result.completions[0] + assert (completion.service_tier, completion.fast) == (None, None) + assert completion.match_status == "invalid" + assert result.diagnostics["otel_invalid_record"] >= 1 + + +def test_semantic_fingerprint_is_stable_and_changes_with_aggregate_identity() -> None: + attributes = completion_attributes() + reversed_attributes = dict(reversed(list(attributes.items()))) + changed_attributes = completion_attributes(tokens=(121, 40, 30, 10)) + + first = parse_otlp_json_line( + synthetic_otlp_line(attributes=attributes) + ).completions[0] + reordered = parse_otlp_json_line( + synthetic_otlp_line(attributes=reversed_attributes) + ).completions[0] + changed = parse_otlp_json_line( + synthetic_otlp_line(attributes=changed_attributes) + ).completions[0] + + assert first.fingerprint == reordered.fingerprint + assert first.fingerprint != changed.fingerprint diff --git a/tests/pricing/test_allowance.py b/tests/pricing/test_allowance.py index 3be25758..a9daa9cf 100644 --- a/tests/pricing/test_allowance.py +++ b/tests/pricing/test_allowance.py @@ -1,9 +1,14 @@ from __future__ import annotations import json +from dataclasses import replace from pathlib import Path +import pytest + from codex_usage_tracker.pricing.allowance import ( + FastMultiplierRate, + UsageAllowanceConfig, annotate_rows_with_allowance, load_allowance_config, parse_allowance_text, @@ -13,6 +18,164 @@ ) +def _credit_row( + *, model: str, fast: int | None, service_tier: str | None +) -> dict[str, object]: + return { + "model": model, + "input_tokens": 100, + "cached_input_tokens": 20, + "uncached_input_tokens": 80, + "output_tokens": 10, + "total_tokens": 110, + "fast": fast, + "service_tier": service_tier, + "service_tier_source": "otel_response_completed" if fast is not None else None, + "service_tier_confidence": "exact" if fast is not None else None, + } + + +def _synthetic_allowance_config() -> UsageAllowanceConfig: + rates = { + "input_per_million": 10.0, + "cached_input_per_million": 1.0, + "output_per_million": 50.0, + } + models = ( + "gpt-5.6", + "gpt-5.6-sol", + "gpt-5.5", + "gpt-5.4", + "synthetic-unknown", + ) + multiplier_source = { + "source_name": "OpenAI Codex Fast mode docs", + "source_url": "https://learn.chatgpt.com/docs/agent-configuration/speed", + "fetched_at": "2026-07-16", + "confidence": "exact", + } + return UsageAllowanceConfig( + path=Path("/synthetic/allowance.json"), + rate_card_path=Path("/synthetic/rate-card.json"), + credit_rates={model: dict(rates) for model in models}, + aliases={}, + rate_metadata={model: {} for model in models}, + alias_metadata={}, + windows=[], + loaded=True, + rate_card_loaded=True, + source={"name": "Synthetic credit rates"}, + fast_multipliers={ + "gpt-5.6": FastMultiplierRate(multiplier=2.5, **multiplier_source), + "gpt-5.5": FastMultiplierRate(multiplier=2.5, **multiplier_source), + "gpt-5.4": FastMultiplierRate(multiplier=2.0, **multiplier_source), + }, + ) + + +@pytest.mark.parametrize( + ("model", "multiplier"), + [ + ("gpt-5.6", 2.5), + ("gpt-5.6-sol", 2.5), + ("gpt-5.5", 2.5), + ("gpt-5.4", 2.0), + ], +) +def test_confirmed_fast_multiplies_standard_credit_estimate( + model: str, multiplier: float +) -> None: + row = _credit_row(model=model, fast=1, service_tier="fast") + + annotated = annotate_rows_with_allowance( + [row], _synthetic_allowance_config() + )[0] + + assert annotated["usage_credits"] == pytest.approx( + annotated["standard_usage_credits"] * multiplier + ) + assert annotated["usage_credit_multiplier"] == multiplier + assert annotated["usage_credit_multiplier_source"] == "OpenAI Codex Fast mode docs" + + +def test_fast_multiplier_provenance_comes_from_rate_card() -> None: + config = replace( + _synthetic_allowance_config(), + fast_multipliers={ + "gpt-5.6": FastMultiplierRate( + multiplier=2.5, + source_name="OpenAI Codex Fast mode docs", + source_url="https://learn.chatgpt.com/docs/agent-configuration/speed", + fetched_at="2026-07-16", + confidence="exact", + ) + }, + ) + + annotated = annotate_rows_with_allowance( + [_credit_row(model="gpt-5.6", fast=1, service_tier="priority")], + config, + )[0] + + assert annotated["usage_credit_multiplier"] == 2.5 + assert annotated["usage_credit_multiplier_source"] == "OpenAI Codex Fast mode docs" + assert annotated["usage_credit_multiplier_source_url"].endswith( + "/agent-configuration/speed" + ) + assert annotated["usage_credit_multiplier_fetched_at"] == "2026-07-16" + assert annotated["usage_credit_multiplier_confidence"] == "exact" + assert annotated["fast_usage_credits"] == pytest.approx( + annotated["standard_usage_credits"] * 2.5 + ) + assert annotated["service_tier_source"] == "otel_response_completed" + + +@pytest.mark.parametrize("fast", [0, None]) +def test_standard_and_unknown_rows_keep_multiplier_one(fast: int | None) -> None: + row = _credit_row( + model="gpt-5.6", + fast=fast, + service_tier="standard" if fast == 0 else None, + ) + + annotated = annotate_rows_with_allowance( + [row], _synthetic_allowance_config() + )[0] + + assert annotated["usage_credits"] == annotated["standard_usage_credits"] + assert annotated["usage_credit_multiplier"] == 1.0 + + +def test_confirmed_fast_unknown_model_does_not_invent_multiplier() -> None: + row = _credit_row(model="synthetic-unknown", fast=1, service_tier="fast") + + annotated = annotate_rows_with_allowance( + [row], _synthetic_allowance_config() + )[0] + + assert annotated["usage_credit_multiplier"] == 1.0 + assert ( + annotated["usage_credit_multiplier_source"] + == "no_documented_fast_multiplier" + ) + + +def test_unpriced_rows_include_bounded_multiplier_annotations() -> None: + row = _credit_row(model="not-in-rate-card", fast=1, service_tier="fast") + + annotated = annotate_rows_with_allowance( + [row], _synthetic_allowance_config() + )[0] + + assert annotated["usage_credits"] is None + assert annotated["standard_usage_credits"] is None + assert annotated["usage_credit_multiplier"] == 1.0 + assert ( + annotated["usage_credit_multiplier_source"] + == "no_documented_fast_multiplier" + ) + + def test_allowance_estimates_exact_codex_credit_usage() -> None: rows = annotate_rows_with_allowance( [ @@ -224,6 +387,7 @@ def test_update_rate_card_writes_bundled_snapshot(tmp_path: Path) -> None: assert config.credit_rates["gpt-5.6-sol"]["output_per_million"] == 1500.0 assert config.credit_rates["gpt-5.6-terra"]["output_per_million"] == 125.0 assert config.credit_rates["gpt-5.6-luna"]["output_per_million"] == 300.0 + assert config.fast_multipliers["gpt-5.6"].multiplier == 2.5 def test_write_allowance_template_refuses_to_overwrite(tmp_path: Path) -> None: diff --git a/tests/pricing/test_pricing.py b/tests/pricing/test_pricing.py index 6f9e4f9c..9ea32633 100644 --- a/tests/pricing/test_pricing.py +++ b/tests/pricing/test_pricing.py @@ -11,6 +11,7 @@ OPENAI_LATEST_MODEL_MD_URL, OPENAI_PRICING_MD_URL, PRICING_SCHEMA, + PricingConfig, PricingParseError, annotate_rows_with_efficiency, efficiency_flags, @@ -42,9 +43,190 @@ ["gpt-5.5 (<272K context length)", 2.5, 0.25, 15], ]} /> + + """ +def test_service_tier_does_not_change_estimated_cost_usd() -> None: + pricing = PricingConfig( + path=Path("/synthetic/pricing.json"), + models={ + "gpt-5.6": { + "input_per_million": 10.0, + "cached_input_per_million": 1.0, + "output_per_million": 50.0, + } + }, + loaded=True, + ) + row = _credit_row_for_cost(fast=0, service_tier="standard") + + standard = estimate_cost_usd(row, pricing) + fast = estimate_cost_usd( + {**row, "fast": 1, "service_tier": "fast"}, pricing + ) + + assert fast == standard + + +def test_pricing_v2_selects_rates_by_service_tier(tmp_path: Path) -> None: + pricing_path = tmp_path / "pricing.json" + standard = { + "input_per_million": 5.0, + "cached_input_per_million": 0.5, + "output_per_million": 30.0, + } + priority = { + "input_per_million": 10.0, + "cached_input_per_million": 1.0, + "output_per_million": 60.0, + } + pricing_path.write_text( + json.dumps( + { + "_schema": "codex-usage-tracker-pricing-v2", + "billing_basis": "api_tokens", + "models": {"gpt-5.6-sol": standard}, + "api_service_tiers": { + "standard": {"gpt-5.6-sol": standard}, + "priority": {"gpt-5.6-sol": priority}, + }, + "aliases": {"gpt-5.6": "gpt-5.6-sol"}, + } + ), + encoding="utf-8", + ) + + config = load_pricing_config(pricing_path) + + assert config.billing_basis == "api_tokens" + assert config.rates_for("gpt-5.6", service_tier="priority") == priority + assert config.rates_for("gpt-5.6", service_tier="fast") == priority + assert config.rates_for("gpt-5.6", service_tier="default") == standard + + +def test_tiered_pricing_does_not_fallback_for_an_unconfigured_tier( + tmp_path: Path, +) -> None: + selected = { + "input_per_million": 5.0, + "cached_input_per_million": 0.5, + "output_per_million": 20.0, + } + pricing_path = tmp_path / "pricing.json" + pricing_path.write_text( + json.dumps( + { + "models": {"gpt-5.6": selected}, + "api_service_tiers": {"standard": {"gpt-5.6": selected}}, + } + ), + encoding="utf-8", + ) + + config = load_pricing_config(pricing_path) + + assert config.rates_for("gpt-5.6", service_tier="flex") is None + assert config.pricing_tier_for("flex") is None + + +def test_pricing_v1_keeps_selected_projection_for_every_row_tier(tmp_path: Path) -> None: + pricing_path = tmp_path / "pricing.json" + selected = { + "input_per_million": 5.0, + "cached_input_per_million": 0.5, + "output_per_million": 30.0, + } + pricing_path.write_text( + json.dumps({"_schema": "codex-usage-tracker-pricing-v1", "models": {"gpt": selected}}), + encoding="utf-8", + ) + + config = load_pricing_config(pricing_path) + + assert config.rates_for("gpt", service_tier="priority") == selected + assert config.billing_basis == "unknown" + + +def _credit_row_for_cost( + *, fast: int | None, service_tier: str | None +) -> dict[str, object]: + return { + "record_id": "synthetic-call", + "model": "gpt-5.6", + "input_tokens": 100, + "cached_input_tokens": 20, + "uncached_input_tokens": 80, + "output_tokens": 10, + "fast": fast, + "service_tier": service_tier, + } + + +def _tiered_pricing_config(*, billing_basis: str = "unknown") -> PricingConfig: + standard = { + "input_per_million": 5.0, + "cached_input_per_million": 0.5, + "output_per_million": 30.0, + } + priority = { + "input_per_million": 10.0, + "cached_input_per_million": 1.0, + "output_per_million": 60.0, + } + return PricingConfig( + path=Path("/synthetic/pricing-v2.json"), + models={"gpt-5.6": standard}, + loaded=True, + api_service_tiers={ + "standard": {"gpt-5.6": standard}, + "priority": {"gpt-5.6": priority}, + }, + billing_basis=billing_basis, + ) + + +def test_pricing_v2_service_tier_selects_api_cost() -> None: + pricing = _tiered_pricing_config(billing_basis="api_tokens") + standard = estimate_cost_usd( + _credit_row_for_cost(fast=0, service_tier="standard"), pricing + ) + priority = estimate_cost_usd( + _credit_row_for_cost(fast=1, service_tier="priority"), pricing + ) + + assert priority == pytest.approx(float(standard) * 2) + + +def test_efficiency_annotation_exposes_tier_cost_scenarios() -> None: + annotated = annotate_rows_with_efficiency( + [_credit_row_for_cost(fast=1, service_tier="priority")], + _tiered_pricing_config(), + )[0] + + assert annotated["estimated_cost_usd"] == annotated["priority_cost_usd"] + assert annotated["priority_cost_usd"] == pytest.approx( + annotated["standard_cost_usd"] * 2 + ) + assert annotated["pricing_service_tier"] == "priority" + assert annotated["billing_basis"] == "unknown" + assert annotated["cost_semantics"] == "api_token_estimate" + + def test_pricing_fetch_rejects_non_https_sources() -> None: with pytest.raises(ValueError, match="must use HTTPS"): pricing_openai._fetch_text("file:///tmp/pricing.md") @@ -95,6 +277,14 @@ def test_parse_openai_pricing_markdown_uses_requested_tier() -> None: } +def test_parse_all_openai_pricing_tiers() -> None: + tiers = pricing_openai.parse_all_openai_pricing_tiers(OPENAI_PRICING_FIXTURE) + + assert set(tiers) == {"standard", "batch", "flex", "priority"} + assert tiers["standard"]["gpt-5.6-sol"]["input_per_million"] == 5 + assert tiers["priority"]["gpt-5.6-sol"]["input_per_million"] == 10 + + def test_parse_openai_pricing_markdown_does_not_invent_priority_long_context_rates() -> None: source = ( OPENAI_PRICING_FIXTURE @@ -171,6 +361,10 @@ def test_update_pricing_from_openai_docs_writes_source_metadata(tmp_path: Path) assert raw["_source"]["url"] == OPENAI_PRICING_MD_URL assert raw["_source"]["tier"] == "standard" assert raw["_source"]["estimated_model_count"] == 2 + assert raw["_source"]["tiers"] == ["standard", "batch", "flex", "priority"] + assert set(raw["api_service_tiers"]) == {"standard", "batch", "flex", "priority"} + assert raw["api_service_tiers"]["priority"]["gpt-5.6-sol"]["input_per_million"] == 10 + assert raw["billing_basis"] == "unknown" assert raw["aliases"]["gpt-5.6"] == "gpt-5.6-sol" assert raw["models"]["codex-auto-review"] == ESTIMATED_MODEL_PRICES["codex-auto-review"] assert raw["models"]["gpt-5.3-codex-spark"] == ESTIMATED_MODEL_PRICES["gpt-5.3-codex-spark"] @@ -185,6 +379,23 @@ def test_update_pricing_from_openai_docs_writes_source_metadata(tmp_path: Path) assert config.is_estimated_model("gpt-5.3-codex-spark") +def test_update_pricing_preserves_existing_billing_basis(tmp_path: Path) -> None: + pricing_path = tmp_path / "pricing.json" + pricing_path.write_text( + json.dumps({"billing_basis": "chatgpt_credits", "models": {}}), + encoding="utf-8", + ) + + update_pricing_from_openai_docs( + pricing_path, + fetch_text=lambda url: OPENAI_PRICING_FIXTURE, + include_estimates=False, + ) + + raw = json.loads(pricing_path.read_text(encoding="utf-8")) + assert raw["billing_basis"] == "chatgpt_credits" + + def test_update_pricing_from_openai_docs_can_skip_estimates(tmp_path: Path) -> None: pricing_path = tmp_path / "pricing.json" diff --git a/tests/pricing/test_rate_card.py b/tests/pricing/test_rate_card.py new file mode 100644 index 00000000..e12333d3 --- /dev/null +++ b/tests/pricing/test_rate_card.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +import json +from pathlib import Path + +from codex_usage_tracker.pricing.allowance import load_allowance_config + + +def test_local_fast_multiplier_override_is_source_stamped(tmp_path: Path) -> None: + path = tmp_path / "allowance.json" + path.write_text( + json.dumps( + { + "fast_multipliers": { + "gpt-5.6": { + "multiplier": 3.0, + "source_name": "Synthetic override", + "source_url": "https://example.invalid/synthetic-fast-rate", + "fetched_at": "2026-07-16", + } + } + } + ), + encoding="utf-8", + ) + + config = load_allowance_config( + path, rate_card_path=tmp_path / "missing-rate-card.json" + ) + rate = config.fast_multipliers["gpt-5.6"] + + assert rate.multiplier == 3.0 + assert rate.source_name == "Synthetic override" + assert rate.source_url == "https://example.invalid/synthetic-fast-rate" + assert rate.fetched_at == "2026-07-16" + assert rate.confidence == "user_override" + + +def test_malformed_local_multiplier_retains_valid_bundled_rate(tmp_path: Path) -> None: + path = tmp_path / "allowance.json" + path.write_text( + json.dumps( + {"fast_multipliers": {"gpt-5.6": {"multiplier": 0.5}}} + ), + encoding="utf-8", + ) + + config = load_allowance_config( + path, rate_card_path=tmp_path / "missing-rate-card.json" + ) + + assert config.loaded is True + assert config.fast_multipliers["gpt-5.6"].multiplier == 2.5 + assert config.fast_multipliers["gpt-5.6"].confidence == "exact" + + +def test_boolean_local_multiplier_retains_valid_bundled_rate(tmp_path: Path) -> None: + path = tmp_path / "allowance.json" + path.write_text( + json.dumps( + {"fast_multipliers": {"gpt-5.6": {"multiplier": True}}} + ), + encoding="utf-8", + ) + + config = load_allowance_config( + path, rate_card_path=tmp_path / "missing-rate-card.json" + ) + + assert config.loaded is True + assert config.fast_multipliers["gpt-5.6"].multiplier == 2.5 + assert config.fast_multipliers["gpt-5.6"].confidence == "exact" + + +def test_legacy_local_rate_card_inherits_bundled_fast_multipliers( + tmp_path: Path, +) -> None: + rate_card_path = tmp_path / "legacy-rate-card.json" + rate_card_path.write_text( + json.dumps( + { + "schema": "codex-usage-tracker-codex-rate-card-v1", + "credit_rates": { + "gpt-5.6": { + "input_per_million": 1, + "cached_input_per_million": 1, + "output_per_million": 1, + } + }, + "aliases": {}, + } + ), + encoding="utf-8", + ) + + config = load_allowance_config( + tmp_path / "missing-allowance.json", rate_card_path=rate_card_path + ) + + assert config.rate_card_loaded is True + assert config.fast_multipliers["gpt-5.6"].multiplier == 2.5 diff --git a/tests/reports/test_support.py b/tests/reports/test_support.py index 7eb5aa46..b7fcea96 100644 --- a/tests/reports/test_support.py +++ b/tests/reports/test_support.py @@ -57,7 +57,10 @@ def test_support_bundle_default_mode_contract_and_secret_safety(tmp_path: Path) assert bundle["database"]["exists"] is True assert bundle["refresh"]["parsed_events"] == "1" assert bundle["pricing"]["loaded"] is True + assert bundle["pricing"]["billing_basis"] == "unknown" + assert bundle["pricing"]["api_service_tier_count"] == 0 assert bundle["allowance"]["window_count"] == 0 + assert bundle["allowance"]["fast_multiplier_count"] == 3 assert "low_cache_ratio" in bundle["thresholds"]["keys"] assert bundle["projects"]["tag_group_count"] == 1 assert bundle["doctor"]["schema"] == "codex-usage-tracker-doctor-v1" @@ -119,6 +122,19 @@ def test_support_bundle_strict_mode_redacts_local_paths_and_doctor_text( assert raw_path not in bundle_text +def test_support_bundle_exposes_counts_without_otel_identifiers(tmp_path: Path) -> None: + payload = support_bundle_payload( + db_path=tmp_path / "usage.sqlite3", codex_home=tmp_path / "codex" + ) + encoded = json.dumps(payload, sort_keys=True) + otel_encoded = json.dumps(payload["otel"], sort_keys=True) + + assert payload["otel"]["completion_directory_exists"] in {True, False} + assert "conversation-a" not in encoded + assert "fingerprint" not in otel_encoded + assert "source_path" not in otel_encoded + + def _make_support_fixture(tmp_path: Path) -> dict[str, Path]: codex_home = tmp_path / ".codex" db_path = tmp_path / "usage.sqlite3" diff --git a/tests/server/test_server_call_detail.py b/tests/server/test_server_call_detail.py index 8bd9dda5..6b59df76 100644 --- a/tests/server/test_server_call_detail.py +++ b/tests/server/test_server_call_detail.py @@ -8,6 +8,8 @@ import pytest from codex_usage_tracker.server import call_detail as server_call_detail +from codex_usage_tracker.store.api import upsert_usage_events +from tests.otel_helpers import synthetic_usage_event class _RouteSenders: @@ -153,6 +155,27 @@ def test_call_detail_payload_requires_record_id(tmp_path: Path) -> None: ) +def test_call_detail_exposes_tier_without_sidecar_identity(tmp_path: Path) -> None: + db_path = tmp_path / "usage.sqlite3" + upsert_usage_events( + [ + synthetic_usage_event( + "record-a", "conversation-a", (100, 40, 30, 10), fast=1 + ) + ], + db_path=db_path, + ) + + payload = server_call_detail.call_detail_payload( + "record_id=record-a", db_path=db_path, annotate_rows=lambda rows: rows + ) + + record = payload["record"] + assert record["service_tier"] == "fast" + assert record["service_tier_confidence"] == "exact" + assert "source_path" not in record + + def test_call_detail_payload_raises_when_record_missing( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, diff --git a/tests/store/test_compression_runs.py b/tests/store/test_compression_runs.py index 327963a3..c8388079 100644 --- a/tests/store/test_compression_runs.py +++ b/tests/store/test_compression_runs.py @@ -28,7 +28,7 @@ update_compression_run, ) from codex_usage_tracker.store.connection import connect -from codex_usage_tracker.store.schema import init_db +from codex_usage_tracker.store.schema import SCHEMA_VERSION, init_db class CacheKey(TypedDict): @@ -171,7 +171,7 @@ def test_candidate_record_metadata_migration_backfills_existing_claims( conn.execute("PRAGMA user_version = 18") with connect(db_path) as conn: init_db(conn) - assert conn.execute("PRAGMA user_version").fetchone()[0] == 29 + assert conn.execute("PRAGMA user_version").fetchone()[0] == SCHEMA_VERSION with connect(db_path) as conn: conn.execute("DELETE FROM usage_events WHERE record_id = ?", ("record-cmp_old",)) diff --git a/tests/store/test_content_query_exports.py b/tests/store/test_content_query_exports.py index 8ca2ce01..4fcedec7 100644 --- a/tests/store/test_content_query_exports.py +++ b/tests/store/test_content_query_exports.py @@ -1,4 +1,10 @@ +from __future__ import annotations + +import csv +from pathlib import Path + from codex_usage_tracker.store import content_index +from codex_usage_tracker.store.api import export_usage_csv, upsert_usage_events from codex_usage_tracker.store.content_index_models import ContentIndexPlan, ContentIndexResult from codex_usage_tracker.store.content_persistence import ( clear_content_index_rows, @@ -9,6 +15,7 @@ search_content_fragments, ) from codex_usage_tracker.store.content_trace import ContentTraceResult, trace_thread_content +from tests.otel_helpers import synthetic_usage_event def test_content_index_preserves_query_exports() -> None: @@ -23,3 +30,28 @@ def test_content_index_preserves_query_exports() -> None: content_index.delete_content_index_rows_for_source_files is delete_content_index_rows_for_source_files ) + + +def test_csv_export_includes_additive_service_tier_fields(tmp_path: Path) -> None: + db_path = tmp_path / "usage.sqlite3" + upsert_usage_events( + [ + synthetic_usage_event( + "record-a", "conversation-a", (100, 40, 30, 10), fast=1 + ) + ], + db_path=db_path, + ) + output_path = tmp_path / "usage.csv" + + export_usage_csv(output_path, db_path=db_path) + + header = next(csv.reader(output_path.read_text(encoding="utf-8").splitlines())) + assert [ + name for name in header if name.startswith("service_tier") or name == "fast" + ] == [ + "service_tier", + "fast", + "service_tier_source", + "service_tier_confidence", + ] diff --git a/tests/store/test_otel_ingest.py b/tests/store/test_otel_ingest.py new file mode 100644 index 00000000..2b5a2736 --- /dev/null +++ b/tests/store/test_otel_ingest.py @@ -0,0 +1,240 @@ +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from codex_usage_tracker.store.otel_ingest import ( + discover_otel_sources, + ingest_otel_completion_files, +) +from tests.otel_helpers import ( + append_text, + completion_attributes, + initialized_connection, + synthetic_fast_completion, + synthetic_otlp_line, + synthetic_standard_completion, + write_lines, +) + + +def test_discovery_is_bounded_and_deterministic(tmp_path: Path) -> None: + directory = tmp_path / "otel" + write_lines(directory / "codex-completions.jsonl", []) + write_lines(directory / "codex-completions-20260716.jsonl", []) + write_lines(directory / "unrelated.jsonl", []) + + assert [path.name for path in discover_otel_sources(directory)] == [ + "codex-completions-20260716.jsonl", + "codex-completions.jsonl", + ] + assert discover_otel_sources(tmp_path / "missing") == [] + + +def test_incremental_ingest_reads_each_complete_line_once(tmp_path: Path) -> None: + directory = tmp_path / "otel" + source = directory / "codex-completions.jsonl" + write_lines(source, [synthetic_fast_completion("conversation-a", 100)]) + with initialized_connection(tmp_path) as conn: + first = ingest_otel_completion_files(conn, directory) + append_text(source, synthetic_standard_completion("conversation-b", 200) + "\n") + second = ingest_otel_completion_files(conn, directory) + rows = conn.execute("SELECT fingerprint FROM otel_completion_events").fetchall() + + assert first.imported == 1 + assert second.imported == 1 + assert second.duplicates == 0 + assert len(rows) == 2 + + +def test_partial_last_line_is_retried_after_append(tmp_path: Path) -> None: + directory = tmp_path / "otel" + source = directory / "codex-completions.jsonl" + complete = synthetic_otlp_line(attributes=completion_attributes()) + source.parent.mkdir(parents=True) + source.write_text(complete[:-1], encoding="utf-8") + with initialized_connection(tmp_path) as conn: + assert ingest_otel_completion_files(conn, directory).imported == 0 + source.write_text(complete + "\n", encoding="utf-8") + assert ingest_otel_completion_files(conn, directory).imported == 1 + + +def test_rotation_and_reread_do_not_duplicate_semantic_completion(tmp_path: Path) -> None: + directory = tmp_path / "otel" + current = directory / "codex-completions.jsonl" + rotated = directory / "codex-completions-20260716.jsonl" + line = synthetic_otlp_line(attributes=completion_attributes()) + "\n" + current.parent.mkdir(parents=True) + current.write_text(line, encoding="utf-8") + with initialized_connection(tmp_path) as conn: + ingest_otel_completion_files(conn, directory) + current.replace(rotated) + current.write_text(line, encoding="utf-8") + result = ingest_otel_completion_files(conn, directory) + count = conn.execute("SELECT COUNT(*) FROM otel_completion_events").fetchone()[0] + + assert count == 1 + assert result.duplicates >= 1 + + +def test_truncation_or_inode_replacement_resets_cursor_safely(tmp_path: Path) -> None: + directory = tmp_path / "otel" + source = directory / "codex-completions.jsonl" + source.parent.mkdir(parents=True) + source.write_text( + synthetic_otlp_line( + attributes=completion_attributes(conversation_id="synthetic-a") + ) + + "\n", + encoding="utf-8", + ) + with initialized_connection(tmp_path) as conn: + ingest_otel_completion_files(conn, directory) + source.unlink() + source.write_text( + synthetic_otlp_line( + attributes=completion_attributes(conversation_id="synthetic-b") + ) + + "\n", + encoding="utf-8", + ) + assert ingest_otel_completion_files(conn, directory).imported == 1 + + +def test_same_inode_same_size_rewrite_resets_cursor_safely(tmp_path: Path) -> None: + directory = tmp_path / "otel" + source = directory / "codex-completions.jsonl" + first_line = synthetic_otlp_line( + attributes=completion_attributes(conversation_id="synthetic-a") + ) + replacement_line = synthetic_otlp_line( + attributes=completion_attributes(conversation_id="synthetic-b") + ) + assert len(first_line) == len(replacement_line) + source.parent.mkdir(parents=True) + source.write_text(first_line + "\n", encoding="utf-8") + + with initialized_connection(tmp_path) as conn: + ingest_otel_completion_files(conn, directory) + first_stat = source.stat() + source.write_text(replacement_line + "\n", encoding="utf-8") + replacement_stat = source.stat() + result = ingest_otel_completion_files(conn, directory) + + assert first_stat.st_ino == replacement_stat.st_ino + assert first_stat.st_size == replacement_stat.st_size + assert result.imported == 1 + + +def test_legacy_cursor_without_anchor_is_reread_once(tmp_path: Path) -> None: + directory = tmp_path / "otel" + source = directory / "codex-completions.jsonl" + write_lines(source, [synthetic_otlp_line(attributes=completion_attributes())]) + + with initialized_connection(tmp_path) as conn: + assert ingest_otel_completion_files(conn, directory).imported == 1 + conn.execute("UPDATE otel_completion_sources SET resume_anchor = NULL") + + legacy_resume = ingest_otel_completion_files(conn, directory) + anchored_resume = ingest_otel_completion_files(conn, directory) + + assert legacy_resume.imported == 0 + assert legacy_resume.duplicates == 1 + assert anchored_resume.imported == 0 + assert anchored_resume.duplicates == 0 + + +def test_rotation_between_discovery_and_open_reads_replacement_from_zero( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + directory = tmp_path / "otel" + source = directory / "codex-completions.jsonl" + replacement = tmp_path / "replacement.jsonl" + write_lines(source, [synthetic_fast_completion("conversation-a", 100)]) + + with initialized_connection(tmp_path) as conn: + ingest_otel_completion_files(conn, directory) + write_lines( + replacement, + [ + synthetic_fast_completion( + "conversation-b-with-a-longer-synthetic-identifier", 200 + ), + synthetic_standard_completion("conversation-c", 300), + ], + ) + real_open = Path.open + rotated = False + + def rotate_before_open(path: Path, *args: object, **kwargs: object): + nonlocal rotated + if path == source and args and args[0] == "rb" and not rotated: + rotated = True + source.unlink() + replacement.replace(source) + return real_open(path, *args, **kwargs) + + monkeypatch.setattr(Path, "open", rotate_before_open) + result = ingest_otel_completion_files(conn, directory) + event_count = conn.execute( + "SELECT COUNT(*) FROM otel_completion_events" + ).fetchone()[0] + cursor = conn.execute( + """ + SELECT device, inode, parsed_offset + FROM otel_completion_sources + WHERE source_path = ? + """, + (str(source.resolve()),), + ).fetchone() + + source_stat = source.stat() + assert result.imported == 2 + assert event_count == 3 + assert tuple(cursor) == ( + source_stat.st_dev, + source_stat.st_ino, + source_stat.st_size, + ) + + +def test_ingest_never_persists_body_or_arbitrary_attributes(tmp_path: Path) -> None: + directory = tmp_path / "otel" + attributes = completion_attributes() + attributes["secret.attribute"] = "synthetic-secret-value" + write_lines( + directory / "codex-completions.jsonl", + [ + synthetic_otlp_line( + attributes=attributes, body="synthetic-private-body" + ) + ], + ) + with initialized_connection(tmp_path) as conn: + ingest_otel_completion_files(conn, directory) + rows = conn.execute("SELECT * FROM otel_completion_events").fetchall() + encoded = json.dumps([dict(row) for row in rows], sort_keys=True) + + assert "synthetic-private-body" not in encoded + assert "synthetic-secret-value" not in encoded + assert "secret.attribute" not in encoded + + +def test_cursor_advances_past_complete_invalid_lines(tmp_path: Path) -> None: + directory = tmp_path / "otel" + source = directory / "codex-completions.jsonl" + write_lines(source, ["{"]) + + with initialized_connection(tmp_path) as conn: + first = ingest_otel_completion_files(conn, directory) + second = ingest_otel_completion_files(conn, directory) + cursor = conn.execute( + "SELECT parsed_line, parsed_offset FROM otel_completion_sources" + ).fetchone() + + assert first.diagnostics["otel_invalid_json"] == 1 + assert second.diagnostics == {} + assert tuple(cursor) == (1, source.stat().st_size) diff --git a/tests/store/test_otel_reconciliation.py b/tests/store/test_otel_reconciliation.py new file mode 100644 index 00000000..b9ac904e --- /dev/null +++ b/tests/store/test_otel_reconciliation.py @@ -0,0 +1,269 @@ +from __future__ import annotations + +import hashlib +import sqlite3 +from dataclasses import replace +from pathlib import Path + +from codex_usage_tracker.store.api import EVENT_COLUMNS, query_usage_record, upsert_usage_events +from codex_usage_tracker.store.otel_reconciliation import ( + reconcile_otel_completions, + reset_otel_completion_matches, +) +from tests.otel_helpers import initialized_connection, synthetic_usage_event + + +def insert_usage_clone_group( + conn: sqlite3.Connection, + conversation_id: str, + tokens: tuple[int, int, int, int], + *, + canonical: str = "canonical-a", + model: str | None = "gpt-5.6-sol", + effort: str | None = "high", + service_tier: str | None = None, + fast: int | None = None, +) -> None: + events = [ + synthetic_usage_event( + f"{canonical}-record-{index}", + conversation_id, + tokens, + canonical=canonical, + model=model or "", + effort=effort or "", + service_tier=service_tier, + fast=fast, + duplicate=int(index == 1), + ) + for index in range(2) + ] + rows = [event.to_row() for event in events] + if model is None: + for row in rows: + row["model"] = None + if effort is None: + for row in rows: + row["effort"] = None + placeholders = ", ".join("?" for _column in EVENT_COLUMNS) + conn.executemany( + f"INSERT INTO usage_events ({', '.join(EVENT_COLUMNS)}) VALUES ({placeholders})", # nosec B608 + [[row[column] for column in EVENT_COLUMNS] for row in rows], + ) + + +def stage_completion( + conn: sqlite3.Connection, + conversation_id: str, + tokens: tuple[int, int, int, int], + *, + fast: int, + model: str | None = "gpt-5.6-sol", + effort: str | None = "high", + event_timestamp: str = "2026-07-16T00:00:00Z", +) -> str: + input_tokens, cached_tokens, output_tokens, reasoning_tokens = tokens + fingerprint = hashlib.sha256( + repr((conversation_id, tokens, fast, model, effort, event_timestamp)).encode() + ).hexdigest() + conn.execute( + """ + INSERT INTO otel_completion_events ( + fingerprint, conversation_id, event_timestamp, + input_tokens, cached_input_tokens, output_tokens, reasoning_output_tokens, + model, effort, service_tier, fast, service_tier_source, + service_tier_confidence, app_version, source_path, source_line, + match_status, matched_record_id + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'otel_response_completed', + 'exact', '0.143.0', '/synthetic/codex-completions.jsonl', 1, 'pending', NULL) + """, + ( + fingerprint, + conversation_id, + event_timestamp, + input_tokens, + cached_tokens, + output_tokens, + reasoning_tokens, + model, + effort, + "fast" if fast == 1 else "standard", + fast, + ), + ) + return fingerprint + + +def test_unique_token_identity_enriches_every_clone_in_one_canonical_group( + tmp_path: Path, +) -> None: + with initialized_connection(tmp_path) as conn: + insert_usage_clone_group( + conn, conversation_id="conversation-a", tokens=(100, 40, 30, 10) + ) + stage_completion( + conn, conversation_id="conversation-a", tokens=(100, 40, 30, 10), fast=1 + ) + result = reconcile_otel_completions(conn) + rows = conn.execute( + "SELECT service_tier, fast FROM usage_events ORDER BY record_id" + ).fetchall() + + assert result.matched == 1 + assert [(row["service_tier"], row["fast"]) for row in rows] == [ + ("fast", 1), + ("fast", 1), + ] + assert result.updated_usage_rows == 2 + + +def test_same_tokens_in_two_canonical_groups_remain_ambiguous(tmp_path: Path) -> None: + with initialized_connection(tmp_path) as conn: + insert_usage_clone_group( + conn, "conversation-a", (100, 40, 30, 10), canonical="canonical-a" + ) + insert_usage_clone_group( + conn, "conversation-a", (100, 40, 30, 10), canonical="canonical-b" + ) + fingerprint = stage_completion( + conn, "conversation-a", (100, 40, 30, 10), fast=1 + ) + reconcile_otel_completions(conn) + status = conn.execute( + "SELECT match_status FROM otel_completion_events WHERE fingerprint = ?", + (fingerprint,), + ).fetchone()[0] + tiers = conn.execute("SELECT service_tier FROM usage_events").fetchall() + + assert status == "ambiguous" + assert all(row[0] is None for row in tiers) + + +def test_timestamp_distance_does_not_affect_matching(tmp_path: Path) -> None: + with initialized_connection(tmp_path) as conn: + insert_usage_clone_group(conn, "conversation-a", (100, 40, 30, 10)) + stage_completion( + conn, + "conversation-a", + (100, 40, 30, 10), + fast=1, + event_timestamp="2099-01-01T00:00:00Z", + ) + + assert reconcile_otel_completions(conn).matched == 1 + + +def test_model_or_effort_mismatch_prevents_match_when_both_are_present( + tmp_path: Path, +) -> None: + with initialized_connection(tmp_path) as conn: + insert_usage_clone_group( + conn, + "conversation-a", + (100, 40, 30, 10), + model="gpt-5.5", + effort="medium", + ) + stage_completion( + conn, + "conversation-a", + (100, 40, 30, 10), + fast=1, + model="gpt-5.6", + effort="high", + ) + + assert reconcile_otel_completions(conn).pending == 1 + + +def test_missing_model_or_effort_does_not_block_exact_token_match(tmp_path: Path) -> None: + with initialized_connection(tmp_path) as conn: + insert_usage_clone_group( + conn, + "conversation-a", + (100, 40, 30, 10), + model=None, + effort=None, + ) + stage_completion( + conn, "conversation-a", (100, 40, 30, 10), fast=1 + ) + + assert reconcile_otel_completions(conn).matched == 1 + + +def test_contradictory_existing_tier_is_preserved_and_marks_conflict( + tmp_path: Path, +) -> None: + with initialized_connection(tmp_path) as conn: + insert_usage_clone_group( + conn, + "conversation-a", + (100, 40, 30, 10), + service_tier="standard", + fast=0, + ) + fingerprint = stage_completion( + conn, "conversation-a", (100, 40, 30, 10), fast=1 + ) + assert reconcile_otel_completions(conn).conflicts == 1 + row = conn.execute( + "SELECT service_tier, fast FROM usage_events LIMIT 1" + ).fetchone() + status = conn.execute( + "SELECT match_status FROM otel_completion_events WHERE fingerprint = ?", + (fingerprint,), + ).fetchone()[0] + + assert tuple(row) == ("standard", 0) + assert status == "conflict" + + +def test_usage_upsert_and_source_replacement_preserve_non_null_tier( + tmp_path: Path, +) -> None: + db_path = tmp_path / "usage.sqlite3" + original = synthetic_usage_event( + "record-a", "conversation-a", (100, 40, 30, 10), fast=1 + ) + upsert_usage_events([original], db_path=db_path) + reparsed = replace( + original, + thread_name="reparsed", + service_tier=None, + fast=None, + service_tier_source=None, + service_tier_confidence=None, + ) + + upsert_usage_events( + [reparsed], + db_path=db_path, + replace_source_files=[Path(original.source_file)], + ) + + row = query_usage_record(db_path=db_path, record_id="record-a") + assert row is not None + assert (row["service_tier"], row["fast"]) == ("fast", 1) + assert row["service_tier_source"] == "otel_response_completed" + assert row["service_tier_confidence"] == "exact" + + +def test_matched_rows_are_idempotent_and_can_be_reset_for_rebuild(tmp_path: Path) -> None: + with initialized_connection(tmp_path) as conn: + insert_usage_clone_group(conn, "conversation-a", (100, 40, 30, 10)) + fingerprint = stage_completion( + conn, "conversation-a", (100, 40, 30, 10), fast=1 + ) + first = reconcile_otel_completions(conn) + second = reconcile_otel_completions(conn) + reset_otel_completion_matches(conn) + state = conn.execute( + "SELECT match_status, matched_record_id FROM otel_completion_events WHERE fingerprint = ?", + (fingerprint,), + ).fetchone() + + assert first.matched == 1 + assert second.matched == 1 + assert second.updated_usage_rows == 0 + assert tuple(state) == ("pending", None) diff --git a/tests/store/test_otel_refresh.py b/tests/store/test_otel_refresh.py new file mode 100644 index 00000000..9e74ad08 --- /dev/null +++ b/tests/store/test_otel_refresh.py @@ -0,0 +1,199 @@ +from __future__ import annotations + +from pathlib import Path + +from codex_usage_tracker.store.api import ( + query_usage_record, + rebuild_usage_index, + refresh_metadata, + refresh_usage_index, +) +from codex_usage_tracker.store.connection import connect +from tests.otel_helpers import ( + completion_attributes, + synthetic_otlp_line, + write_lines, + write_otel_directory, + write_usage_session, +) + + +def test_refresh_ingests_session_rows_before_reconciling_otel(tmp_path: Path) -> None: + codex_home = write_usage_session( + tmp_path, conversation_id="conversation-a", tokens=(100, 40, 30, 10) + ) + otel_dir = tmp_path / "otel" + write_lines( + otel_dir / "codex-completions.jsonl", + [ + synthetic_otlp_line( + attributes=completion_attributes( + conversation_id="conversation-a", + tokens=(100, 40, 30, 10), + service_tier="priority", + ) + ) + ], + ) + db_path = tmp_path / "usage.sqlite3" + + result = refresh_usage_index( + codex_home=codex_home, db_path=db_path, otel_dir=otel_dir + ) + + with connect(db_path) as conn: + record_id = str(conn.execute("SELECT record_id FROM usage_events").fetchone()[0]) + row = query_usage_record(db_path=db_path, record_id=record_id) + assert row is not None + assert row["service_tier"] == "priority" + assert result.parser_diagnostics["otel_matched"] == 1 + + +def test_absent_otel_directory_is_a_supported_noop(tmp_path: Path) -> None: + result = refresh_usage_index( + codex_home=tmp_path / "codex", + db_path=tmp_path / "usage.sqlite3", + otel_dir=tmp_path / "missing", + ) + + assert result.parser_diagnostics.get("otel_files_scanned", 0) == 0 + + +def test_default_otel_directory_follows_database_directory(tmp_path: Path) -> None: + tracker_dir = tmp_path / "tracker" + codex_home = write_usage_session( + tmp_path, conversation_id="conversation-local-default", tokens=(90, 30, 20, 5) + ) + write_lines( + tracker_dir / "otel" / "codex-completions.jsonl", + [ + synthetic_otlp_line( + attributes=completion_attributes( + conversation_id="conversation-local-default", + tokens=(90, 30, 20, 5), + service_tier="priority", + ) + ) + ], + ) + db_path = tracker_dir / "usage.sqlite3" + + result = refresh_usage_index(codex_home=codex_home, db_path=db_path) + + with connect(db_path) as conn: + row = conn.execute("SELECT fast FROM usage_events").fetchone() + assert row is not None + assert row["fast"] == 1 + assert result.parser_diagnostics["otel_matched"] == 1 + + +def test_refresh_records_protocol_confirmed_standard(tmp_path: Path) -> None: + codex_home = write_usage_session( + tmp_path, "conversation-standard", (100, 40, 30, 10) + ) + otel_dir = tmp_path / "otel" + write_lines( + otel_dir / "codex-completions.jsonl", + [ + synthetic_otlp_line( + attributes=completion_attributes( + conversation_id="conversation-standard", + tokens=(100, 40, 30, 10), + service_tier=None, + app_version="0.143.0", + ) + ) + ], + ) + db_path = tmp_path / "usage.sqlite3" + + refresh_usage_index(codex_home=codex_home, db_path=db_path, otel_dir=otel_dir) + + with connect(db_path) as conn: + row = conn.execute( + "SELECT service_tier, fast, service_tier_confidence FROM usage_events" + ).fetchone() + assert tuple(row) == ("standard", 0, "protocol") + + +def test_refresh_keeps_older_omitted_tier_unknown(tmp_path: Path) -> None: + codex_home = write_usage_session( + tmp_path, "conversation-legacy", (100, 40, 30, 10) + ) + otel_dir = tmp_path / "otel" + write_lines( + otel_dir / "codex-completions.jsonl", + [ + synthetic_otlp_line( + attributes=completion_attributes( + conversation_id="conversation-legacy", + tokens=(100, 40, 30, 10), + service_tier=None, + app_version="0.142.9", + ) + ) + ], + ) + db_path = tmp_path / "usage.sqlite3" + + refresh_usage_index(codex_home=codex_home, db_path=db_path, otel_dir=otel_dir) + + with connect(db_path) as conn: + row = conn.execute("SELECT service_tier, fast FROM usage_events").fetchone() + assert tuple(row) == (None, None) + + +def test_otel_before_jsonl_matches_on_a_later_refresh(tmp_path: Path) -> None: + otel_dir = write_otel_directory( + tmp_path, "conversation-a", (100, 40, 30, 10) + ) + db_path = tmp_path / "usage.sqlite3" + refresh_usage_index( + codex_home=tmp_path / "empty", db_path=db_path, otel_dir=otel_dir + ) + codex_home = write_usage_session( + tmp_path, "conversation-a", (100, 40, 30, 10) + ) + + refresh_usage_index(codex_home=codex_home, db_path=db_path, otel_dir=otel_dir) + + with connect(db_path) as conn: + assert conn.execute("SELECT service_tier FROM usage_events").fetchone()[0] == "priority" + + +def test_rebuild_retains_staging_resets_match_pointer_and_reapplies_tier( + tmp_path: Path, +) -> None: + codex_home = write_usage_session( + tmp_path, "conversation-a", (100, 40, 30, 10) + ) + otel_dir = write_otel_directory( + tmp_path, "conversation-a", (100, 40, 30, 10) + ) + db_path = tmp_path / "usage.sqlite3" + refresh_usage_index(codex_home=codex_home, db_path=db_path, otel_dir=otel_dir) + + rebuild_usage_index(codex_home=codex_home, db_path=db_path, otel_dir=otel_dir) + + with connect(db_path) as conn: + assert conn.execute("SELECT COUNT(*) FROM otel_completion_events").fetchone()[0] == 1 + assert conn.execute("SELECT service_tier FROM usage_events").fetchone()[0] == "priority" + state = conn.execute( + "SELECT match_status, matched_record_id FROM otel_completion_events" + ).fetchone() + assert state["match_status"] == "matched" + assert state["matched_record_id"] is not None + + +def test_refresh_persists_all_bounded_otel_metadata_counters(tmp_path: Path) -> None: + db_path = tmp_path / "usage.sqlite3" + + refresh_usage_index( + codex_home=tmp_path / "codex", + db_path=db_path, + otel_dir=tmp_path / "missing", + ) + + metadata = refresh_metadata(db_path) + assert metadata["otel_files_scanned"] == "0" + assert metadata["otel_invalid_json"] == "0" diff --git a/tests/store/test_otel_schema.py b/tests/store/test_otel_schema.py new file mode 100644 index 00000000..a74f0fd6 --- /dev/null +++ b/tests/store/test_otel_schema.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from pathlib import Path + +from codex_usage_tracker.store.connection import connect +from codex_usage_tracker.store.otel_schema import add_otel_cursor_resume_anchor +from codex_usage_tracker.store.schema import SCHEMA_VERSION, init_db + + +def test_schema_migration_creates_otel_sidecar_tables(tmp_path: Path) -> None: + with connect(tmp_path / "usage.sqlite3") as conn: + init_db(conn) + source_columns = { + str(row["name"]) + for row in conn.execute("PRAGMA table_info(otel_completion_sources)") + } + event_columns = { + str(row["name"]) + for row in conn.execute("PRAGMA table_info(otel_completion_events)") + } + + assert SCHEMA_VERSION == 31 + assert { + "source_path", + "device", + "inode", + "size", + "parsed_offset", + "parsed_line", + "resume_anchor", + "updated_at", + } <= source_columns + assert { + "fingerprint", + "conversation_id", + "service_tier", + "fast", + "match_status", + "matched_record_id", + } <= event_columns + + +def test_cursor_anchor_migration_preserves_existing_source_state(tmp_path: Path) -> None: + with connect(tmp_path / "usage.sqlite3") as conn: + conn.execute( + """ + CREATE TABLE otel_completion_sources ( + source_path TEXT PRIMARY KEY, + device INTEGER NOT NULL, + inode INTEGER NOT NULL, + size INTEGER NOT NULL, + parsed_offset INTEGER NOT NULL, + parsed_line INTEGER NOT NULL, + updated_at TEXT NOT NULL + ) + """ + ) + conn.execute( + """ + INSERT INTO otel_completion_sources ( + source_path, device, inode, size, parsed_offset, parsed_line, updated_at + ) VALUES ('/tmp/synthetic-otel.jsonl', 1, 2, 100, 100, 1, '2026-07-16') + """ + ) + + add_otel_cursor_resume_anchor(conn) + + row = conn.execute( + """ + SELECT parsed_offset, parsed_line, resume_anchor + FROM otel_completion_sources + WHERE source_path = '/tmp/synthetic-otel.jsonl' + """ + ).fetchone() + + assert row is not None + assert row["parsed_offset"] == 100 + assert row["parsed_line"] == 1 + assert row["resume_anchor"] is None diff --git a/tests/store/test_source_records.py b/tests/store/test_source_records.py index aef8359b..f4f176b6 100644 --- a/tests/store/test_source_records.py +++ b/tests/store/test_source_records.py @@ -15,6 +15,8 @@ reset_usage_database, upsert_usage_events, ) +from codex_usage_tracker.store.otel_ingest import ingest_otel_completion_files +from tests.otel_helpers import write_otel_directory from tests.store_dashboard_helpers import _usage_event @@ -228,3 +230,30 @@ def test_reset_usage_database_clears_content_index_tables(tmp_path: Path) -> Non assert fragment_count is not None assert turn_count[0] == 0 assert fragment_count[0] == 0 + + +def test_reset_usage_database_clears_otel_staging_and_cursors( + tmp_path: Path, +) -> None: + db_path = tmp_path / "usage.sqlite3" + otel_dir = write_otel_directory( + tmp_path, + "synthetic-reset", + (120, 40, 30, 10), + ) + with connect(db_path) as conn: + init_db(conn) + ingest_otel_completion_files(conn, otel_dir) + + reset_usage_database(db_path) + + with connect(db_path) as conn: + event_count = conn.execute( + "SELECT COUNT(*) FROM otel_completion_events" + ).fetchone()[0] + source_count = conn.execute( + "SELECT COUNT(*) FROM otel_completion_sources" + ).fetchone()[0] + + assert event_count == 0 + assert source_count == 0 diff --git a/tests/store/test_store_dashboard_mcp.py b/tests/store/test_store_dashboard_mcp.py index f546c2ff..ce6a14c8 100644 --- a/tests/store/test_store_dashboard_mcp.py +++ b/tests/store/test_store_dashboard_mcp.py @@ -79,12 +79,12 @@ def test_refresh_is_idempotent_and_summary_works(tmp_path: Path) -> None: assert meta["parsed_source_files"] == "0" assert meta["skipped_source_files"] == "3" assert meta["parser_adapter"] == "codex-jsonl-v2" - assert meta["schema_version"] == "29" + assert meta["schema_version"] == "31" assert meta["parser_skipped_events"] == "0" state = schema_state(db_path) - assert state["schema_version"] == 29 + assert state["schema_version"] == 31 assert state["checksum_matches"] is True - assert [row["version"] for row in state["migrations"]] == list(range(1, 30)) + assert [row["version"] for row in state["migrations"]] == list(range(1, 32)) with connect(db_path) as conn: init_db(conn) source_rows = [ @@ -711,7 +711,7 @@ def test_connect_sets_sqlite_concurrency_pragmas(tmp_path: Path) -> None: assert busy_timeout == 5000 assert str(journal_mode).lower() == "wal" - assert user_version == 29 + assert user_version == 31 def test_current_schema_reads_succeed_while_writer_is_active(tmp_path: Path) -> None: @@ -809,8 +809,8 @@ def test_init_db_repairs_version_zero_schema(tmp_path: Path) -> None: assert "used_percent" in allowance_columns assert "window_kind" in allowance_columns assert "idx_allowance_observations_window_time" in allowance_indexes - assert user_version == 29 - assert [row["version"] for row in migrations] == list(range(1, 30)) + assert user_version == 31 + assert [row["version"] for row in migrations] == list(range(1, 32)) assert "idx_usage_source_file_line" in indexes diff --git a/tests/store/test_store_migrations.py b/tests/store/test_store_migrations.py index 8867ce1f..559b69f2 100644 --- a/tests/store/test_store_migrations.py +++ b/tests/store/test_store_migrations.py @@ -70,9 +70,9 @@ def test_init_db_migrates_legacy_aggregate_table_without_data_loss(tmp_path: Pat assert len(str(source_rows[0]["source_record_hash"])) == 64 assert metadata["parsed_events"] == "legacy" assert metadata["parser_invalid_integer"] == "2" - assert state["schema_version"] == 29 + assert state["schema_version"] == 31 assert state["checksum_matches"] is True - assert [row["version"] for row in state["migrations"]] == list(range(1, 30)) + assert [row["version"] for row in state["migrations"]] == list(range(1, 32)) with connect(db_path) as conn: init_db(conn) facts = conn.execute("SELECT COUNT(*) AS count FROM call_diagnostic_facts").fetchone() @@ -108,7 +108,7 @@ def test_refresh_is_idempotent_after_legacy_migration(tmp_path: Path) -> None: assert second_count == 2 assert legacy_rows[0]["record_id"] == "legacy-record" assert new_rows[0]["thread_name"] == "Synthetic migration thread" - assert metadata["schema_version"] == "29" + assert metadata["schema_version"] == "31" assert metadata["parsed_events"] == "0" assert metadata["inserted_or_updated_events"] == "0" assert metadata["parsed_source_files"] == "0" @@ -258,8 +258,8 @@ def test_init_db_records_all_schema_migrations_for_new_database(tmp_path: Path) ).fetchall() ] - assert versions == list(range(1, 30)) - assert user_version == 29 + assert versions == list(range(1, 32)) + assert user_version == 31 assert "idx_usage_source_file_line" in usage_indexes assert { "idx_recommendation_facts_rank_active", @@ -462,7 +462,7 @@ def test_init_db_upgrades_v25_database_without_changing_physical_rows(tmp_path: conn.execute("DROP TABLE allowance_source_state") conn.execute("DROP INDEX idx_allowance_observations_active_newest") conn.execute("DROP INDEX idx_allowance_observations_active_window_newest") - conn.execute("DELETE FROM schema_migrations WHERE version IN (26, 27, 28, 29)") + conn.execute("DELETE FROM schema_migrations WHERE version IN (26, 27, 28, 29, 30, 31)") conn.execute("PRAGMA user_version = 25") versions_before = [ row[0] @@ -484,7 +484,7 @@ def test_init_db_upgrades_v25_database_without_changing_physical_rows(tmp_path: user_version = conn.execute("PRAGMA user_version").fetchone()[0] assert versions_before == list(range(1, 26)) - assert user_version == 29 + assert user_version == 31 assert { "allowance_source_state", "allowance_cycles", @@ -503,7 +503,7 @@ def test_init_db_upgrades_v27_allowance_indexes_to_v28(tmp_path: Path) -> None: """ DROP INDEX idx_allowance_intervals_evidence_cohort; DROP INDEX idx_allowance_intervals_evidence_global; - DELETE FROM schema_migrations WHERE version IN (28, 29); + DELETE FROM schema_migrations WHERE version IN (28, 29, 30, 31); PRAGMA user_version = 27; """ ) @@ -518,8 +518,8 @@ def test_init_db_upgrades_v27_allowance_indexes_to_v28(tmp_path: Path) -> None: ] user_version = conn.execute("PRAGMA user_version").fetchone()[0] - assert user_version == 29 - assert versions == list(range(1, 30)) + assert user_version == 31 + assert versions == list(range(1, 32)) assert { "idx_allowance_intervals_evidence_cohort", "idx_allowance_intervals_evidence_global", @@ -533,7 +533,7 @@ def test_init_db_upgrades_v28_with_allowance_plan_provenance(tmp_path: Path) -> conn.executescript( """ ALTER TABLE allowance_cycles DROP COLUMN plan_type; - DELETE FROM schema_migrations WHERE version = 29; + DELETE FROM schema_migrations WHERE version IN (29, 30, 31); PRAGMA user_version = 28; """ ) @@ -549,8 +549,8 @@ def test_init_db_upgrades_v28_with_allowance_plan_provenance(tmp_path: Path) -> ] user_version = conn.execute("PRAGMA user_version").fetchone()[0] - assert user_version == 29 - assert versions == list(range(1, 30)) + assert user_version == 31 + assert versions == list(range(1, 32)) assert "plan_type" in columns @@ -604,6 +604,10 @@ def test_csv_export_keeps_current_columns_after_legacy_migration(tmp_path: Path) assert rows[0]["next_record_id"] == "" assert rows[0]["rate_limit_plan_type"] == "" assert rows[0]["rate_limit_primary_used_percent"] == "" + assert rows[0]["service_tier"] == "" + assert rows[0]["fast"] == "" + assert rows[0]["service_tier_source"] == "" + assert rows[0]["service_tier_confidence"] == "" assert list(rows[0]) == EVENT_COLUMNS diff --git a/tests/usage_drain/test_usage_drain_model.py b/tests/usage_drain/test_usage_drain_model.py index 789a1bfb..f5b59e9f 100644 --- a/tests/usage_drain/test_usage_drain_model.py +++ b/tests/usage_drain/test_usage_drain_model.py @@ -13,6 +13,7 @@ load_fast_proxy_annotations, summarize_usage_drain_model, ) +from codex_usage_tracker.usage_drain.spans import _span_from_rows from tests.usage_drain.model_test_helpers import ( coefficients_by_feature as _coefficients_by_feature, ) @@ -147,6 +148,17 @@ def test_alternate_codex_limit_rows_count_as_work_but_not_boundaries() -> None: assert spans[0].rate_limit_limit_id == "codex" +def test_usage_drain_prefers_standard_credits_after_fast_annotation() -> None: + row = _row("fast", "2026-06-01T00:00:00Z", 1.0, 2.5) + row["standard_usage_credits"] = 1.0 + + span = _span_from_rows( + [row], baseline_percent=0.0, end_used_percent=1.0, proxies={} + ) + + assert span.standard_usage_credits == 1.0 + + def test_build_usage_delta_spans_prefers_five_hour_window_when_secondary() -> None: rows = [ _row(