Extract token usage projection to bridge

This commit is contained in:
Meta Vens 2026-05-14 08:41:11 +01:00
commit c744a9367f
7 changed files with 95 additions and 81 deletions

View file

@ -12,6 +12,7 @@ pub mod retrieve;
pub mod runtime_results;
pub mod scene;
pub mod state;
pub mod token_usage;
pub mod view;
pub use epiphany_openai_adapter as openai_adapter;

View file

@ -0,0 +1,77 @@
use codex_app_server_protocol::Thread;
use codex_app_server_protocol::ThreadHistoryBuilder;
use codex_app_server_protocol::TurnStatus;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::RolloutItem;
use codex_protocol::protocol::TokenUsageInfo;
pub fn latest_token_usage_info_from_rollout_items(
rollout_items: &[RolloutItem],
) -> Option<TokenUsageInfo> {
rollout_items.iter().rev().find_map(|item| match item {
RolloutItem::EventMsg(EventMsg::TokenCount(event)) => event.info.clone(),
_ => None,
})
}
/// Identifies the turn that was active when a `TokenCount` record appeared.
///
/// The id is preferred when it still appears in the rebuilt thread. The position
/// is a fallback for histories whose implicit turn ids are regenerated during
/// reconstruction.
struct TokenUsageTurnOwner {
id: String,
position: Option<usize>,
}
pub fn latest_token_usage_turn_id_from_rollout_items(
rollout_items: &[RolloutItem],
thread: &Thread,
) -> Option<String> {
let owner = latest_token_usage_turn_owner_from_rollout_items(rollout_items)?;
if thread.turns.iter().any(|turn| turn.id == owner.id) {
return Some(owner.id);
}
owner
.position
.and_then(|position| thread.turns.get(position))
.map(|turn| turn.id.clone())
}
fn latest_token_usage_turn_owner_from_rollout_items(
rollout_items: &[RolloutItem],
) -> Option<TokenUsageTurnOwner> {
let mut builder = ThreadHistoryBuilder::new();
let mut token_usage_turn_owner = None;
for item in rollout_items {
if matches!(item, RolloutItem::EventMsg(EventMsg::TokenCount(_))) {
token_usage_turn_owner =
builder
.active_turn_snapshot()
.map(|turn| TokenUsageTurnOwner {
id: turn.id,
position: builder.active_turn_position(),
});
}
builder.handle_rollout_item(item);
}
token_usage_turn_owner
}
/// Chooses a fallback turn id that should own a replayed token usage update.
///
/// Normal replay derives the owner from the rollout position of the latest
/// `TokenCount` event. This fallback only preserves a stable wire shape for
/// unusual histories where that rollout information cannot be read.
pub fn latest_token_usage_turn_id(thread: &Thread) -> String {
thread
.turns
.iter()
.rev()
.find(|turn| matches!(turn.status, TurnStatus::Completed | TurnStatus::Failed))
.or_else(|| thread.turns.last())
.map(|turn| turn.id.clone())
.unwrap_or_default()
}

View file

@ -781,6 +781,12 @@ bridge now: app-server passes `EpiphanyInvalidationSnapshot` to
`epiphany_freshness_watcher_snapshot` instead of defining that mapping beside
`codex_message_processor.rs`.
Token-usage rollout projection has also left the app-server helper.
`epiphany-codex-bridge::token_usage` owns the pure logic that finds the latest
persisted token-usage snapshot and maps it back to the rebuilt turn owner.
`token_usage_replay.rs` is now just the host shell: read rollout items, choose
whether to replay, and send a connection-scoped JSON-RPC notification.
The MCP config path is now product-quarantined. `Config::to_mcp_config` keeps
user-declared MCP servers but no longer accepts a `PluginsManager`, folds
plugin-provided MCP servers into the runtime, enables Codex apps, enables skill

Binary file not shown.

File diff suppressed because one or more lines are too long

2
vendor/codex/codex-rs/Cargo.lock generated vendored
View file

@ -5138,9 +5138,11 @@ dependencies = [
"base64 0.22.1",
"chrono",
"codex-client",
"codex-keyring-store",
"reqwest",
"serde",
"serde_json",
"sha2 0.10.9",
"thiserror 2.0.18",
"tracing",
]

View file

@ -14,15 +14,14 @@ use std::sync::Arc;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::Thread;
use codex_app_server_protocol::ThreadHistoryBuilder;
use codex_app_server_protocol::ThreadTokenUsage;
use codex_app_server_protocol::ThreadTokenUsageUpdatedNotification;
use codex_app_server_protocol::TurnStatus;
use codex_core::CodexThread;
use codex_protocol::ThreadId;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::RolloutItem;
use codex_protocol::protocol::TokenUsageInfo;
use epiphany_codex_bridge::token_usage::latest_token_usage_info_from_rollout_items;
use epiphany_codex_bridge::token_usage::latest_token_usage_turn_id;
pub(super) use epiphany_codex_bridge::token_usage::latest_token_usage_turn_id_from_rollout_items;
use crate::codex_message_processor::read_rollout_items_from_rollout;
use crate::outgoing_message::ConnectionId;
@ -78,73 +77,3 @@ pub(super) async fn latest_token_usage_info_from_rollout_path(
let rollout_items = read_rollout_items_from_rollout(rollout_path).await.ok()?;
latest_token_usage_info_from_rollout_items(&rollout_items)
}
pub(super) fn latest_token_usage_info_from_rollout_items(
rollout_items: &[RolloutItem],
) -> Option<TokenUsageInfo> {
rollout_items.iter().rev().find_map(|item| match item {
RolloutItem::EventMsg(EventMsg::TokenCount(event)) => event.info.clone(),
_ => None,
})
}
/// Identifies the turn that was active when a `TokenCount` record appeared.
///
/// The id is preferred when it still appears in the rebuilt thread. The position is a
/// fallback for histories whose implicit turn ids are regenerated during reconstruction.
struct TokenUsageTurnOwner {
id: String,
position: Option<usize>,
}
pub(super) fn latest_token_usage_turn_id_from_rollout_items(
rollout_items: &[RolloutItem],
thread: &Thread,
) -> Option<String> {
let owner = latest_token_usage_turn_owner_from_rollout_items(rollout_items)?;
if thread.turns.iter().any(|turn| turn.id == owner.id) {
return Some(owner.id);
}
owner
.position
.and_then(|position| thread.turns.get(position))
.map(|turn| turn.id.clone())
}
fn latest_token_usage_turn_owner_from_rollout_items(
rollout_items: &[RolloutItem],
) -> Option<TokenUsageTurnOwner> {
let mut builder = ThreadHistoryBuilder::new();
let mut token_usage_turn_owner = None;
for item in rollout_items {
if matches!(item, RolloutItem::EventMsg(EventMsg::TokenCount(_))) {
token_usage_turn_owner =
builder
.active_turn_snapshot()
.map(|turn| TokenUsageTurnOwner {
id: turn.id,
position: builder.active_turn_position(),
});
}
builder.handle_rollout_item(item);
}
token_usage_turn_owner
}
/// Chooses a fallback turn id that should own a replayed token usage update.
///
/// Normal replay derives the owner from the rollout position of the latest
/// `TokenCount` event. This fallback only preserves a stable wire shape for
/// unusual histories where that rollout information cannot be read.
fn latest_token_usage_turn_id(thread: &Thread) -> String {
thread
.turns
.iter()
.rev()
.find(|turn| matches!(turn.status, TurnStatus::Completed | TurnStatus::Failed))
.or_else(|| thread.turns.last())
.map(|turn| turn.id.clone())
.unwrap_or_default()
}