Skip to main content

codex_agent_extension/
lib.rs

1use codex_core::CodexThread;
2use codex_core::NewThread;
3use codex_core::StartThreadOptions;
4use codex_core::ThreadManager;
5use codex_core::config::Config;
6use codex_protocol::ThreadId;
7use codex_protocol::error::CodexErr;
8use codex_protocol::error::Result as CodexResult;
9use codex_protocol::protocol::InitialHistory;
10use codex_protocol::protocol::W3cTraceContext;
11use codex_protocol::user_input::UserInput;
12use std::sync::Arc;
13use std::sync::Weak;
14
15/// A fully resolved agent invocation.
16///
17/// Agent discovery owns rendering `prompt`, including any selected skill
18/// references. The runtime only starts that prompt in isolated forked context.
19pub struct AgentInvocation {
20    pub config: Config,
21    pub prompt: String,
22    pub parent_trace: Option<W3cTraceContext>,
23}
24
25/// A spawned agent whose initial turn has been submitted.
26pub struct AgentRun {
27    pub thread_id: ThreadId,
28    pub turn_id: String,
29    pub thread: Arc<CodexThread>,
30}
31
32/// Runs resolved agents in threads forked by the owning [`ThreadManager`].
33#[derive(Clone)]
34pub struct AgentRunner {
35    thread_manager: Weak<ThreadManager>,
36}
37
38impl AgentRunner {
39    pub fn new(thread_manager: Weak<ThreadManager>) -> Self {
40        Self { thread_manager }
41    }
42
43    /// Starts a resolved agent in a fork of `parent_thread_id`.
44    pub async fn start(
45        &self,
46        parent_thread_id: ThreadId,
47        invocation: AgentInvocation,
48    ) -> CodexResult<AgentRun> {
49        let AgentInvocation {
50            config,
51            prompt,
52            parent_trace,
53        } = invocation;
54        if prompt.trim().is_empty() {
55            return Err(CodexErr::InvalidRequest(
56                "agent prompt must not be empty".to_string(),
57            ));
58        }
59
60        let thread_manager = self
61            .thread_manager
62            .upgrade()
63            .ok_or_else(|| CodexErr::UnsupportedOperation("thread manager dropped".to_string()))?;
64        let environments =
65            thread_manager.default_environment_selections(&config.cwd, &config.workspace_roots);
66        let NewThread {
67            thread_id, thread, ..
68        } = thread_manager
69            .spawn_subagent(
70                parent_thread_id,
71                StartThreadOptions {
72                    config,
73                    allow_provider_model_fallback: false,
74                    initial_history: InitialHistory::New,
75                    history_mode: None,
76                    session_source: None,
77                    thread_source: None,
78                    dynamic_tools: Vec::new(),
79                    metrics_service_name: None,
80                    parent_trace: parent_trace.clone(),
81                    environments,
82                    thread_extension_init: Default::default(),
83                    supports_openai_form_elicitation: false,
84                },
85            )
86            .await?;
87        let turn_id = thread
88            .submit_with_trace(
89                vec![UserInput::Text {
90                    text: prompt,
91                    text_elements: Vec::new(),
92                }]
93                .into(),
94                parent_trace,
95            )
96            .await?;
97
98        Ok(AgentRun {
99            thread_id,
100            turn_id,
101            thread,
102        })
103    }
104}