Files
codex/codex-rs/core/src/tasks/lifecycle.rs
T
jif-oai 59507b8491 feat: expose turn-start metadata to extensions (#23688)
## Why

The goal extension needs more context when a turn starts than
`turn_store` alone provides.

In particular, goal accounting needs the stable turn id, the effective
collaboration mode, and the cumulative token-usage baseline captured at
turn start so it can:

- suppress goal accounting for plan-mode turns
- compute exact per-turn deltas from cumulative `total_token_usage`
snapshots instead of relying on the most recent usage event alone
- keep the extension-owned accounting path aligned with the host turn
lifecycle

## What

- extend `codex_extension_api::TurnStartInput` to expose `turn_id`,
`collaboration_mode`, and `token_usage_at_turn_start`
- pass the full `TurnContext` plus the captured token-usage baseline
through the turn-start lifecycle emission path
- initialize goal turn accounting from the turn-start baseline and
collaboration mode
- switch goal token accounting to compute deltas from cumulative
`total_token_usage` snapshots
- add coverage for the new turn-start lifecycle fields and for
goal-accounting baseline behavior

## Testing

- added `turn_start_lifecycle_exposes_turn_metadata_and_token_baseline`
in `codex-rs/core/src/session/tests.rs`
- added `ext/goal/tests/accounting.rs` coverage for baseline-aware goal
accounting and plan-mode suppression
2026-05-20 15:54:29 +02:00

57 lines
2.1 KiB
Rust

use codex_extension_api::ExtensionData;
use codex_protocol::protocol::TokenUsage;
use codex_protocol::protocol::TurnAbortReason;
use crate::session::session::Session;
use crate::session::turn_context::TurnContext;
impl Session {
pub(super) async fn emit_turn_start_lifecycle(
&self,
turn_context: &TurnContext,
token_usage_at_turn_start: &TokenUsage,
) {
for contributor in self.services.extensions.turn_lifecycle_contributors() {
contributor
.on_turn_start(codex_extension_api::TurnStartInput {
turn_id: turn_context.sub_id.as_str(),
collaboration_mode: &turn_context.collaboration_mode,
token_usage_at_turn_start,
session_store: &self.services.session_extension_data,
thread_store: &self.services.thread_extension_data,
turn_store: turn_context.extension_data.as_ref(),
})
.await;
}
}
pub(super) async fn emit_turn_stop_lifecycle(&self, turn_store: &ExtensionData) {
for contributor in self.services.extensions.turn_lifecycle_contributors() {
contributor
.on_turn_stop(codex_extension_api::TurnStopInput {
session_store: &self.services.session_extension_data,
thread_store: &self.services.thread_extension_data,
turn_store,
})
.await;
}
}
pub(super) async fn emit_turn_abort_lifecycle(
&self,
reason: TurnAbortReason,
turn_store: &ExtensionData,
) {
for contributor in self.services.extensions.turn_lifecycle_contributors() {
contributor
.on_turn_abort(codex_extension_api::TurnAbortInput {
reason: reason.clone(),
session_store: &self.services.session_extension_data,
thread_store: &self.services.thread_extension_data,
turn_store,
})
.await;
}
}
}