fix(agent-core): parse provider cache token usage into accounting - #280
Open
chsimonpan wants to merge 926 commits into
Open
fix(agent-core): parse provider cache token usage into accounting#280chsimonpan wants to merge 926 commits into
chsimonpan wants to merge 926 commits into
Conversation
chsimonpan
force-pushed
the
pr/cache-token-usage
branch
from
July 8, 2026 09:38
b040797 to
e2e5b25
Compare
Pre-commit hook ran. Total eslint: 0, total circular: 0
Convert the three remaining keep-alive-trio renderers off HostCoupledPlaceholder (project-workitems -> WorkItemsPage, project-linear-projects / project-linear-work-items -> LinearProjectsPage), sourcing action callbacks from the hoisted Project host context and data from tab.data. Rewrite ProjectManagerContentRouter to mount project tab content through UnifiedTabContent: the keep-alive trio still renders one pane per open tab with display:none toggling, and the active non-trio project tab routes through the dispatcher. chat-session and git-commit-detail keep their bespoke inline branches (the host needs ChatView secondary / direct git-commit mount, not the unified renderers).
The WorkStation bottom dock app-switcher was removed and host-switching is now tab-driven (activeHostAtom -> effectiveHost). Remove the now-dead per-app machinery while keeping host-switching and the + menu intact. - WorkstationTabBar: drop archived "data" host from WORKSTATION_SINGLE_HOST_MODES; fix stale header comment. - Remove the per-app switcher chip from the My Station tab bar. Its dropdown (All Tabs / Code / Browser / Database Manager / Project) is no longer wanted now that the tab surface is unified; the "Database Manager" entry was also a dead app. WorkStationTabBarLeading now renders only the station-mode chip. The + menu (TabBarPlusMenu) and other trailing-slot content are untouched. - Delete the now-unreferenced WorkStationAppSwitcherChip, TabBarWorkStationAppSwitcherChip components and useWorkStationAppSwitcher hook (+ their exports). The Agent Station switcher (SimulatorAppSwitcherChip / SimulatorAgentChip / SimulatorTabBarLeading, shared AppSwitcherChip / AppSwitcherDropdownPanel) still has importers and is kept. dockFilter machinery (dockFilterAtom, useDockFilterUrlSync, useAppShellDockFilterSync, per-host sub-routes / buildDockFilterPath) is left intact: it is still load-bearing (route + ?filter= URL sync feed useAppShellDerivedState's effectiveHost) and simplifying it is not clearly safe. Pre-commit hook ran. Total eslint: 0, total circular: 0
With app levels gone, the "+" menu was still gated on isAllTabsView / appMode === "browser", so it disappeared on the code/project surfaces. Render it unconditionally (full palette) — the unified tab surface has no per-app gating left. Drops the now-dead isAllTabsView plumbing from the trailing slot. Pre-commit hook ran. Total eslint: 0, total circular: 0
The tab strip was filtered by dockFilter, so once dockFilter tracked the active host (e.g. "code"), the bar hid every other host's tabs — Browser and Project tabs vanished from the unified surface. Show all open tabs regardless of active host; dockFilter still drives which host renders (effectiveHost), it just no longer filters the strip. Drops the now-dead dockFilterToHost / hostFilter. Pre-commit hook ran. Total eslint: 0, total circular: 0
Move the browser Token-Manager (TokenManagerContent, DesignFileBar) and the token-category tab renderer into .archive, and unwire it from the tab registry, tab-host mapping, tab factory/types, and BrowserLayout/sidebar. Also includes minor chat-panel appearance settings tweaks. Pre-commit hook ran. Total eslint: 0, total circular: 0
Phase 2.4 of the WorkStation unified-tab migration. Publishes the Code Editor host's live state + action surface (the 14-field prop bag today threaded through TabContentRenderer) above the tab dispatcher via a new EditorHostContext, and converts the editor stub renderers to consume it. - Add EditorHostProvider/useEditorHostContext (Pick of TabContentRendererProps) and mount the provider above TabContentRenderer in EditorMainPane, sourcing the SAME live fileContentManager + terminalState instances (never recreated). - Convert file, git-diff, git-log, git-commit-detail, git-stash-detail, terminal, terminal-content, and search renderers from HostCoupledPlaceholder stubs to 1:1 mirrors of TabContentRenderer's switch cases, sourcing host values from context and tab values from tab.data. - source-control renderer becomes a no-op (returns null), matching the switch: the keep-alive Source Control overlay in EditorMainPane owns that surface and must not be double-mounted. Additive and inert: TabContentRenderer still drives all live editor rendering via its switch; the converted renderers are only reachable through UnifiedTabContent, which no host mounts for these types yet. The TabContentRenderer -> UnifiedTabContent swap (step 3) is intentionally left for a careful human-reviewed follow-up. Typecheck clean except the pre-existing ContextInfoButton.tsx(468,53) error. Pre-commit hook ran. Total eslint: 0, total circular: 0
The project-linear-projects and project-linear-work-items renderers replicated the same LinearProjectsPage wiring (host-context callbacks, breadcrumb/surface derivation) and differed only in the default Linear surface. Extract the shared logic into LinearProjectsRenderer, taking the default surface as a prop, so both files become thin wrappers. Also extract the identical org-scope derivation shared by the project-dashboard and project-work-items renderers into a small getProjectOrgScopeProps helper. Behavior is unchanged. Pre-commit hook ran. Total eslint: 0, total circular: 0
Publish the Browser host's render surface above the unified tab dispatcher via a new `BrowserHostProvider`, mirroring `ProjectHostProvider` (Phase 2.1). The value bundles the shared-webview activation flags + the DevTools polling stack (console/network/inspector entries, counts, selected element, webview label) + panel size/position handlers, so browser tab renderers can consume it via `useBrowserHostContext` instead of the bespoke BrowserLayout tree. - Add `Browser/context/browserHostContext.tsx` (provider + throwing accessor). - Mount `BrowserHostProvider` above the shell in BrowserLayout (additive; the bespoke SharedBrowserWorkspace / SharedBrowserDevToolsPanel render unchanged, and the new-browser-session effect stays mounted). - Convert `browser-session` renderer off HostCoupledPlaceholder to mount the rect-publisher (SharedBrowserWorkspace) for the MY_STATION host. - Convert `devtools` renderer off HostCoupledPlaceholder to render the real SharedBrowserDevToolsPanel from hoisted devtools data. - Convert `dom-component-preview` renderer to render DomComponentPreviewContent directly from tab data (self-contained; the host-coupling note was stale). Staged/inert: UnifiedTabContent is not yet mounted for browser tabs, so the live mount (Phase 2.2 step 4) is deferred. Pre-commit hook ran. Total eslint: 0, total circular: 0
Nothing is auto-opened on app start. The editor fixtures (Explorer, Source Control, Terminal) are no longer force-seeded (usePinnedTabs disabled) and are now closable/unpinned, so the pool can reach empty. When empty, AppShellContent renders a WorkStationStartPage launcher mirroring the + menu actions (search file, new/private browser tab, work items, projects). Everything lazy-loads from there. Pre-commit hook ran. Total eslint: 0, total circular: 0
Tabs persisted before the "all closable" change kept pinned:true / closable:false (Source Control, Terminal), so they rendered icon-only with no close button. The storage validator now normalizes every restored tab to closable/unpinned/named, and the tab strip no longer hides labels for pinned tabs. Reload to apply to already-open tabs. Pre-commit hook ran. Total eslint: 0, total circular: 0
The start page now offers Explorer (open-file-folder), Source Control, and Terminal alongside search / browser / work items / projects, each opening via WorkStationViewService with its shortcut badge. Those shortcuts are bound workstation-wide (useGlobalShortcuts), so they also fire from the empty start page. Pre-commit hook ran. Total eslint: 0, total circular: 0
Make the empty-pool launcher a real `start` tab type and fix its tiles (and the ⌘G/⌘E/⌘J shortcuts) so they actually open content. Root causes: - The content host was pinned by `dockFilter` (route-derived), so every opened tab rendered under whatever host the route pinned — usually the Code Editor showing Explorer. Now `effectiveHost` follows the active tab (`activeHost`); Browser stays reachable via an explicit `dockFilter === "browser"` pin since its sessions still live outside `mainPane`. Focusing any unified-strip tab releases that pin. - Explorer / Source Control / Terminal opened via a switch-to-tab path that no-oped when the (no-longer-seeded) tab was absent. The start-page tiles and `WorkStationViewService` now create real `mainPane` tabs via the tab factories / `EditorTabService`, activating an existing tab instead of re-opening it to avoid clobbering live data. Also register a `start` renderer, seed one start tab on mount when the pool is empty, and hide the launcher while the Browser host is showing. Pre-commit hook ran. Total eslint: 0, total circular: 0
Share one action list between the empty-pool launcher and the `+` menu, and rebrand the launcher as the Launchpad. - Extract useWorkStationLaunchActions as the single source of truth for the eight launch actions (search / Explorer / Source Control / Terminal / browser / private browser / work items / projects). Both the start page tiles and the TabBarPlusMenu dropdown render from it, so their items and icons always match — the dropdown now also offers Explorer / Source Control / Terminal. - Rebrand the `start` tab as "Launchpad" with the LayoutGrid icon (the same icon the chat panel uses); register LayoutGrid in the tab-icon map and title the launcher page "Launchpad". - Make the Launchpad an ephemeral placeholder (useLaunchpadTab, renamed from useEnsureStartTab): seed it when the pool empties, drop it once a real tab exists — so closing the last tab brings it back and opening anything replaces it. Pre-commit hook ran. Total eslint: 0, total circular: 0
- Drop the non-closable/pinned tab concept from the close paths so every tab (Source Control included) shows and honors its close button: SortableTab always renders it, and closeTab / closeAllTabs / closeOtherTabs / closeSavedTabs / closeActive no longer keep fixtures. - Render launch-action shortcuts with the shared KeyboardShortcut component (dropdown variant) — the same one the shortcut tooltips use — instead of KeyBadge, so the `+` menu and Launchpad match the tooltips. - Hide the "New Private Browser Tab" entrance from both surfaces (LAUNCHPAD_ACTION_IDS) while keeping the feature available in the hook. - Restyle the Launchpad as a compact quick-action list: label + shortcut rows, no icons. Pre-commit hook ran. Total eslint: 0, total circular: 0
- Give the Source Control tab and its launch action the FileDiff (file-diff) icon; register FileDiff in the tab-icon map. - Drop the Launchpad heading and subtitle so it's just the quick-action list. Pre-commit hook ran. Total eslint: 0, total circular: 0
Pre-commit hook ran. Total eslint: 0, total circular: 0
Point the launch actions at fully-translated shared keys so every label localizes: Files (common:labels.files), Review (common:actions.review), Terminal (common:tabs.terminal), Browser (labels.browser — the prior routes.browser key did not exist and fell back to raw text). Rename the Explorer and Source Control tab titles to Files and Review to match. Pre-commit hook ran. Total eslint: 0, total circular: 0
- Translate common:labels.files across all locales so "Files" is localized everywhere it's used. - Localize the singleton tool-tab titles in the strip (Launchpad, Files, Review, main Terminal) via getDisplayTitle. - Add a small leading icon to each Launchpad quick-action row. Pre-commit hook ran. Total eslint: 0, total circular: 0
- Render the 40px tab header empty on the Launchpad — no sidebar toggle, no search/lab actions (the code host, and thus its sidebar, is already hidden behind the launcher). - Use the Folder icon for Files and move it above Search file in both the Launchpad and the `+` menu. Pre-commit hook ran. Total eslint: 0, total circular: 0
Move the Browser host's "last consumed request tick" from a per-mount ref to a module-level atom, so a new-session request issued before the host mounts (New Browser from the empty Launchpad) still opens a session on mount, while a later remount with no new request doesn't re-fire. Pre-commit hook ran. Total eslint: 0, total circular: 0
Every rail action now goes to its existing tab or creates one through the unified tab system: Source Control / Terminal / Files via WorkStationViewService (open-or-activate), and Browser switches to an open browser tab or opens a fresh session. Drops the old bottom-panel and primary-sidebar coupling. Pre-commit hook ran. Total eslint: 0, total circular: 0
Remove the New Tab / New Private Tab / DevTools / Sidebar quick actions from the empty browser placeholder — the globe empty state now stands alone. Drops the browserQuickActions chain (host-context field and the renderer/placeholder wiring); the config helper was already removed. Pre-commit hook ran. Total eslint: 0, total circular: 0
Browser sessions already live in mainPane as browser-session tabs, so the active tab's host (activeHost) alone determines the visible surface. Drop the dockFilter === "browser" host pin from effectiveHost: opening browser from the focused-chat rail no longer strands the empty browser host over the Launchpad after the last tab is closed. Pre-commit hook ran. Total eslint: 0, total circular: 0
Match the rail to the Launchpad / `+` menu: Source Control now uses the FileDiff icon and the localized "Review" label; all labels are localized (Review / Browser / Terminal / Files); and the expanded rail shows the keyboard hint (⌘E / ⌘J / ⌘G) next to each action via KeyboardShortcut. Pre-commit hook ran. Total eslint: 0, total circular: 0
…d-tabs refactor(workstation): unify into a single tab surface + Launchpad
The unified WorkStation tab pool makes the visible content host follow the active tab via `activeHostAtom`; `dockFilter` no longer drives host selection, so remove the whole concept without changing behavior. - Relocate `activeHostAtom` to `store/workstation/tabHost.ts`; re-export it from the store barrel so imports keep working. - Delete the `store/workstation/dockFilter/` module (dockFilterAtom, DockFilter type, DEFAULT_DOCK_FILTER, path/query helpers). - Delete `useDockFilterUrlSync` (the `?filter=` URL sync) and the now-inert `useActiveTabHostReconciliation` (activeHost === effectiveHost always). - Rename `useAppShellDockFilterSync` -> `useAppShellRouteSync`, keeping only the base-path side effects (My Station, chat visibility, chat width). - `useAppShellDock` now always seeds ["code", "browser"] + the active tab's host so "New Browser" works and the host still mounts; drop the dead handleDockClick / activeDockId. - Sweep remaining reads in tab list, focus, launch actions, the focused-chat rail, tutorials, view service, open-in-workstation helpers, e2e helpers, and remove the unused i18n `workstation.dockFilter` keys across locales. Pre-commit hook ran. Total eslint: 0, total circular: 0
Pre-commit hook ran. Total eslint: 0, total circular: 0
Pre-commit hook ran. Total eslint: 0, total circular: 0
feat(chat): GitHub URL pills in composer
Pre-commit hook ran. Total eslint: 6, total circular: 0
Pre-commit hook ran. Total eslint: 0, total circular: 0
Pre-commit hook ran. Total eslint: 0, total circular: 0
Pre-commit hook ran. Total eslint: 0, total circular: 0
feat(chat): interactive ACP approvals, cursor native transcripts, session id surfacing
Probe installed and official latest CLI versions only after selection, cache observations for twelve hours, and show a dismissible non-blocking upgrade warning below the session creator pill row. Preserve individual Chat error messages so provider failures remain diagnosable. Keep the scanner read-only and independent from Key Vault. Pre-commit hook ran. Total eslint: 0, total circular: 0
perf(chat): move history projection to worker
Add a Sessions tab to managed cloud organization settings using the shared SessionTable with member, agent, model, repository, and replay-state fields. Reuse the existing cloud remote-session cache and replay/fork actions, including loading, retry, empty, metadata-only, and retention states. Also align destructive row actions with secondary button styling, consolidate sharing controls, place members above invitations, and add unit plus rendered E2E coverage for the session table mapping and full-width layout. Pre-commit hook ran. Total eslint: 0, total circular: 0
Reduce the ChatHistory entry point to a focused composition boundary by extracting public types, projection, navigation, viewport, item-action, tail-collapse, planning-indicator, and render-view responsibilities. Add characterization coverage for tail-turn selection and conversation history page mapping, together with architecture and frontend UI audit reports. Pre-commit hook ran. Total eslint: 1, total circular: 0
Use source and agent app icons for shared sessions, add the supplied ORG2 app icon for native rows, and keep sidebar subtitles limited to activity time. Surface live viewer names in the cloud session hover card and cover the new watcher row with a focused test. Pre-commit hook ran. Total eslint: 12, total circular: 0
Publish Agent Org run-view and snapshot invalidations from their durable mutation owners so active UI projections refresh from committed state. Share run-view, Git numstat, and local-key reads across mounted consumers; coalesce bursts, reject stale results, queue follow-up reads after in-flight changes, and recover on reconnect or visibility changes. Derive intervention state from the canonical run view, schedule exact TTL expiry reconciliation, and remove duplicate intervention mutations from submit and queue layers. Suppress unchanged Git status broadcasts and normalize DevTools hotspot rates against the actual observation window. Add focused Rust and TypeScript regressions for the new lifecycle behavior. Pre-commit hook ran. Total eslint: 0, total circular: 0
Persist lightweight source-presence results separately from user scan policy. Probe enabled automatic sources at startup and every 30 minutes, skip full imports while their stores are absent, and import immediately when a store appears. Keep manual and disabled sources out of the probe path, preserve full-scan fallback when detection fails, and add scheduler coverage for startup, interval boundaries, discovery, failure handling, and global disable behavior. Pre-commit hook ran. Total eslint: 2, total circular: 0
perf: replace polling hot spots with push invalidation
Pre-commit hook ran. Total eslint: 0, total circular: 0
Resolve the ChatHistory grouping conflict by keeping the pure projection adapter introduced on develop and porting the PR's error-preservation behavior into useChatGroupsProjection. Preserve the CLI version scan changes and the removal of repeated tool-error deduplication.
Place the cloud team sessions group above local date-based session groups in organization scope. Keep reveal candidate ordering aligned with the rendered sidebar order. Pre-commit hook ran. Total eslint: 0, total circular: 0
feat(cli): add demand-driven version checks
Scope Realtime inbound synchronization to the affected cloud org, preserve delta cursors for ordinary changes, and avoid duplicate drain passes and empty outbox probes. Deduplicate shared comments, roster, and remote-session requests across mounted consumers; observe address-run completion through session status events with a sparse recovery check. Simplify imported shared-session header actions, update dual-instance coverage and translations, expose every likely polling hotspot, and include architecture and UI audit reports. Pre-commit hook ran. Total eslint: 0, total circular: 0
Resolve the coordination-store conflicts by preserving coordinator-owned task assignment, CAS finality, and shutdown disposition while integrating durable run-event invalidation. Keep intervention ownership in the session adapters and update merged frontend fixtures for the current Agent Org state shape.
Unwrap Codex Desktop exec_command wrappers into canonical shell calls. Map write_stdin polling to await_output, merge numeric-session and cell-wait continuations into the originating command, and preserve incremental output, stdin events, and exit status. Add parser and alias regression coverage. Pre-commit hook ran. Total eslint: 0, total circular: 0
…overy-invariants fix(agent-org): harden recovery, task orchestration, and run finality
Pre-commit hook ran. Total eslint: 0, total circular: 0
Add an Orca-style feature wall covering replay, cross-tool sessions, collaboration, time tracking, development tools, AI blame, and Design Mode.\n\nDocument the supported CLI agents with linked icon pills, remove the old status badges, and include optimized GIF and PNG assets for GitHub rendering. Pre-commit hook ran. Total eslint: 0, total circular: 0
Allow chat user-message rows to inherit the surrounding panel surface by removing the bg-chat-pane utility from the group header wrapper. Preserve the existing content width, spacing, and collapse behavior. Pre-commit hook ran. Total eslint: 0, total circular: 0
Reorder the seven visual feature rows into the product narrative requested for the README.\n\nTighten the headings around cross-tool session management, trajectory review, video-like tool-call replay, AI blame, time awareness, the development workspace, and Design Mode. Pre-commit hook ran. Total eslint: 0, total circular: 0
…ling perf(cloud): reduce shared-session polling
OpenAI-compatible chat (stream + non-stream + SSE reassembly) now reads usage.prompt_tokens_details.cached_tokens, and the Responses API parser reads usage.input_tokens_details.cached_tokens, mapping both into usage_key::CACHE_READ_TOKENS. Previously only the Anthropic native provider populated cache counters, so session_token_usage and session_llm_usage_spans recorded 0 cache_read/cache_write for all OpenAI-protocol models even when the upstream cache was hitting. Pre-commit hook ran. Total eslint: 6, total circular: 0
chsimonpan
force-pushed
the
pr/cache-token-usage
branch
from
July 17, 2026 18:39
e2e5b25 to
ec3ba8a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Contributes to #50 (per-session usage / cache rate visibility).
Currently the OpenAI-compatible and Responses providers drop all prompt-cache usage data returned by the API, so cache hit rate can never be surfaced in session stats:
openai_compat:usage.prompt_tokens_details.cached_tokens(both non-streaming and SSEchunk.usage) is not deserializedresponses_common:usage.input_tokens_details.cached_tokensis not deserializedThis PR adds the missing typed fields and threads
cached_tokensinto the existing usage accounting maps ascache_read_input_tokens(matching the key the Anthropic native provider already emits), keeping accounting consistent across providers:openai_compat/types.rs:PromptTokensDetails+ field on usage structopenai_compat/client.rs,streaming/chat.rs,streaming/sse_stream.rs: propagate into usage mapresponses_common/types.rs+parser.rs: same for the Responses APIPure additive change (52 insertions, no behavior change when providers omit the fields). Verified against ZenMux/OpenAI-compatible endpoints returning
prompt_tokens_details, andcargo test -p agent_core --libpasses for the touched modules (98 tests).