Files
codex/codex-rs/ext/extension-api/tests/capabilities.rs
Won Park 56012fafb8 Add Guardian internal session support (#39994)
## What changed

- Add an extension API for spawning host-owned internal sessions and a
  `ThreadReadyInput` lifecycle callback that runs after thread registration.
- Add Guardian reviewer session scaffolding that records the parent thread and
  effective model, plus the under-development `guardian_ext` feature flag.
- Start internal sessions with fresh history while preserving parent lineage,
  shared session controls, and internal-thread visibility rules.
- Scope internal-session prompt cache keys to their source and parent thread,
  and expose `guardian` as an internal session source.

## Testing

- Cover internal-session spawning, parent metadata, history isolation, prompt
  cache keys, and extension spawner argument forwarding.

GitOrigin-RevId: 682dae80397d62bb36247796b447042e760ca364
2026-08-21 19:11:05 +00:00

86 lines
2.8 KiB
Rust

use std::sync::Arc;
use std::sync::Mutex;
use codex_extension_api::AgentSpawnFuture;
use codex_extension_api::AgentSpawner;
use codex_extension_api::InternalSessionSpawnFuture;
use codex_extension_api::InternalSessionSpawner;
use codex_extension_api::NoopResponseItemInjector;
use codex_extension_api::ResponseItemInjector;
use codex_protocol::ThreadId;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ResponseInputItem;
use pretty_assertions::assert_eq;
#[tokio::test]
async fn noop_response_item_injector_returns_original_items() {
let items = vec![ResponseInputItem::Message {
role: "user".to_string(),
content: vec![ContentItem::InputText {
text: "keep this input".to_string(),
}],
phase: None,
}];
let returned_items = NoopResponseItemInjector
.inject_response_items(items.clone())
.await
.expect_err("noop injector should reject same-turn injection");
assert_eq!(returned_items, items);
}
#[tokio::test]
async fn closure_agent_spawner_forwards_arguments_and_result() {
let calls = Arc::new(Mutex::new(Vec::new()));
let recorded_calls = Arc::clone(&calls);
let spawner = move |thread_id: ThreadId,
request: String|
-> AgentSpawnFuture<'static, usize, &'static str> {
recorded_calls
.lock()
.expect("agent spawn calls lock")
.push((thread_id, request.clone()));
Box::pin(async move { Ok(request.len()) })
};
let thread_id =
ThreadId::from_string("11111111-1111-4111-8111-111111111111").expect("valid thread id");
let spawned = spawner
.spawn_subagent(thread_id, "delegate this".to_string())
.await;
assert_eq!(spawned, Ok(13));
assert_eq!(
calls.lock().expect("agent spawn calls lock").as_slice(),
[(thread_id, "delegate this".to_string())]
);
}
#[tokio::test]
async fn closure_internal_session_spawner_forwards_arguments_and_result() {
let calls = Arc::new(Mutex::new(Vec::new()));
let recorded_calls = Arc::clone(&calls);
let spawner = move |thread_id: ThreadId,
request: String|
-> InternalSessionSpawnFuture<'static, usize, &'static str> {
recorded_calls
.lock()
.expect("agent spawn calls lock")
.push((thread_id, request.clone()));
Box::pin(async move { Ok(request.len()) })
};
let thread_id =
ThreadId::from_string("11111111-1111-4111-8111-111111111111").expect("valid thread id");
let spawned = spawner
.spawn_internal_session(thread_id, "delegate this".to_string())
.await;
assert_eq!(spawned, Ok(13));
assert_eq!(
calls.lock().expect("agent spawn calls lock").as_slice(),
[(thread_id, "delegate this".to_string())]
);
}