Compare commits

...

1 Commits

Author SHA1 Message Date
Ahmed Ibrahim
c12b629417 Remove Op::UserInput and defer user-turn defaults 2026-01-26 19:41:02 -08:00
50 changed files with 845 additions and 686 deletions

View File

@@ -3605,10 +3605,7 @@ impl CodexMessageProcessor {
// Submit user input to the conversation.
let _ = conversation
.submit(Op::UserInput {
items: mapped_items,
final_output_json_schema: None,
})
.submit_user_turn_with_defaults(mapped_items, None)
.await;
// Acknowledge with an empty result.
@@ -3657,6 +3654,7 @@ impl CodexMessageProcessor {
let _ = conversation
.submit(Op::UserTurn {
use_thread_defaults: false,
items: mapped_items,
cwd,
approval_policy,
@@ -3871,36 +3869,35 @@ impl CodexMessageProcessor {
.map(V2UserInput::into_core)
.collect();
let has_any_overrides = params.cwd.is_some()
|| params.approval_policy.is_some()
|| params.sandbox_policy.is_some()
|| params.model.is_some()
|| params.effort.is_some()
|| params.summary.is_some()
|| params.collaboration_mode.is_some()
|| params.personality.is_some();
// If any overrides are provided, update the session turn context first.
if has_any_overrides {
let _ = thread
.submit(Op::OverrideTurnContext {
cwd: params.cwd,
approval_policy: params.approval_policy.map(AskForApproval::to_core),
sandbox_policy: params.sandbox_policy.map(|p| p.to_core()),
model: params.model,
effort: params.effort.map(Some),
summary: params.summary,
collaboration_mode: params.collaboration_mode,
personality: params.personality,
})
.await;
}
// Start the turn by submitting the user input. Return its submission id as turn_id.
// Start the turn by submitting the user input with the thread's current defaults,
// overridden by any per-turn parameters.
let snapshot = thread.config_snapshot().await;
let cwd = params.cwd.unwrap_or(snapshot.cwd);
let approval_policy = params
.approval_policy
.map(AskForApproval::to_core)
.unwrap_or(snapshot.approval_policy);
let sandbox_policy = params
.sandbox_policy
.map(|policy| policy.to_core())
.unwrap_or(snapshot.sandbox_policy);
let model = params.model.unwrap_or(snapshot.model);
let effort = params.effort.or(snapshot.reasoning_effort);
let summary = params.summary.unwrap_or(snapshot.reasoning_summary);
let personality = params.personality.or(snapshot.personality);
let turn_id = thread
.submit(Op::UserInput {
.submit(Op::UserTurn {
use_thread_defaults: false,
items: mapped_items,
cwd,
approval_policy,
sandbox_policy,
model,
effort,
summary,
final_output_json_schema: params.output_schema,
collaboration_mode: params.collaboration_mode,
personality,
})
.await;

View File

@@ -72,19 +72,26 @@ impl AgentControl {
prompt: String,
) -> CodexResult<String> {
let state = self.upgrade()?;
let result = state
.send_op(
agent_id,
Op::UserInput {
items: vec![UserInput::Text {
text: prompt,
// Agent control prompts are plain text with no UI text elements.
text_elements: Vec::new(),
}],
final_output_json_schema: None,
},
)
.await;
let thread = state.get_thread(agent_id).await?;
let snapshot = thread.config_snapshot().await;
let op = Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: prompt,
// Agent control prompts are plain text with no UI text elements.
text_elements: Vec::new(),
}],
cwd: snapshot.cwd,
approval_policy: snapshot.approval_policy,
sandbox_policy: snapshot.sandbox_policy,
model: snapshot.model,
effort: snapshot.reasoning_effort,
summary: snapshot.reasoning_summary,
final_output_json_schema: None,
collaboration_mode: None,
personality: snapshot.personality,
};
let result = state.send_op(agent_id, op).await;
if matches!(result, Err(CodexErr::InternalAgentDied)) {
let _ = state.remove_thread(&agent_id).await;
self.state.release_spawned_thread(agent_id);
@@ -347,7 +354,8 @@ mod tests {
#[tokio::test]
async fn send_prompt_submits_user_message() {
let harness = AgentControlHarness::new().await;
let (thread_id, _thread) = harness.start_thread().await;
let (thread_id, thread) = harness.start_thread().await;
let snapshot = thread.config_snapshot().await;
let submission_id = harness
.control
@@ -357,12 +365,21 @@ mod tests {
assert!(!submission_id.is_empty());
let expected = (
thread_id,
Op::UserInput {
Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello from tests".to_string(),
text_elements: Vec::new(),
}],
cwd: snapshot.cwd,
approval_policy: snapshot.approval_policy,
sandbox_policy: snapshot.sandbox_policy,
model: snapshot.model,
effort: snapshot.reasoning_effort,
summary: snapshot.reasoning_summary,
final_output_json_schema: None,
collaboration_mode: None,
personality: snapshot.personality,
},
);
let captured = harness
@@ -381,19 +398,29 @@ mod tests {
.spawn_agent(harness.config.clone(), "spawned".to_string(), None)
.await
.expect("spawn_agent should succeed");
let _thread = harness
let thread = harness
.manager
.get_thread(thread_id)
.await
.expect("thread should be registered");
let snapshot = thread.config_snapshot().await;
let expected = (
thread_id,
Op::UserInput {
Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "spawned".to_string(),
text_elements: Vec::new(),
}],
cwd: snapshot.cwd,
approval_policy: snapshot.approval_policy,
sandbox_policy: snapshot.sandbox_policy,
model: snapshot.model,
effort: snapshot.reasoning_effort,
summary: snapshot.reasoning_summary,
final_output_json_schema: None,
collaboration_mode: None,
personality: snapshot.personality,
},
);
let captured = harness

View File

@@ -521,6 +521,7 @@ impl SessionConfiguration {
sandbox_policy: self.sandbox_policy.get().clone(),
cwd: self.cwd.clone(),
reasoning_effort: self.collaboration_mode.reasoning_effort(),
reasoning_summary: self.model_reasoning_summary,
personality: self.personality,
session_source: self.session_source.clone(),
}
@@ -2175,9 +2176,8 @@ async fn submission_loop(sess: Arc<Session>, config: Arc<Config>, rx_sub: Receiv
)
.await;
}
Op::UserInput { .. } | Op::UserTurn { .. } => {
handlers::user_input_or_turn(&sess, sub.id.clone(), sub.op, &mut previous_context)
.await;
Op::UserTurn { .. } => {
handlers::user_turn(&sess, sub.id.clone(), sub.op, &mut previous_context).await;
}
Op::ExecApproval { id, decision } => {
handlers::exec_approval(&sess, id, decision).await;
@@ -2343,59 +2343,83 @@ mod handlers {
}
}
pub async fn user_input_or_turn(
pub async fn user_turn(
sess: &Arc<Session>,
sub_id: String,
op: Op,
previous_context: &mut Option<Arc<TurnContext>>,
) {
let (items, updates) = match op {
Op::UserTurn {
cwd,
approval_policy,
sandbox_policy,
model,
effort,
summary,
final_output_json_schema,
items,
collaboration_mode,
personality,
} => {
let collaboration_mode = collaboration_mode.or_else(|| {
Some(CollaborationMode {
mode: ModeKind::Custom,
settings: Settings {
model: model.clone(),
reasoning_effort: effort,
developer_instructions: None,
},
})
});
let Op::UserTurn {
use_thread_defaults,
cwd,
approval_policy,
sandbox_policy,
model,
effort,
summary,
final_output_json_schema,
items,
collaboration_mode,
personality,
} = op
else {
unreachable!();
};
let snapshot = if use_thread_defaults {
let state = sess.state.lock().await;
Some(state.session_configuration.thread_config_snapshot())
} else {
None
};
let (cwd, approval_policy, sandbox_policy, model, effort, summary, snapshot_personality) =
if let Some(snapshot) = snapshot {
(
items,
SessionSettingsUpdate {
cwd: Some(cwd),
approval_policy: Some(approval_policy),
sandbox_policy: Some(sandbox_policy),
collaboration_mode,
reasoning_summary: Some(summary),
final_output_json_schema: Some(final_output_json_schema),
personality,
},
snapshot.cwd,
snapshot.approval_policy,
snapshot.sandbox_policy,
snapshot.model,
snapshot.reasoning_effort,
snapshot.reasoning_summary,
snapshot.personality,
)
}
Op::UserInput {
items,
final_output_json_schema,
} => (
items,
SessionSettingsUpdate {
final_output_json_schema: Some(final_output_json_schema),
..Default::default()
},
),
_ => unreachable!(),
} else {
(
cwd,
approval_policy,
sandbox_policy,
model,
effort,
summary,
None,
)
};
let personality = if use_thread_defaults {
personality.or(snapshot_personality)
} else {
personality
};
let collaboration_mode = if use_thread_defaults {
collaboration_mode
} else {
collaboration_mode.or_else(|| {
Some(CollaborationMode {
mode: ModeKind::Custom,
settings: Settings {
model: model.clone(),
reasoning_effort: effort,
developer_instructions: None,
},
})
})
};
let updates = SessionSettingsUpdate {
cwd: Some(cwd),
approval_policy: Some(approval_policy),
sandbox_policy: Some(sandbox_policy),
collaboration_mode,
reasoning_summary: Some(summary),
final_output_json_schema: Some(final_output_json_schema),
personality,
};
let previous_collaboration_mode = sess

View File

@@ -126,9 +126,19 @@ pub(crate) async fn run_codex_thread_one_shot(
.await?;
// Send the initial input to kick off the one-shot turn.
io.submit(Op::UserInput {
let snapshot = io.thread_config_snapshot().await;
io.submit(Op::UserTurn {
use_thread_defaults: false,
items: input,
cwd: snapshot.cwd,
approval_policy: snapshot.approval_policy,
sandbox_policy: snapshot.sandbox_policy,
model: snapshot.model,
effort: snapshot.reasoning_effort,
summary: snapshot.reasoning_summary,
final_output_json_schema: None,
collaboration_mode: None,
personality: snapshot.personality,
})
.await?;

View File

@@ -5,10 +5,13 @@ use crate::protocol::Event;
use crate::protocol::Op;
use crate::protocol::Submission;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionSource;
use codex_protocol::user_input::UserInput;
use serde_json::Value;
use std::path::PathBuf;
use tokio::sync::watch;
@@ -20,6 +23,7 @@ pub struct ThreadConfigSnapshot {
pub sandbox_policy: SandboxPolicy,
pub cwd: PathBuf,
pub reasoning_effort: Option<ReasoningEffort>,
pub reasoning_summary: ReasoningSummary,
pub personality: Option<Personality>,
pub session_source: SessionSource,
}
@@ -67,4 +71,38 @@ impl CodexThread {
pub async fn config_snapshot(&self) -> ThreadConfigSnapshot {
self.codex.thread_config_snapshot().await
}
/// Build a user turn using the thread's current default settings.
pub async fn user_turn_with_defaults(
&self,
items: Vec<UserInput>,
final_output_json_schema: Option<Value>,
) -> Op {
let snapshot = self.config_snapshot().await;
Op::UserTurn {
use_thread_defaults: true,
items,
cwd: snapshot.cwd,
approval_policy: snapshot.approval_policy,
sandbox_policy: snapshot.sandbox_policy,
model: snapshot.model,
effort: snapshot.reasoning_effort,
summary: snapshot.reasoning_summary,
final_output_json_schema,
collaboration_mode: None,
personality: snapshot.personality,
}
}
/// Submit a user turn using the thread's current default settings.
pub async fn submit_user_turn_with_defaults(
&self,
items: Vec<UserInput>,
final_output_json_schema: Option<Value>,
) -> CodexResult<String> {
let op = self
.user_turn_with_defaults(items, final_output_json_schema)
.await;
self.submit(op).await
}
}

View File

@@ -889,7 +889,7 @@ mod tests {
.collect();
assert_eq!(ops_for_agent.len(), 2);
assert!(matches!(ops_for_agent[0], Op::Interrupt));
assert!(matches!(ops_for_agent[1], Op::UserInput { .. }));
assert!(matches!(ops_for_agent[1], Op::UserTurn { .. }));
let _ = thread
.thread

View File

@@ -268,6 +268,7 @@ impl TestCodex {
let session_model = self.session_configured.model.clone();
self.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: prompt.into(),
text_elements: Vec::new(),

View File

@@ -45,13 +45,13 @@ async fn interrupt_long_running_tool_emits_turn_aborted() {
// Kick off a turn that triggers the function call.
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "start sleep".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -99,13 +99,13 @@ async fn interrupt_tool_records_history_entries() {
let codex = Arc::clone(&fixture.codex);
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "start history recording".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -117,13 +117,13 @@ async fn interrupt_tool_records_history_entries() {
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnAborted(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "follow up".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -197,13 +197,13 @@ async fn interrupt_persists_turn_aborted_marker_in_next_request() {
let codex = Arc::clone(&fixture.codex);
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "start interrupt marker".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -215,13 +215,13 @@ async fn interrupt_persists_turn_aborted_marker_in_next_request() {
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnAborted(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "follow up".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();

View File

@@ -300,6 +300,7 @@ async fn apply_patch_cli_move_without_content_change_has_no_turn_diff(
let model = test.session_configured.model.clone();
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "rename without content change".into(),
text_elements: Vec::new(),
@@ -889,6 +890,7 @@ async fn apply_patch_shell_command_heredoc_with_cd_emits_turn_diff() -> Result<(
let model = test.session_configured.model.clone();
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "apply via shell heredoc with cd".into(),
text_elements: Vec::new(),
@@ -969,6 +971,7 @@ async fn apply_patch_shell_command_failure_propagates_error_and_skips_diff() ->
let model = test.session_configured.model.clone();
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "apply patch via shell".into(),
text_elements: Vec::new(),
@@ -1119,6 +1122,7 @@ async fn apply_patch_emits_turn_diff_event_with_unified_diff(
let model = test.session_configured.model.clone();
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "emit diff".into(),
text_elements: Vec::new(),
@@ -1182,6 +1186,7 @@ async fn apply_patch_turn_diff_for_rename_with_content_change(
let model = test.session_configured.model.clone();
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "rename with change".into(),
text_elements: Vec::new(),
@@ -1253,6 +1258,7 @@ async fn apply_patch_aggregates_diff_across_multiple_tool_calls() -> Result<()>
let model = test.session_configured.model.clone();
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "aggregate diffs".into(),
text_elements: Vec::new(),
@@ -1324,6 +1330,7 @@ async fn apply_patch_aggregates_diff_preserves_success_after_failure() -> Result
let model = test.session_configured.model.clone();
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "apply patch twice with failure".into(),
text_elements: Vec::new(),

View File

@@ -490,6 +490,7 @@ async fn submit_turn(
test.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: prompt.into(),
text_elements: Vec::new(),

View File

@@ -294,13 +294,13 @@ async fn resume_includes_initial_messages_and_sends_prior_items() {
// 2) Submit new input; the request body must include the prior items, then initial context, then new user input.
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -393,13 +393,13 @@ async fn includes_conversation_id_and_model_headers_in_request() {
.expect("create new conversation");
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -447,13 +447,13 @@ async fn includes_base_instructions_override_in_request() {
.thread;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -504,13 +504,13 @@ async fn chatgpt_auth_sends_correct_request() {
.expect("create new conversation");
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -597,13 +597,13 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() {
.expect("create new conversation");
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -639,13 +639,13 @@ async fn includes_user_instructions_message_in_request() {
.thread;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -719,13 +719,13 @@ async fn skills_append_to_instructions() {
.thread;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -772,13 +772,13 @@ async fn includes_configured_effort_in_request() -> anyhow::Result<()> {
.await?;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -810,13 +810,13 @@ async fn includes_no_effort_in_request() -> anyhow::Result<()> {
.await?;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -846,13 +846,13 @@ async fn includes_default_reasoning_effort_in_request_when_defined_by_model_info
let TestCodex { codex, .. } = test_codex().with_model("gpt-5.1").build(&server).await?;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -899,6 +899,7 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -944,13 +945,13 @@ async fn configured_reasoning_summary_is_sent() -> anyhow::Result<()> {
.await?;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -984,13 +985,13 @@ async fn reasoning_summary_is_omitted_when_disabled() -> anyhow::Result<()> {
.await?;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -1018,13 +1019,13 @@ async fn includes_default_verbosity_in_request() -> anyhow::Result<()> {
let TestCodex { codex, .. } = test_codex().with_model("gpt-5.1").build(&server).await?;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -1059,13 +1060,13 @@ async fn configured_verbosity_not_sent_for_models_without_support() -> anyhow::R
.await?;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -1099,13 +1100,13 @@ async fn configured_verbosity_is_sent() -> anyhow::Result<()> {
.await?;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -1155,13 +1156,13 @@ async fn includes_developer_instructions_message_in_request() {
.thread;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -1406,13 +1407,13 @@ async fn token_count_includes_rate_limits_snapshot() {
.thread;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -1565,13 +1566,13 @@ async fn usage_limit_error_emits_rate_limit_event() -> anyhow::Result<()> {
});
let submission_id = codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.expect("submission should succeed while emitting usage limit error events");
@@ -1636,25 +1637,25 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res
.await?;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "seed turn".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "trigger context window".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
let token_event = wait_for_event(&codex, |event| {
@@ -1769,13 +1770,13 @@ async fn azure_overrides_assign_properties_used_for_responses_url() {
.thread;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -1853,13 +1854,13 @@ async fn env_var_overrides_loaded_auth() {
.thread;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -1928,39 +1929,39 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() {
// Turn 1: user sends U1; wait for completion.
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "U1".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
// Turn 2: user sends U2; wait for completion.
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "U2".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
// Turn 3: user sends U3; wait for completion.
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "U3".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;

View File

@@ -70,13 +70,13 @@ async fn no_collaboration_instructions_by_default() -> Result<()> {
let test = test_codex().build(&server).await?;
test.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -113,13 +113,13 @@ async fn user_input_includes_collaboration_instructions_after_override() -> Resu
.await?;
test.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -144,6 +144,7 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> {
test.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -195,6 +196,7 @@ async fn override_then_user_turn_uses_updated_collaboration_instructions() -> Re
test.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -248,6 +250,7 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu
test.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -301,13 +304,13 @@ async fn collaboration_mode_update_emits_new_instruction_message() -> Result<()>
.await?;
test.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 1".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -325,13 +328,13 @@ async fn collaboration_mode_update_emits_new_instruction_message() -> Result<()>
.await?;
test.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 2".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -370,13 +373,13 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> {
.await?;
test.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 1".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -394,13 +397,13 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> {
.await?;
test.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 2".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -446,26 +449,26 @@ async fn resume_replays_collaboration_instructions() -> Result<()> {
initial
.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
let resumed = builder.resume(&server, home, rollout_path).await?;
resumed
.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "after resume".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&resumed.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -500,13 +503,13 @@ async fn empty_collaboration_instructions_are_ignored() -> Result<()> {
.await?;
test.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;

View File

@@ -158,13 +158,13 @@ async fn summarize_context_three_requests_and_instructions() {
// 1) Normal user input should hit server once.
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello world".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -180,13 +180,13 @@ async fn summarize_context_three_requests_and_instructions() {
// 3) Next user input third hit; history should include only the summary.
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: THIRD_USER_MSG.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -574,13 +574,13 @@ async fn multiple_auto_compact_per_task_runs_after_token_limit_hit() {
// Start the conversation with the user message
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: user_message.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.expect("submit user input");
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -1051,39 +1051,39 @@ async fn auto_compact_runs_after_token_limit_hit() {
let codex = thread_manager.start_thread(config).await.unwrap().thread;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: FIRST_AUTO_MSG.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: SECOND_AUTO_MSG.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: POST_AUTO_USER_MSG.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -1284,6 +1284,7 @@ async fn auto_compact_runs_after_resume_when_token_usage_is_over_limit() {
resumed
.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: follow_up_user.into(),
text_elements: Vec::new(),
@@ -1395,37 +1396,37 @@ async fn auto_compact_persists_rollout_entries() {
} = thread_manager.start_thread(config).await.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: FIRST_AUTO_MSG.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: SECOND_AUTO_MSG.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: POST_AUTO_USER_MSG.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -1512,13 +1513,13 @@ async fn manual_compact_retries_after_context_window_error() {
.thread;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "first turn".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -1646,13 +1647,13 @@ async fn manual_compact_twice_preserves_latest_user_messages() {
.thread;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: first_user_message.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -1661,13 +1662,13 @@ async fn manual_compact_twice_preserves_latest_user_messages() {
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: second_user_message.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -1676,13 +1677,13 @@ async fn manual_compact_twice_preserves_latest_user_messages() {
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: final_user_message.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -1857,13 +1858,13 @@ async fn auto_compact_allows_multiple_attempts_when_interleaved_with_other_turn_
let mut auto_compact_lifecycle_events = Vec::new();
for user in [MULTI_AUTO_MSG, follow_up_user, final_user] {
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: user.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -1971,26 +1972,26 @@ async fn auto_compact_triggers_after_function_call_over_95_percent_usage() {
.thread;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: FUNCTION_CALL_LIMIT_MSG.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |msg| matches!(msg, EventMsg::TurnComplete(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: follow_up_user.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -2103,13 +2104,13 @@ async fn auto_compact_counts_encrypted_reasoning_before_last_user() {
.enumerate()
{
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: user.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -2220,13 +2221,13 @@ async fn auto_compact_runs_when_reasoning_header_clears_between_turns() {
for user in [first_user, second_user, third_user] {
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: user.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;

View File

@@ -71,13 +71,13 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> {
.await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello remote compact".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -85,13 +85,13 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> {
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "after compact".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -193,13 +193,13 @@ async fn remote_compact_runs_automatically() -> Result<()> {
.await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello remote compact".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
let message = wait_for_event_match(&codex, |ev| match ev {
EventMsg::ContextCompacted(_) => Some(true),
@@ -274,13 +274,13 @@ async fn remote_compact_persists_replacement_history_in_rollout() -> Result<()>
.await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "needs compaction".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;

View File

@@ -987,13 +987,13 @@ async fn start_test_conversation(
async fn user_turn(conversation: &Arc<CodexThread>, text: &str) {
conversation
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: text.into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.expect("submit user turn");
wait_for_event(conversation, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;

View File

@@ -70,6 +70,7 @@ async fn execpolicy_blocks_shell_invocation() -> Result<()> {
let session_model = test.session_configured.model.clone();
test.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "run shell command".into(),
text_elements: Vec::new(),

View File

@@ -5,7 +5,6 @@ use codex_core::ThreadManager;
use codex_core::built_in_model_providers;
use codex_core::parse_turn_item;
use codex_core::protocol::EventMsg;
use codex_core::protocol::Op;
use codex_core::protocol::RolloutItem;
use codex_core::protocol::RolloutLine;
use codex_protocol::items::TurnItem;
@@ -67,13 +66,13 @@ async fn fork_thread_twice_drops_to_first_message() {
// Send three user messages; wait for three completed turns.
for text in ["first", "second", "third"] {
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: text.to_string(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
let _ = wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;

View File

@@ -111,6 +111,7 @@ async fn copy_paste_local_image_persists_rollout_request_shape() -> anyhow::Resu
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![
UserInput::LocalImage {
path: abs_path.clone(),
@@ -192,6 +193,7 @@ async fn drag_drop_image_persists_rollout_request_shape() -> anyhow::Result<()>
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![
UserInput::Image {
image_url: image_url.clone(),

View File

@@ -4,7 +4,6 @@ use anyhow::Ok;
use codex_core::protocol::EventMsg;
use codex_core::protocol::ItemCompletedEvent;
use codex_core::protocol::ItemStartedEvent;
use codex_core::protocol::Op;
use codex_protocol::items::TurnItem;
use codex_protocol::user_input::ByteRange;
use codex_protocol::user_input::TextElement;
@@ -50,10 +49,7 @@ async fn user_message_item_is_emitted() -> anyhow::Result<()> {
};
codex
.submit(Op::UserInput {
items: vec![expected_input.clone()],
final_output_json_schema: None,
})
.submit_user_turn_with_defaults(vec![expected_input.clone()], None)
.await?;
let started_item = wait_for_event_match(&codex, |ev| match ev {
@@ -103,13 +99,13 @@ async fn assistant_message_item_is_emitted() -> anyhow::Result<()> {
mount_sse_once(&server, first_response).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "please summarize results".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
let started = wait_for_event_match(&codex, |ev| match ev {
@@ -161,13 +157,13 @@ async fn reasoning_item_is_emitted() -> anyhow::Result<()> {
mount_sse_once(&server, first_response).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "explain your reasoning".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
let started = wait_for_event_match(&codex, |ev| match ev {
@@ -221,13 +217,13 @@ async fn web_search_item_is_emitted() -> anyhow::Result<()> {
mount_sse_once(&server, first_response).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "find the weather".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
let started = wait_for_event_match(&codex, |ev| match ev {
@@ -275,13 +271,13 @@ async fn agent_message_content_delta_has_item_metadata() -> anyhow::Result<()> {
mount_sse_once(&server, stream).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "please stream text".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
let (started_turn_id, started_item) = wait_for_event_match(&codex, |ev| match ev {
@@ -342,13 +338,13 @@ async fn reasoning_content_delta_has_item_metadata() -> anyhow::Result<()> {
mount_sse_once(&server, stream).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "reason through it".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
let reasoning_item = wait_for_event_match(&codex, |ev| match ev {
@@ -401,13 +397,13 @@ async fn reasoning_raw_content_delta_respects_flag() -> anyhow::Result<()> {
mount_sse_once(&server, stream).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "show raw reasoning".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
let reasoning_item = wait_for_event_match(&codex, |ev| match ev {

View File

@@ -74,6 +74,7 @@ async fn codex_returns_json_result(model: String) -> anyhow::Result<()> {
// 1) Normal user input should hit server once.
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello world".into(),
text_elements: Vec::new(),

View File

@@ -87,6 +87,7 @@ async fn renews_cache_ttl_on_matching_models_etag() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hi".into(),
text_elements: Vec::new(),

View File

@@ -98,6 +98,7 @@ async fn refresh_models_on_models_etag_mismatch_and_avoid_duplicate_models_fetch
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "please run a tool".into(),
text_elements: Vec::new(),

View File

@@ -42,13 +42,13 @@ async fn responses_api_emits_api_request_event() {
let TestCodex { codex, .. } = test_codex().build(&server).await.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -85,13 +85,13 @@ async fn process_sse_emits_tracing_for_output_item() {
let TestCodex { codex, .. } = test_codex().build(&server).await.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -125,13 +125,13 @@ async fn process_sse_emits_failed_event_on_parse_error() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -166,13 +166,13 @@ async fn process_sse_records_failed_event_when_stream_closes_without_completed()
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -227,13 +227,13 @@ async fn process_sse_failed_event_records_response_error_message() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -286,13 +286,13 @@ async fn process_sse_failed_event_logs_parse_error() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -332,13 +332,13 @@ async fn process_sse_failed_event_logs_missing_error() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -387,13 +387,13 @@ async fn process_sse_failed_event_logs_response_completed_parse_error() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -439,13 +439,13 @@ async fn process_sse_emits_completed_telemetry() {
let TestCodex { codex, .. } = test_codex().build(&server).await.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -508,13 +508,13 @@ async fn handle_responses_span_records_response_kind_and_tool_name() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -574,13 +574,13 @@ async fn record_responses_sets_span_fields_for_response_events() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -655,13 +655,13 @@ async fn handle_response_item_records_tool_result_for_custom_tool_call() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -724,13 +724,13 @@ async fn handle_response_item_records_tool_result_for_function_call() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -803,13 +803,13 @@ async fn handle_response_item_records_tool_result_for_local_shell_missing_ids()
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -866,13 +866,13 @@ async fn handle_response_item_records_tool_result_for_local_shell_call() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -972,13 +972,13 @@ async fn handle_container_exec_autoapprove_from_config_records_tool_decision() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -1022,13 +1022,13 @@ async fn handle_container_exec_user_approved_records_tool_decision() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "approved".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -1082,13 +1082,13 @@ async fn handle_container_exec_user_approved_for_session_records_tool_decision()
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "persist".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -1142,13 +1142,13 @@ async fn handle_sandbox_error_user_approves_retry_records_tool_decision() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "retry".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -1202,13 +1202,13 @@ async fn handle_container_exec_user_denies_records_tool_decision() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "deny".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -1262,13 +1262,13 @@ async fn handle_sandbox_error_user_approves_for_session_records_tool_decision()
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "persist".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -1323,13 +1323,13 @@ async fn handle_sandbox_error_user_denies_records_tool_decision() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "deny".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();

View File

@@ -1,5 +1,4 @@
use codex_core::protocol::EventMsg;
use codex_core::protocol::Op;
use codex_protocol::user_input::UserInput;
use core_test_support::responses;
use core_test_support::responses::ev_completed;
@@ -97,13 +96,13 @@ async fn injected_user_input_triggers_follow_up_request_with_deltas() {
.codex;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "first prompt".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -113,13 +112,13 @@ async fn injected_user_input_triggers_follow_up_request_with_deltas() {
.await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "second prompt".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();

View File

@@ -58,13 +58,13 @@ async fn permissions_message_sent_once_on_start() -> Result<()> {
let test = builder.build(&server).await?;
test.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -91,13 +91,13 @@ async fn permissions_message_added_on_override_change() -> Result<()> {
let test = builder.build(&server).await?;
test.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 1".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -115,13 +115,13 @@ async fn permissions_message_added_on_override_change() -> Result<()> {
.await?;
test.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 2".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -154,24 +154,24 @@ async fn permissions_message_not_added_when_no_change() -> Result<()> {
let test = builder.build(&server).await?;
test.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 1".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
test.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 2".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -211,13 +211,13 @@ async fn resume_replays_permissions_messages() -> Result<()> {
initial
.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 1".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -237,26 +237,26 @@ async fn resume_replays_permissions_messages() -> Result<()> {
initial
.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 2".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
let resumed = builder.resume(&server, home, rollout_path).await?;
resumed
.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "after resume".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&resumed.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -293,13 +293,13 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> {
initial
.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 1".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -319,13 +319,13 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> {
initial
.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 2".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -340,13 +340,13 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> {
let resumed = builder.resume(&server, home, rollout_path.clone()).await?;
resumed
.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "after resume".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&resumed.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -368,13 +368,13 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> {
.await?;
forked
.thread
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "after fork".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&forked.thread, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -416,13 +416,13 @@ async fn permissions_message_includes_writable_roots() -> Result<()> {
let test = builder.build(&server).await?;
test.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;

View File

@@ -85,6 +85,7 @@ async fn user_turn_personality_none_does_not_add_update_message() -> anyhow::Res
test.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -131,6 +132,7 @@ async fn config_personality_some_sets_instructions_template() -> anyhow::Result<
test.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -187,6 +189,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()>
test.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -220,6 +223,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()>
test.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -339,6 +343,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() -
test.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -372,6 +377,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() -
test.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),

View File

@@ -110,24 +110,24 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
.base_instructions;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 1".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 2".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -186,24 +186,24 @@ async fn codex_mini_latest_tools() -> anyhow::Result<()> {
.await?;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 1".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 2".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -250,24 +250,24 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests
.await?;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 1".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 2".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -328,13 +328,13 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an
// First turn
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 1".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -360,13 +360,13 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an
// Second turn after overrides
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 2".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -436,13 +436,13 @@ async fn override_before_first_turn_emits_environment_context() -> anyhow::Resul
.await?;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "first message".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -556,13 +556,13 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res
// First turn
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello 1".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
@@ -577,6 +577,7 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res
};
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello 2".into(),
text_elements: Vec::new(),
@@ -672,6 +673,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello 1".into(),
text_elements: Vec::new(),
@@ -691,6 +693,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello 2".into(),
text_elements: Vec::new(),
@@ -772,6 +775,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello 1".into(),
text_elements: Vec::new(),
@@ -791,6 +795,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello 2".into(),
text_elements: Vec::new(),

View File

@@ -1,6 +1,5 @@
use anyhow::Result;
use codex_core::protocol::EventMsg;
use codex_core::protocol::Op;
use codex_protocol::user_input::UserInput;
use core_test_support::responses::ev_response_created;
use core_test_support::responses::mount_sse_once;
@@ -40,13 +39,13 @@ async fn quota_exceeded_emits_single_error_event() -> Result<()> {
let test = builder.build(&server).await?;
test.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "quota?".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();

View File

@@ -167,6 +167,7 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "run call".into(),
text_elements: Vec::new(),
@@ -377,6 +378,7 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "hello remote".into(),
text_elements: Vec::new(),

View File

@@ -3,7 +3,6 @@
use codex_core::CodexAuth;
use codex_core::features::Feature;
use codex_core::protocol::EventMsg;
use codex_core::protocol::Op;
use codex_protocol::user_input::UserInput;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_response_created;
@@ -36,13 +35,13 @@ async fn request_body_is_zstd_compressed_for_codex_backend_when_enabled() -> any
let codex = builder.build(&server).await?.codex;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "compress me".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
// Wait until the task completes so the request definitely hit the server.
@@ -80,13 +79,13 @@ async fn request_body_is_not_compressed_for_api_key_auth_even_when_enabled() ->
let codex = builder.build(&server).await?.codex;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "do not compress".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;

View File

@@ -123,6 +123,7 @@ async fn request_user_input_round_trip_resolves_pending() -> anyhow::Result<()>
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "please confirm".into(),
text_elements: Vec::new(),
@@ -244,6 +245,7 @@ where
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "please confirm".into(),
text_elements: Vec::new(),

View File

@@ -1,6 +1,5 @@
use anyhow::Result;
use codex_core::protocol::EventMsg;
use codex_core::protocol::Op;
use codex_protocol::user_input::ByteRange;
use codex_protocol::user_input::TextElement;
use codex_protocol::user_input::UserInput;
@@ -45,13 +44,13 @@ async fn resume_includes_initial_messages_from_rollout_events() -> Result<()> {
)];
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "Record some messages".into(),
text_elements: text_elements.clone(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
@@ -104,13 +103,13 @@ async fn resume_includes_initial_messages_from_reasoning_events() -> Result<()>
mount_sse_once(&server, initial_sse).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "Record reasoning messages".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
@@ -165,13 +164,13 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> {
let initial_mock = mount_sse_once(&server, initial_sse).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "Record initial instructions".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
@@ -195,13 +194,13 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> {
let resumed = resume_builder.resume(&server, home, rollout_path).await?;
resumed
.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "Resume with different model".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&resumed.codex, |event| {
matches!(event, EventMsg::TurnComplete(_))

View File

@@ -705,13 +705,13 @@ async fn review_history_surfaces_in_parent_session() {
// 2) Continue in the parent session; request input must not include any review items.
let followup = "back to parent".to_string();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: followup.clone(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
let _complete = wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;

View File

@@ -108,6 +108,7 @@ async fn stdio_server_round_trip() -> anyhow::Result<()> {
fixture
.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "call the rmcp echo tool".into(),
text_elements: Vec::new(),
@@ -249,6 +250,7 @@ async fn stdio_image_responses_round_trip() -> anyhow::Result<()> {
fixture
.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "call the rmcp image tool".into(),
text_elements: Vec::new(),
@@ -448,6 +450,7 @@ async fn stdio_image_completions_round_trip() -> anyhow::Result<()> {
fixture
.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "call the rmcp image tool".into(),
text_elements: Vec::new(),
@@ -595,6 +598,7 @@ async fn stdio_server_propagates_whitelisted_env_vars() -> anyhow::Result<()> {
fixture
.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "call the rmcp echo tool".into(),
text_elements: Vec::new(),
@@ -753,6 +757,7 @@ async fn streamable_http_tool_call_round_trip() -> anyhow::Result<()> {
fixture
.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "call the rmcp streamable http echo tool".into(),
text_elements: Vec::new(),
@@ -943,6 +948,7 @@ async fn streamable_http_with_oauth_round_trip() -> anyhow::Result<()> {
fixture
.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "call the rmcp streamable http oauth echo tool".into(),
text_elements: Vec::new(),

View File

@@ -89,6 +89,7 @@ async fn run_snapshot_command(command: &str) -> Result<SnapshotRun> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "run unified exec with shell snapshot".into(),
text_elements: Vec::new(),
@@ -163,6 +164,7 @@ async fn run_shell_command_snapshot(command: &str) -> Result<SnapshotRun> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "run shell_command with shell snapshot".into(),
text_elements: Vec::new(),
@@ -298,6 +300,7 @@ async fn shell_command_snapshot_still_intercepts_apply_patch() -> Result<()> {
let model = test.session_configured.model.clone();
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "apply patch via shell_command with snapshot".into(),
text_elements: Vec::new(),

View File

@@ -61,6 +61,7 @@ async fn user_turn_includes_skill_instructions() -> Result<()> {
let session_model = test.session_configured.model.clone();
test.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![
UserInput::Text {
text: "please use $demo".to_string(),

View File

@@ -1,7 +1,6 @@
use codex_core::ModelProviderInfo;
use codex_core::WireApi;
use codex_core::protocol::EventMsg;
use codex_core::protocol::Op;
use codex_protocol::user_input::UserInput;
use core_test_support::load_sse_fixture_with_id;
use core_test_support::skip_if_no_network;
@@ -85,13 +84,13 @@ async fn continue_after_stream_error() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "first message".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();
@@ -104,13 +103,13 @@ async fn continue_after_stream_error() {
// mock server SSE stream. If the agent failed to clear the running task on
// error above, this submission would be rejected/queued indefinitely.
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "follow up".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();

View File

@@ -4,7 +4,6 @@
use codex_core::ModelProviderInfo;
use codex_core::WireApi;
use codex_core::protocol::EventMsg;
use codex_core::protocol::Op;
use codex_protocol::user_input::UserInput;
use core_test_support::load_sse_fixture;
use core_test_support::load_sse_fixture_with_id;
@@ -92,13 +91,13 @@ async fn retries_on_early_close() {
.unwrap();
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await
.unwrap();

View File

@@ -79,6 +79,7 @@ async fn shell_tool_executes_command_and_streams_output() -> anyhow::Result<()>
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "please run the shell command".into(),
text_elements: Vec::new(),
@@ -148,6 +149,7 @@ async fn update_plan_tool_emits_plan_update_event() -> anyhow::Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "please update the plan".into(),
text_elements: Vec::new(),
@@ -227,6 +229,7 @@ async fn update_plan_tool_rejects_malformed_payload() -> anyhow::Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "please update the plan".into(),
text_elements: Vec::new(),
@@ -318,6 +321,7 @@ async fn apply_patch_tool_executes_and_emits_patch_events() -> anyhow::Result<()
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "please apply a patch".into(),
text_elements: Vec::new(),
@@ -417,6 +421,7 @@ async fn apply_patch_reports_parse_diagnostics() -> anyhow::Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "please apply a patch".into(),
text_elements: Vec::new(),

View File

@@ -36,6 +36,7 @@ async fn run_turn(test: &TestCodex, prompt: &str) -> anyhow::Result<()> {
test.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: prompt.into(),
text_elements: Vec::new(),
@@ -354,6 +355,7 @@ async fn shell_tools_start_before_response_completed_when_stream_delayed() -> an
let session_model = test.session_configured.model.clone();
test.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "stream delayed completion".into(),
text_elements: Vec::new(),

View File

@@ -538,6 +538,7 @@ async fn mcp_image_output_preserves_image_and_no_text_summary() -> Result<()> {
fixture
.codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "call the rmcp image tool".into(),
text_elements: Vec::new(),

View File

@@ -198,6 +198,7 @@ async fn unified_exec_intercepts_apply_patch_exec_command() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "apply patch via unified exec".into(),
text_elements: Vec::new(),
@@ -327,6 +328,7 @@ async fn unified_exec_emits_exec_command_begin_event() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "emit begin event".into(),
text_elements: Vec::new(),
@@ -405,6 +407,7 @@ async fn unified_exec_resolves_relative_workdir() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "run relative workdir test".into(),
text_elements: Vec::new(),
@@ -486,6 +489,7 @@ async fn unified_exec_respects_workdir_override() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "run workdir test".into(),
text_elements: Vec::new(),
@@ -579,6 +583,7 @@ async fn unified_exec_emits_exec_command_end_event() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "emit end event".into(),
text_elements: Vec::new(),
@@ -654,6 +659,7 @@ async fn unified_exec_emits_output_delta_for_exec_command() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "emit delta".into(),
text_elements: Vec::new(),
@@ -730,6 +736,7 @@ async fn unified_exec_full_lifecycle_with_background_end_event() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "exercise full unified exec lifecycle".into(),
text_elements: Vec::new(),
@@ -860,6 +867,7 @@ async fn unified_exec_emits_terminal_interaction_for_write_stdin() -> Result<()>
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "stdin delta".into(),
text_elements: Vec::new(),
@@ -997,6 +1005,7 @@ async fn unified_exec_terminal_interaction_captures_delayed_output() -> Result<(
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "delayed terminal interaction output".into(),
text_elements: Vec::new(),
@@ -1157,6 +1166,7 @@ async fn unified_exec_emits_one_begin_and_one_end_event() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "check poll event behavior".into(),
text_elements: Vec::new(),
@@ -1255,6 +1265,7 @@ async fn exec_command_reports_chunk_and_exit_metadata() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "run metadata test".into(),
text_elements: Vec::new(),
@@ -1373,6 +1384,7 @@ async fn unified_exec_defaults_to_pipe() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "check default pipe mode".into(),
text_elements: Vec::new(),
@@ -1463,6 +1475,7 @@ async fn unified_exec_can_enable_tty() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "check tty enabled".into(),
text_elements: Vec::new(),
@@ -1544,6 +1557,7 @@ async fn unified_exec_respects_early_exit_notifications() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "watch early exit timing".into(),
text_elements: Vec::new(),
@@ -1675,6 +1689,7 @@ async fn write_stdin_returns_exit_metadata_and_clears_session() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "test write_stdin exit behavior".into(),
text_elements: Vec::new(),
@@ -1843,6 +1858,7 @@ async fn unified_exec_emits_end_event_when_session_dies_via_stdin() -> Result<()
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "end on exit".into(),
text_elements: Vec::new(),
@@ -1920,6 +1936,7 @@ async fn unified_exec_closes_long_running_session_at_turn_end() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "close unified exec processes on turn end".into(),
text_elements: Vec::new(),
@@ -2042,6 +2059,7 @@ async fn unified_exec_reuses_session_via_stdin() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "run unified exec".into(),
text_elements: Vec::new(),
@@ -2177,6 +2195,7 @@ PY
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "exercise lag handling".into(),
text_elements: Vec::new(),
@@ -2291,6 +2310,7 @@ async fn unified_exec_timeout_and_followup_poll() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "check timeout".into(),
text_elements: Vec::new(),
@@ -2387,6 +2407,7 @@ PY
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "summarize large output".into(),
text_elements: Vec::new(),
@@ -2468,6 +2489,7 @@ async fn unified_exec_runs_under_sandbox() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "summarize large output".into(),
text_elements: Vec::new(),
@@ -2573,6 +2595,7 @@ async fn unified_exec_python_prompt_under_seatbelt() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "start python under seatbelt".into(),
text_elements: Vec::new(),
@@ -2668,6 +2691,7 @@ async fn unified_exec_runs_on_all_platforms() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "summarize large output".into(),
text_elements: Vec::new(),
@@ -2803,6 +2827,7 @@ async fn unified_exec_prunes_exited_sessions_first() -> Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "fill session cache".into(),
text_elements: Vec::new(),

View File

@@ -3,7 +3,6 @@
use std::os::unix::fs::PermissionsExt;
use codex_core::protocol::EventMsg;
use codex_core::protocol::Op;
use codex_protocol::user_input::UserInput;
use core_test_support::fs_wait;
use core_test_support::responses;
@@ -57,13 +56,13 @@ echo -n "${@: -1}" > $(dirname "${0}")/notify.txt"#,
// 1) Normal user input should hit server once.
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
.submit_user_turn_with_defaults(
vec![UserInput::Text {
text: "hello world".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
None,
)
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;

View File

@@ -78,6 +78,7 @@ async fn user_turn_with_local_image_attaches_image() -> anyhow::Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::LocalImage {
path: abs_path.clone(),
}],
@@ -171,6 +172,7 @@ async fn view_image_tool_attaches_local_image() -> anyhow::Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "please add the screenshot".into(),
text_elements: Vec::new(),
@@ -304,6 +306,7 @@ async fn view_image_tool_errors_when_path_is_directory() -> anyhow::Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "please attach the folder".into(),
text_elements: Vec::new(),
@@ -379,6 +382,7 @@ async fn view_image_tool_placeholder_for_non_image_files() -> anyhow::Result<()>
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "please use the view_image tool to read the json file".into(),
text_elements: Vec::new(),
@@ -473,6 +477,7 @@ async fn view_image_tool_errors_when_file_missing() -> anyhow::Result<()> {
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::Text {
text: "please attach the missing image".into(),
text_elements: Vec::new(),
@@ -557,6 +562,7 @@ async fn replaces_invalid_local_image_after_bad_request() -> anyhow::Result<()>
codex
.submit(Op::UserTurn {
use_thread_defaults: false,
items: vec![UserInput::LocalImage {
path: abs_path.clone(),
}],

View File

@@ -23,7 +23,7 @@ These are entities exit on the codex backend. The intent of this section is to e
3. `Task`
- A `Task` is `Codex` executing work in response to user input.
- `Session` has at most one `Task` running at a time.
- Receiving `Op::UserTurn` starts a `Task` (`Op::UserInput` is legacy)
- Receiving `Op::UserTurn` starts a `Task`
- Consists of a series of `Turn`s
- The `Task` executes to until:
- The `Model` completes the task and there is no output to feed into an additional `Turn`
@@ -66,7 +66,6 @@ For complete documentation of the `Op` and `EventMsg` variants, refer to [protoc
- `Op`
- `Op::UserTurn` Any input from the user to kick off a `Turn`
- `Op::UserInput` Legacy form of user input
- `Op::Interrupt` Interrupts a running turn
- `Op::ExecApproval` Approve or deny code execution
- `Op::UserInputAnswer` Provide answers for a `request_user_input` tool call

View File

@@ -466,6 +466,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
} => {
let task_id = thread
.submit(Op::UserTurn {
use_thread_defaults: false,
items,
cwd: default_cwd,
approval_policy: default_approval_policy,

View File

@@ -18,7 +18,6 @@ use codex_core::protocol::ApplyPatchApprovalRequestEvent;
use codex_core::protocol::Event;
use codex_core::protocol::EventMsg;
use codex_core::protocol::ExecApprovalRequestEvent;
use codex_core::protocol::Op;
use codex_core::protocol::Submission;
use codex_core::protocol::TurnCompleteEvent;
use codex_protocol::ThreadId;
@@ -117,16 +116,19 @@ pub async fn run_codex_tool_session(
.lock()
.await
.insert(id.clone(), thread_id);
let submission = Submission {
id: sub_id.clone(),
op: Op::UserInput {
items: vec![UserInput::Text {
let op = thread
.user_turn_with_defaults(
vec![UserInput::Text {
text: initial_prompt.clone(),
// MCP tool prompts are plain text with no UI element ranges.
text_elements: Vec::new(),
}],
final_output_json_schema: None,
},
None,
)
.await;
let submission = Submission {
id: sub_id.clone(),
op,
};
if let Err(e) = thread.submit_with_id(submission).await {
@@ -164,17 +166,17 @@ pub async fn run_codex_tool_session_reply(
.lock()
.await
.insert(request_id.clone(), thread_id);
if let Err(e) = thread
.submit(Op::UserInput {
items: vec![UserInput::Text {
let op = thread
.user_turn_with_defaults(
vec![UserInput::Text {
text: prompt,
// MCP tool prompts are plain text with no UI element ranges.
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
.await
{
None,
)
.await;
if let Err(e) = thread.submit(op).await {
tracing::error!("Failed to submit user input: {e}");
let result = create_call_tool_result_with_thread_id(
thread_id,

View File

@@ -61,6 +61,10 @@ pub const COLLABORATION_MODE_OPEN_TAG: &str = "<collaboration_mode>";
pub const COLLABORATION_MODE_CLOSE_TAG: &str = "</collaboration_mode>";
pub const USER_MESSAGE_BEGIN: &str = "## My request for Codex:";
const fn is_false(value: &bool) -> bool {
!*value
}
/// Submission Queue Entry - requests from user
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
pub struct Submission {
@@ -87,21 +91,13 @@ pub enum Op {
/// This server sends [`EventMsg::TurnAborted`] in response.
Interrupt,
/// Legacy user input.
///
/// Prefer [`Op::UserTurn`] so the caller provides full turn context
/// (cwd/approval/sandbox/model/etc.) for each turn.
UserInput {
/// User input items, see `InputItem`
items: Vec<UserInput>,
/// Optional JSON Schema used to constrain the final assistant message for this turn.
#[serde(skip_serializing_if = "Option::is_none")]
final_output_json_schema: Option<Value>,
},
/// Similar to [`Op::UserInput`], but contains additional context required
/// for a turn of a [`crate::codex_thread::CodexThread`].
/// User input with full turn context.
UserTurn {
/// If true, defaults are computed at handling time from the session.
/// When set, collaboration mode is only updated if explicitly provided.
#[serde(default, skip_serializing_if = "is_false")]
use_thread_defaults: bool,
/// User input items, see `InputItem`
items: Vec<UserInput>,
@@ -143,7 +139,7 @@ pub enum Op {
/// All fields are optional; when omitted, the existing value is preserved.
/// This does not enqueue any input it only updates defaults used for
/// turns that rely on persistent session-level context (for example,
/// [`Op::UserInput`]).
/// [`Op::UserTurn`]).
OverrideTurnContext {
/// Updated `cwd` for sandbox/tool calls.
#[serde(skip_serializing_if = "Option::is_none")]
@@ -2398,35 +2394,7 @@ mod tests {
}
#[test]
fn user_input_serialization_omits_final_output_json_schema_when_none() -> Result<()> {
let op = Op::UserInput {
items: Vec::new(),
final_output_json_schema: None,
};
let json_op = serde_json::to_value(op)?;
assert_eq!(json_op, json!({ "type": "user_input", "items": [] }));
Ok(())
}
#[test]
fn user_input_deserializes_without_final_output_json_schema_field() -> Result<()> {
let op: Op = serde_json::from_value(json!({ "type": "user_input", "items": [] }))?;
assert_eq!(
op,
Op::UserInput {
items: Vec::new(),
final_output_json_schema: None,
}
);
Ok(())
}
#[test]
fn user_input_serialization_includes_final_output_json_schema_when_some() -> Result<()> {
fn user_turn_serialization_includes_final_output_json_schema_when_some() -> Result<()> {
let schema = json!({
"type": "object",
"properties": {
@@ -2435,17 +2403,32 @@ mod tests {
"required": ["answer"],
"additionalProperties": false
});
let op = Op::UserInput {
let cwd = PathBuf::from("/tmp");
let op = Op::UserTurn {
use_thread_defaults: false,
items: Vec::new(),
cwd: cwd.clone(),
approval_policy: AskForApproval::Never,
sandbox_policy: SandboxPolicy::ReadOnly,
model: "test-model".to_string(),
effort: None,
summary: ReasoningSummaryConfig::Auto,
final_output_json_schema: Some(schema.clone()),
collaboration_mode: None,
personality: None,
};
let json_op = serde_json::to_value(op)?;
assert_eq!(
json_op,
json!({
"type": "user_input",
"type": "user_turn",
"items": [],
"cwd": cwd,
"approval_policy": "never",
"sandbox_policy": { "type": "read-only" },
"model": "test-model",
"summary": "auto",
"final_output_json_schema": schema,
})
);

View File

@@ -2843,6 +2843,7 @@ impl ChatWidget {
.model_personality
.filter(|_| self.current_model_supports_personality());
let op = Op::UserTurn {
use_thread_defaults: false,
items,
cwd: self.config.cwd.clone(),
approval_policy: self.config.approval_policy.value(),

View File

@@ -1220,6 +1220,7 @@ async fn submit_user_message_with_mode_sets_coding_collaboration_mode() {
match next_submit_op(&mut op_rx) {
Op::UserTurn {
use_thread_defaults: false,
collaboration_mode:
Some(CollaborationMode {
mode: ModeKind::Code,
@@ -2243,6 +2244,7 @@ async fn collab_slash_command_opens_picker_and_updates_mode() {
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
match next_submit_op(&mut op_rx) {
Op::UserTurn {
use_thread_defaults: false,
collaboration_mode:
Some(CollaborationMode {
mode: ModeKind::Code,
@@ -2261,6 +2263,7 @@ async fn collab_slash_command_opens_picker_and_updates_mode() {
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
match next_submit_op(&mut op_rx) {
Op::UserTurn {
use_thread_defaults: false,
collaboration_mode:
Some(CollaborationMode {
mode: ModeKind::Code,
@@ -2398,6 +2401,7 @@ async fn collab_mode_is_not_sent_until_selected() {
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
match next_submit_op(&mut op_rx) {
Op::UserTurn {
use_thread_defaults: false,
collaboration_mode,
personality: None,
..
@@ -2430,6 +2434,7 @@ async fn user_turn_includes_personality_from_config() {
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
match next_submit_op(&mut op_rx) {
Op::UserTurn {
use_thread_defaults: false,
personality: Some(Personality::Friendly),
..
} => {}