diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index dd5dac527d..8d6532aa7c 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,5 +1,11 @@
{
"recommendations": [
+ "rust-lang.rust-analyzer",
"tamasfe.even-better-toml",
+ "vadimcn.vscode-lldb",
+
+ // Useful if touching files in .github/workflows, though most
+ // contributors will not be doing that?
+ // "github.vscode-github-actions",
]
}
diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock
index 51d65d5870..db4ab916aa 100644
--- a/codex-rs/Cargo.lock
+++ b/codex-rs/Cargo.lock
@@ -298,17 +298,6 @@ dependencies = [
"syn 2.0.104",
]
-[[package]]
-name = "async-trait"
-version = "0.1.88"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.104",
-]
-
[[package]]
name = "atomic-waker"
version = "1.1.2"
@@ -935,6 +924,7 @@ dependencies = [
"serde",
"serde_bytes",
"serde_json",
+ "serde_with",
"strum 0.27.2",
"strum_macros 0.27.2",
"tracing",
@@ -1280,12 +1270,12 @@ dependencies = [
[[package]]
name = "deadpool"
-version = "0.10.0"
+version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb84100978c1c7b37f09ed3ce3e5f843af02c2a2c431bae5b19230dad2c1b490"
+checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b"
dependencies = [
- "async-trait",
"deadpool-runtime",
+ "lazy_static",
"num_cpus",
"tokio",
]
@@ -2132,13 +2122,14 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
-version = "1.6.0"
+version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
+checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e"
dependencies = [
+ "atomic-waker",
"bytes",
"futures-channel",
- "futures-util",
+ "futures-core",
"h2",
"http",
"http-body",
@@ -2146,6 +2137,7 @@ dependencies = [
"httpdate",
"itoa",
"pin-project-lite",
+ "pin-utils",
"smallvec",
"tokio",
"want",
@@ -6308,12 +6300,11 @@ checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]]
name = "wiremock"
-version = "0.6.4"
+version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2b8b99d4cdbf36b239a9532e31fe4fb8acc38d1897c1761e161550a7dc78e6a"
+checksum = "08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031"
dependencies = [
"assert-json-diff",
- "async-trait",
"base64 0.22.1",
"deadpool",
"futures",
diff --git a/codex-rs/cli/src/login.rs b/codex-rs/cli/src/login.rs
index b8a8e45e02..cff03e7c16 100644
--- a/codex-rs/cli/src/login.rs
+++ b/codex-rs/cli/src/login.rs
@@ -12,8 +12,8 @@ use codex_protocol::mcp_protocol::AuthMode;
use std::env;
use std::path::PathBuf;
-pub async fn login_with_chatgpt(codex_home: PathBuf) -> std::io::Result<()> {
- let opts = ServerOptions::new(codex_home, CLIENT_ID.to_string());
+pub async fn login_with_chatgpt(codex_home: PathBuf, originator: String) -> std::io::Result<()> {
+ let opts = ServerOptions::new(codex_home, CLIENT_ID.to_string(), originator);
let server = run_login_server(opts)?;
eprintln!(
@@ -27,7 +27,12 @@ pub async fn login_with_chatgpt(codex_home: PathBuf) -> std::io::Result<()> {
pub async fn run_login_with_chatgpt(cli_config_overrides: CliConfigOverrides) -> ! {
let config = load_config_or_exit(cli_config_overrides);
- match login_with_chatgpt(config.codex_home).await {
+ match login_with_chatgpt(
+ config.codex_home,
+ config.responses_originator_header.clone(),
+ )
+ .await
+ {
Ok(_) => {
eprintln!("Successfully logged in");
std::process::exit(0);
diff --git a/codex-rs/core/prompt.md b/codex-rs/core/prompt.md
index cc7e930a5d..4b39ed6bbe 100644
--- a/codex-rs/core/prompt.md
+++ b/codex-rs/core/prompt.md
@@ -228,7 +228,6 @@ You are producing plain text that will later be styled by the CLI. Follow these
**Bullets**
- Use `-` followed by a space for every bullet.
-- Bold the keyword, then colon + concise description.
- Merge related points when possible; avoid a bullet for every trivial detail.
- Keep bullets to one line unless breaking for clarity is unavoidable.
- Group into short lists (4–6 bullets) ordered by importance.
diff --git a/codex-rs/core/src/client_common.rs b/codex-rs/core/src/client_common.rs
index 77f6a63a09..d561d36946 100644
--- a/codex-rs/core/src/client_common.rs
+++ b/codex-rs/core/src/client_common.rs
@@ -1,4 +1,3 @@
-use crate::config_types::Verbosity as VerbosityConfig;
use crate::error::Result;
use crate::model_family::ModelFamily;
use crate::openai_tools::OpenAiTool;
@@ -6,7 +5,7 @@ use crate::protocol::TokenUsage;
use codex_apply_patch::APPLY_PATCH_TOOL_INSTRUCTIONS;
use codex_protocol::config_types::ReasoningEffort as ReasoningEffortConfig;
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
-use codex_protocol::models::ContentItem;
+use codex_protocol::config_types::Verbosity as VerbosityConfig;
use codex_protocol::models::ResponseItem;
use futures::Stream;
use serde::Serialize;
@@ -20,10 +19,6 @@ use tokio::sync::mpsc;
/// with this content.
const BASE_INSTRUCTIONS: &str = include_str!("../prompt.md");
-/// wraps user instructions message in a tag for the model to parse more easily.
-const USER_INSTRUCTIONS_START: &str = "\n\n";
-const USER_INSTRUCTIONS_END: &str = "\n\n";
-
/// API request payload for a single model turn
#[derive(Default, Debug, Clone)]
pub struct Prompt {
@@ -68,17 +63,6 @@ impl Prompt {
pub(crate) fn get_formatted_input(&self) -> Vec {
self.input.clone()
}
-
- /// Creates a formatted user instructions message from a string
- pub(crate) fn format_user_instructions_message(ui: &str) -> ResponseItem {
- ResponseItem::Message {
- id: None,
- role: "user".to_string(),
- content: vec![ContentItem::InputText {
- text: format!("{USER_INSTRUCTIONS_START}{ui}{USER_INSTRUCTIONS_END}"),
- }],
- }
- }
}
#[derive(Debug)]
diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs
index cf1ed5d148..f461af72b6 100644
--- a/codex-rs/core/src/codex.rs
+++ b/codex-rs/core/src/codex.rs
@@ -9,6 +9,7 @@ use std::sync::atomic::AtomicU64;
use std::time::Duration;
use crate::AuthManager;
+use crate::event_mapping::map_response_item_to_event_messages;
use async_channel::Receiver;
use async_channel::Sender;
use codex_apply_patch::ApplyPatchAction;
@@ -78,9 +79,7 @@ use crate::project_doc::get_user_instructions;
use crate::protocol::AgentMessageDeltaEvent;
use crate::protocol::AgentMessageEvent;
use crate::protocol::AgentReasoningDeltaEvent;
-use crate::protocol::AgentReasoningEvent;
use crate::protocol::AgentReasoningRawContentDeltaEvent;
-use crate::protocol::AgentReasoningRawContentEvent;
use crate::protocol::AgentReasoningSectionBreakEvent;
use crate::protocol::ApplyPatchApprovalRequestEvent;
use crate::protocol::AskForApproval;
@@ -105,13 +104,13 @@ use crate::protocol::Submission;
use crate::protocol::TaskCompleteEvent;
use crate::protocol::TurnDiffEvent;
use crate::protocol::WebSearchBeginEvent;
-use crate::protocol::WebSearchEndEvent;
use crate::rollout::RolloutRecorder;
use crate::safety::SafetyCheck;
use crate::safety::assess_command_safety;
use crate::safety::assess_safety_for_untrusted_command;
use crate::shell;
use crate::turn_diff_tracker::TurnDiffTracker;
+use crate::user_instructions::UserInstructions;
use crate::user_notification::UserNotification;
use crate::util::backoff;
use codex_protocol::config_types::ReasoningEffort as ReasoningEffortConfig;
@@ -120,12 +119,9 @@ use codex_protocol::custom_prompts::CustomPrompt;
use codex_protocol::models::ContentItem;
use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::LocalShellAction;
-use codex_protocol::models::ReasoningItemContent;
-use codex_protocol::models::ReasoningItemReasoningSummary;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::models::ResponseItem;
use codex_protocol::models::ShellToolCallParams;
-use codex_protocol::models::WebSearchAction;
// A convenience extension trait for acquiring mutex locks where poisoning is
// unrecoverable and should abort the program. This avoids scattered `.unwrap()`
@@ -202,6 +198,7 @@ impl Codex {
config.clone(),
auth_manager.clone(),
tx_event.clone(),
+ conversation_history.clone(),
)
.await
.map_err(|e| {
@@ -364,6 +361,7 @@ impl Session {
config: Arc,
auth_manager: Arc,
tx_event: Sender,
+ initial_history: InitialHistory,
) -> anyhow::Result<(Arc, TurnContext)> {
let session_id = Uuid::new_v4();
let ConfigureSession {
@@ -483,6 +481,12 @@ impl Session {
});
// Dispatch the SessionConfiguredEvent first and then report any errors.
+ // If resuming, include converted initial messages in the payload so UIs can render them immediately.
+ let initial_messages = match &initial_history {
+ InitialHistory::New => None,
+ InitialHistory::Resumed(items) => Some(sess.build_initial_messages(items)),
+ };
+
let events = std::iter::once(Event {
id: INITIAL_SUBMIT_ID.to_owned(),
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
@@ -490,6 +494,7 @@ impl Session {
model,
history_log_id,
history_entry_count,
+ initial_messages,
}),
})
.chain(post_session_configured_error_events.into_iter());
@@ -540,7 +545,7 @@ impl Session {
// TODO: Those items shouldn't be "user messages" IMO. Maybe developer messages.
let mut conversation_items = Vec::::with_capacity(2);
if let Some(user_instructions) = turn_context.user_instructions.as_deref() {
- conversation_items.push(Prompt::format_user_instructions_message(user_instructions));
+ conversation_items.push(UserInstructions::new(user_instructions.to_string()).into());
}
conversation_items.push(ResponseItem::from(EnvironmentContext::new(
Some(turn_context.cwd.clone()),
@@ -555,6 +560,17 @@ impl Session {
self.record_conversation_items(&items).await;
}
+ /// build the initial messages vector for SessionConfigured by converting
+ /// ResponseItems into EventMsg.
+ fn build_initial_messages(&self, items: &[ResponseItem]) -> Vec {
+ items
+ .iter()
+ .flat_map(|item| {
+ map_response_item_to_event_messages(item, self.show_raw_agent_reasoning)
+ })
+ .collect()
+ }
+
/// Sends the given event to the client and swallows the send event, if
/// any, logging it as an error.
pub(crate) async fn send_event(&self, event: Event) {
@@ -571,9 +587,19 @@ impl Session {
cwd: PathBuf,
reason: Option,
) -> oneshot::Receiver {
+ // Add the tx_approve callback to the map before sending the request.
let (tx_approve, rx_approve) = oneshot::channel();
+ let event_id = sub_id.clone();
+ let prev_entry = {
+ let mut state = self.state.lock_unchecked();
+ state.pending_approvals.insert(sub_id, tx_approve)
+ };
+ if prev_entry.is_some() {
+ warn!("Overwriting existing pending approval for sub_id: {event_id}");
+ }
+
let event = Event {
- id: sub_id.clone(),
+ id: event_id,
msg: EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent {
call_id,
command,
@@ -582,10 +608,6 @@ impl Session {
}),
};
let _ = self.tx_event.send(event).await;
- {
- let mut state = self.state.lock_unchecked();
- state.pending_approvals.insert(sub_id, tx_approve);
- }
rx_approve
}
@@ -597,9 +619,19 @@ impl Session {
reason: Option,
grant_root: Option,
) -> oneshot::Receiver {
+ // Add the tx_approve callback to the map before sending the request.
let (tx_approve, rx_approve) = oneshot::channel();
+ let event_id = sub_id.clone();
+ let prev_entry = {
+ let mut state = self.state.lock_unchecked();
+ state.pending_approvals.insert(sub_id, tx_approve)
+ };
+ if prev_entry.is_some() {
+ warn!("Overwriting existing pending approval for sub_id: {event_id}");
+ }
+
let event = Event {
- id: sub_id.clone(),
+ id: event_id,
msg: EventMsg::ApplyPatchApprovalRequest(ApplyPatchApprovalRequestEvent {
call_id,
changes: convert_apply_patch_to_protocol(action),
@@ -608,10 +640,6 @@ impl Session {
}),
};
let _ = self.tx_event.send(event).await;
- {
- let mut state = self.state.lock_unchecked();
- state.pending_approvals.insert(sub_id, tx_approve);
- }
rx_approve
}
@@ -1906,53 +1934,6 @@ async fn handle_response_item(
) -> CodexResult