[codex] Steer budget-limited goal extension turns (#23718)

## What
- Add a small extension capability for injecting model-visible response
items into the active turn
- Have the goal extension inject hidden goal-context steering when
tool-finish accounting reaches `BudgetLimited`
- Cover the extension backend path with an assertion on the injected
steering item

## Why
PR #23696 persists and emits the budget-limited goal update from
tool-finish accounting, but it leaves the model unaware of that
transition. The existing core runtime steers the model to wrap up in
this case; the extension path should do the same through an explicit
host capability.

## Testing
- `just fmt`
- `cargo test -p codex-goal-extension`
- `cargo test -p codex-extension-api`
This commit is contained in:
jif-oai
2026-05-21 12:54:00 +02:00
committed by GitHub
Unverified
parent 20fedafff8
commit 791b69dd53
14 changed files with 298 additions and 34 deletions
@@ -1,7 +1,11 @@
mod agent;
mod events;
mod response_items;
pub use agent::AgentSpawnFuture;
pub use agent::AgentSpawner;
pub use events::ExtensionEventSink;
pub use events::NoopExtensionEventSink;
pub use response_items::NoopResponseItemInjector;
pub use response_items::ResponseItemInjectionFuture;
pub use response_items::ResponseItemInjector;
@@ -0,0 +1,33 @@
use std::future::Future;
use std::pin::Pin;
use codex_protocol::models::ResponseInputItem;
/// Future returned when an extension asks the host to inject model-visible input.
pub type ResponseItemInjectionFuture<'a> =
Pin<Box<dyn Future<Output = Result<(), Vec<ResponseInputItem>>> + Send + 'a>>;
/// Host-provided helper for extensions that need to steer the active model turn.
///
/// Implementations should inject the supplied response items into the active turn
/// when one can accept same-turn model input. If injection is unavailable, they
/// return the unchanged items to the caller.
pub trait ResponseItemInjector: Send + Sync {
fn inject_response_items<'a>(
&'a self,
items: Vec<ResponseInputItem>,
) -> ResponseItemInjectionFuture<'a>;
}
/// Injector used when a host does not expose same-turn model steering.
#[derive(Debug, Default, Clone, Copy)]
pub struct NoopResponseItemInjector;
impl ResponseItemInjector for NoopResponseItemInjector {
fn inject_response_items<'a>(
&'a self,
items: Vec<ResponseInputItem>,
) -> ResponseItemInjectionFuture<'a> {
Box::pin(std::future::ready(Err(items)))
}
}