Skip to content

fix(agent-core): parse provider cache token usage into accounting - #280

Open
chsimonpan wants to merge 926 commits into
org2AI:developfrom
chsimonpan:pr/cache-token-usage
Open

fix(agent-core): parse provider cache token usage into accounting#280
chsimonpan wants to merge 926 commits into
org2AI:developfrom
chsimonpan:pr/cache-token-usage

Conversation

@chsimonpan

Copy link
Copy Markdown
Contributor

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 SSE chunk.usage) is not deserialized
  • responses_common: usage.input_tokens_details.cached_tokens is not deserialized

This PR adds the missing typed fields and threads cached_tokens into the existing usage accounting maps as cache_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 struct
  • openai_compat/client.rs, streaming/chat.rs, streaming/sse_stream.rs: propagate into usage map
  • responses_common/types.rs + parser.rs: same for the Responses API

Pure additive change (52 insertions, no behavior change when providers omit the fields). Verified against ZenMux/OpenAI-compatible endpoints returning prompt_tokens_details, and cargo test -p agent_core --lib passes for the touched modules (98 tests).

@chsimonpan
chsimonpan force-pushed the pr/cache-token-usage branch from b040797 to e2e5b25 Compare July 8, 2026 09:38
Harry19081 and others added 29 commits July 14, 2026 16:05
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
sudomaggie and others added 28 commits July 17, 2026 18:38
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants