diff --git a/codex-rs/core/src/context/token_budget_context.rs b/codex-rs/core/src/context/token_budget_context.rs index c52993b3a9..0d7bf383f5 100644 --- a/codex-rs/core/src/context/token_budget_context.rs +++ b/codex-rs/core/src/context/token_budget_context.rs @@ -15,7 +15,7 @@ pub(crate) struct TokenBudgetContext { first_window_id: Uuid, previous_window_id: Option, window_id: Uuid, - mcp_result: Option, + thread_hint: Option, } impl TokenBudgetContext { @@ -24,14 +24,14 @@ impl TokenBudgetContext { first_window_id: Uuid, previous_window_id: Option, window_id: Uuid, - mcp_result: Option, + thread_hint: Option, ) -> Self { Self { agent_path, first_window_id, previous_window_id, window_id, - mcp_result, + thread_hint, } } } @@ -68,8 +68,8 @@ impl ContextualUserFragment for TokenBudgetContext { if let Some(previous_window_id) = self.previous_window_id { lines.push(format!("Previous context window id: {previous_window_id}")); } - if let Some(mcp_result) = &self.mcp_result { - lines.push(mcp_result.clone()); + if let Some(thread_hint) = &self.thread_hint { + lines.push(thread_hint.clone()); } format!("\n{}\n", lines.join("\n")) } diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 83d3e994d6..861079a2b1 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -65,6 +65,7 @@ use codex_execpolicy::prefix_rule_migration; use codex_extension_api::ConversationHistorySnapshot; use codex_extension_api::ExtensionDataInit; use codex_extension_api::LoadedUserInstructions; +use codex_extension_api::PromptSlot; use codex_extension_api::TurnContextContributionInput; use codex_features::FEATURES; use codex_features::Feature; @@ -3590,6 +3591,7 @@ impl Session { let mut developer_sections = Vec::::with_capacity(8); let mut contextual_user_sections = Vec::::with_capacity(2); let mut separate_developer_sections = Vec::::new(); + let mut context_window_hints = Vec::new(); let (session_source, auto_compact_window_ids) = { let state = self.state.lock().await; ( @@ -3649,7 +3651,14 @@ impl Session { ) .await { - developer_sections.push(fragment.into()); + match fragment.slot() { + PromptSlot::ContextWindow => { + context_window_hints.push(fragment.text().to_string()); + } + PromptSlot::DeveloperPolicy | PromptSlot::DeveloperCapabilities => { + developer_sections.push(fragment.into()); + } + } } } for contributor in &context_contributors { @@ -3671,34 +3680,44 @@ impl Session { if turn_context.config.features.enabled(Feature::TokenBudget) && turn_context.model_context_window().is_some() { - let mcp_result = self - .services - .mcp_runtime - .latest_call_tool( - "notes", - "thread_hint", - /*environment_id*/ None, - /*arguments*/ None, - Some(serde_json::json!({ - "threadId": self.thread_id().to_string(), - })), - /*requested_timeout*/ None, - /*wait_for_server*/ true, - ) - .await - .ok() - .and_then(|result| { - let text = result - .content - .iter() - .filter_map(|content| { - content.get("text").and_then(serde_json::Value::as_str) - }) - .filter(|text| !text.is_empty()) - .collect::>() - .join("\n"); - (!text.is_empty()).then_some(text) - }); + // Keep the legacy bridge hint when native Notes is disabled. A failed + // native request must not fall back to the bridge. + if !turn_context + .config + .token_budget + .as_ref() + .is_some_and(|config| config.use_history_notes_extension) + && let Some(mcp_result) = self + .services + .mcp_runtime + .latest_call_tool( + "notes", + "thread_hint", + /*environment_id*/ None, + /*arguments*/ None, + Some(serde_json::json!({ + "threadId": self.thread_id().to_string(), + })), + /*requested_timeout*/ None, + /*wait_for_server*/ true, + ) + .await + .ok() + .and_then(|result| { + let text = result + .content + .iter() + .filter_map(|content| { + content.get("text").and_then(serde_json::Value::as_str) + }) + .filter(|text| !text.is_empty()) + .collect::>() + .join("\n"); + (!text.is_empty()).then_some(text) + }) + { + context_window_hints.push(mcp_result); + } separate_developer_sections.push( crate::context::TokenBudgetContext::new( session_source @@ -3707,7 +3726,7 @@ impl Session { auto_compact_window_ids.first_window_id, auto_compact_window_ids.previous_window_id, auto_compact_window_ids.window_id, - mcp_result, + (!context_window_hints.is_empty()).then(|| context_window_hints.join("\n")), ) .render_fragment(), ); diff --git a/codex-rs/core/src/session/world_state.rs b/codex-rs/core/src/session/world_state.rs index d9640fcb20..2baaf8768d 100644 --- a/codex-rs/core/src/session/world_state.rs +++ b/codex-rs/core/src/session/world_state.rs @@ -116,7 +116,7 @@ impl Session { window_ids.first_window_id, window_ids.previous_window_id, window_ids.window_id, - /*mcp_result*/ None, + /*thread_hint*/ None, )); if let Some(guidance) = turn_context .config diff --git a/codex-rs/ext/extension-api/src/contributors/prompt.rs b/codex-rs/ext/extension-api/src/contributors/prompt.rs index cc4e261925..73a12e3a6a 100644 --- a/codex-rs/ext/extension-api/src/contributors/prompt.rs +++ b/codex-rs/ext/extension-api/src/contributors/prompt.rs @@ -8,6 +8,8 @@ use codex_protocol::models::ContentItemKind; pub enum PromptSlot { DeveloperPolicy, DeveloperCapabilities, + /// Text inside the context-window message, supplied by `contribute_thread_context`. + ContextWindow, } #[derive(Clone, Debug, PartialEq, Eq)]