mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Add turn-scoped environment selections (#18416)
## Summary - add experimental turn/start.environments params for per-turn environment id + cwd selections - pass selections through core protocol ops and resolve them with EnvironmentManager before TurnContext creation - treat omitted selections as default behavior, empty selections as no environment, and non-empty selections as first environment/cwd as the turn primary ## Testing - ran `just fmt` - ran `just write-app-server-schema` - not run: unit tests for this stacked PR --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -3706,6 +3706,21 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TurnEnvironmentParams": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"environmentId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cwd",
|
||||
"environmentId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TurnInterruptParams": {
|
||||
"properties": {
|
||||
"threadId": {
|
||||
|
||||
+15
@@ -16260,6 +16260,21 @@
|
||||
"title": "TurnDiffUpdatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"TurnEnvironmentParams": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
"environmentId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cwd",
|
||||
"environmentId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TurnError": {
|
||||
"properties": {
|
||||
"additionalDetails": {
|
||||
|
||||
+15
@@ -14154,6 +14154,21 @@
|
||||
"title": "TurnDiffUpdatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"TurnEnvironmentParams": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"environmentId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cwd",
|
||||
"environmentId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TurnError": {
|
||||
"properties": {
|
||||
"additionalDetails": {
|
||||
|
||||
@@ -377,6 +377,21 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TurnEnvironmentParams": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"environmentId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cwd",
|
||||
"environmentId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"UserInput": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
|
||||
|
||||
export type TurnEnvironmentParams = { environmentId: string, cwd: AbsolutePathBuf, };
|
||||
@@ -372,6 +372,7 @@ export type { ToolsV2 } from "./ToolsV2";
|
||||
export type { Turn } from "./Turn";
|
||||
export type { TurnCompletedNotification } from "./TurnCompletedNotification";
|
||||
export type { TurnDiffUpdatedNotification } from "./TurnDiffUpdatedNotification";
|
||||
export type { TurnEnvironmentParams } from "./TurnEnvironmentParams";
|
||||
export type { TurnError } from "./TurnError";
|
||||
export type { TurnInterruptParams } from "./TurnInterruptParams";
|
||||
export type { TurnInterruptResponse } from "./TurnInterruptResponse";
|
||||
|
||||
@@ -98,6 +98,13 @@ mod tests {
|
||||
inners: HashMap<String, EnumVariantShapes>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(ExperimentalApi)]
|
||||
struct ExperimentalFieldShape {
|
||||
#[experimental("field/optionalCollection")]
|
||||
optional_collection: Option<Vec<EnumVariantShapes>>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_supports_all_enum_variant_shapes() {
|
||||
assert_eq!(
|
||||
@@ -169,4 +176,20 @@ mod tests {
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_marks_optional_experimental_fields_when_some() {
|
||||
assert_eq!(
|
||||
ExperimentalApiTrait::experimental_reason(&ExperimentalFieldShape {
|
||||
optional_collection: Some(Vec::new()),
|
||||
}),
|
||||
Some("field/optionalCollection")
|
||||
);
|
||||
assert_eq!(
|
||||
ExperimentalApiTrait::experimental_reason(&ExperimentalFieldShape {
|
||||
optional_collection: None,
|
||||
}),
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4641,6 +4641,14 @@ pub enum TurnStatus {
|
||||
}
|
||||
|
||||
// Turn APIs
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TurnEnvironmentParams {
|
||||
pub environment_id: String,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS, ExperimentalApi,
|
||||
)]
|
||||
@@ -4653,6 +4661,10 @@ pub struct TurnStartParams {
|
||||
#[experimental("turn/start.responsesapiClientMetadata")]
|
||||
#[ts(optional = nullable)]
|
||||
pub responsesapi_client_metadata: Option<HashMap<String, String>>,
|
||||
/// Optional turn-scoped environment selections.
|
||||
#[experimental("turn/start.environments")]
|
||||
#[ts(optional = nullable)]
|
||||
pub environments: Option<Vec<TurnEnvironmentParams>>,
|
||||
/// Override the working directory for this turn and subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub cwd: Option<PathBuf>,
|
||||
@@ -9759,6 +9771,7 @@ mod tests {
|
||||
thread_id: "thread_123".to_string(),
|
||||
input: vec![],
|
||||
responsesapi_client_metadata: None,
|
||||
environments: None,
|
||||
cwd: None,
|
||||
approval_policy: None,
|
||||
approvals_reviewer: None,
|
||||
@@ -9775,4 +9788,109 @@ mod tests {
|
||||
serde_json::to_value(&without_override).expect("params should serialize");
|
||||
assert_eq!(serialized_without_override.get("serviceTier"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_start_params_round_trip_environments() {
|
||||
let cwd = test_absolute_path();
|
||||
let params: TurnStartParams = serde_json::from_value(json!({
|
||||
"threadId": "thread_123",
|
||||
"input": [],
|
||||
"environments": [
|
||||
{
|
||||
"environmentId": "local",
|
||||
"cwd": cwd
|
||||
}
|
||||
],
|
||||
}))
|
||||
.expect("params should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
params.environments,
|
||||
Some(vec![TurnEnvironmentParams {
|
||||
environment_id: "local".to_string(),
|
||||
cwd: cwd.clone(),
|
||||
}])
|
||||
);
|
||||
assert_eq!(
|
||||
crate::experimental_api::ExperimentalApi::experimental_reason(¶ms),
|
||||
Some("turn/start.environments")
|
||||
);
|
||||
|
||||
let serialized = serde_json::to_value(¶ms).expect("params should serialize");
|
||||
assert_eq!(
|
||||
serialized.get("environments"),
|
||||
Some(&json!([
|
||||
{
|
||||
"environmentId": "local",
|
||||
"cwd": cwd
|
||||
}
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_start_params_preserve_empty_environments() {
|
||||
let params: TurnStartParams = serde_json::from_value(json!({
|
||||
"threadId": "thread_123",
|
||||
"input": [],
|
||||
"environments": [],
|
||||
}))
|
||||
.expect("params should deserialize");
|
||||
|
||||
assert_eq!(params.environments, Some(Vec::new()));
|
||||
assert_eq!(
|
||||
crate::experimental_api::ExperimentalApi::experimental_reason(¶ms),
|
||||
Some("turn/start.environments")
|
||||
);
|
||||
|
||||
let serialized = serde_json::to_value(¶ms).expect("params should serialize");
|
||||
assert_eq!(serialized.get("environments"), Some(&json!([])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_start_params_treat_null_or_omitted_environments_as_default() {
|
||||
let null_environments: TurnStartParams = serde_json::from_value(json!({
|
||||
"threadId": "thread_123",
|
||||
"input": [],
|
||||
"environments": null,
|
||||
}))
|
||||
.expect("params should deserialize");
|
||||
let omitted_environments: TurnStartParams = serde_json::from_value(json!({
|
||||
"threadId": "thread_123",
|
||||
"input": [],
|
||||
}))
|
||||
.expect("params should deserialize");
|
||||
|
||||
assert_eq!(null_environments.environments, None);
|
||||
assert_eq!(omitted_environments.environments, None);
|
||||
assert_eq!(
|
||||
crate::experimental_api::ExperimentalApi::experimental_reason(&null_environments),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
crate::experimental_api::ExperimentalApi::experimental_reason(&omitted_environments),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_start_params_reject_relative_environment_cwd() {
|
||||
let err = serde_json::from_value::<TurnStartParams>(json!({
|
||||
"threadId": "thread_123",
|
||||
"input": [],
|
||||
"environments": [
|
||||
{
|
||||
"environmentId": "local",
|
||||
"cwd": "relative"
|
||||
}
|
||||
],
|
||||
}))
|
||||
.expect_err("relative environment cwd should fail");
|
||||
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("AbsolutePathBuf deserialized without a base path"),
|
||||
"unexpected error: {err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,6 +533,10 @@ You can optionally specify config overrides on the new turn. If specified, these
|
||||
"input": [ { "type": "text", "text": "Run tests" } ],
|
||||
// Below are optional config overrides
|
||||
"cwd": "/Users/me/project",
|
||||
// Experimental: turn-scoped environment selection.
|
||||
"environments": [
|
||||
{ "environmentId": "local", "cwd": "/Users/me/project" }
|
||||
],
|
||||
"approvalPolicy": "unlessTrusted",
|
||||
"sandboxPolicy": {
|
||||
"type": "workspaceWrite",
|
||||
|
||||
@@ -318,6 +318,7 @@ use codex_protocol::protocol::ReviewTarget as CoreReviewTarget;
|
||||
use codex_protocol::protocol::RolloutItem;
|
||||
use codex_protocol::protocol::SessionConfiguredEvent;
|
||||
use codex_protocol::protocol::SessionMetaLine;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
use codex_protocol::protocol::USER_MESSAGE_BEGIN;
|
||||
use codex_protocol::protocol::W3cTraceContext;
|
||||
use codex_protocol::user_input::MAX_USER_INPUT_TEXT_CHARS;
|
||||
@@ -7162,6 +7163,15 @@ impl CodexMessageProcessor {
|
||||
let collaboration_mode = params.collaboration_mode.map(|mode| {
|
||||
self.normalize_turn_start_collaboration_mode(mode, collaboration_modes_config)
|
||||
});
|
||||
let environments = params.environments.map(|environments| {
|
||||
environments
|
||||
.into_iter()
|
||||
.map(|environment| TurnEnvironmentSelection {
|
||||
environment_id: environment.environment_id,
|
||||
cwd: environment.cwd,
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
// Map v2 input items to core input items.
|
||||
let mapped_items: Vec<CoreInputItem> = params
|
||||
@@ -7213,6 +7223,7 @@ impl CodexMessageProcessor {
|
||||
thread.as_ref(),
|
||||
Op::UserInput {
|
||||
items: mapped_items,
|
||||
environments,
|
||||
final_output_json_schema: params.output_schema,
|
||||
responsesapi_client_metadata: params.responsesapi_client_metadata,
|
||||
},
|
||||
|
||||
@@ -707,6 +707,7 @@ async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> {
|
||||
ClientRequest::TurnStart {
|
||||
request_id: RequestId::Integer(3),
|
||||
params: TurnStartParams {
|
||||
environments: None,
|
||||
thread_id,
|
||||
input: vec![UserInput::Text {
|
||||
text: "hello".to_string(),
|
||||
|
||||
@@ -1733,6 +1733,7 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> {
|
||||
// first turn with workspace-write sandbox and first_cwd
|
||||
let first_turn = mcp
|
||||
.send_turn_start_request(TurnStartParams {
|
||||
environments: None,
|
||||
thread_id: thread.id.clone(),
|
||||
input: vec![V2UserInput::Text {
|
||||
text: "first turn".to_string(),
|
||||
@@ -1773,6 +1774,7 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> {
|
||||
// second turn with workspace-write and second_cwd, ensure exec begins in second_cwd
|
||||
let second_turn = mcp
|
||||
.send_turn_start_request(TurnStartParams {
|
||||
environments: None,
|
||||
thread_id: thread.id.clone(),
|
||||
input: vec![V2UserInput::Text {
|
||||
text: "second turn".to_string(),
|
||||
|
||||
@@ -261,11 +261,8 @@ fn presence_expr_for_access(
|
||||
access: proc_macro2::TokenStream,
|
||||
ty: &Type,
|
||||
) -> proc_macro2::TokenStream {
|
||||
if let Some(inner) = option_inner(ty) {
|
||||
let inner_expr = presence_expr_for_ref(quote!(value), inner);
|
||||
return quote! {
|
||||
#access.as_ref().is_some_and(|value| #inner_expr)
|
||||
};
|
||||
if option_inner(ty).is_some() {
|
||||
return quote! { #access.is_some() };
|
||||
}
|
||||
if is_vec_like(ty) || is_map_like(ty) {
|
||||
return quote! { !#access.is_empty() };
|
||||
@@ -276,22 +273,6 @@ fn presence_expr_for_access(
|
||||
quote! { true }
|
||||
}
|
||||
|
||||
fn presence_expr_for_ref(access: proc_macro2::TokenStream, ty: &Type) -> proc_macro2::TokenStream {
|
||||
if let Some(inner) = option_inner(ty) {
|
||||
let inner_expr = presence_expr_for_ref(quote!(value), inner);
|
||||
return quote! {
|
||||
#access.as_ref().is_some_and(|value| #inner_expr)
|
||||
};
|
||||
}
|
||||
if is_vec_like(ty) || is_map_like(ty) {
|
||||
return quote! { !#access.is_empty() };
|
||||
}
|
||||
if is_bool(ty) {
|
||||
return quote! { *#access };
|
||||
}
|
||||
quote! { true }
|
||||
}
|
||||
|
||||
fn option_inner(ty: &Type) -> Option<&Type> {
|
||||
let Type::Path(type_path) = ty else {
|
||||
return None;
|
||||
|
||||
@@ -428,6 +428,7 @@ async fn send_input_submits_user_message() {
|
||||
let expected = (
|
||||
thread_id,
|
||||
Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello from tests".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -575,6 +576,7 @@ async fn spawn_agent_creates_thread_and_sends_prompt() {
|
||||
let expected = (
|
||||
thread_id,
|
||||
Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "spawned".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -688,6 +690,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
|
||||
let expected = (
|
||||
child_thread_id,
|
||||
Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "child task".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -182,6 +182,7 @@ pub(crate) async fn run_codex_thread_one_shot(
|
||||
|
||||
// Send the initial input to kick off the one-shot turn.
|
||||
io.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: input,
|
||||
final_output_json_schema,
|
||||
responsesapi_client_metadata: None,
|
||||
|
||||
@@ -585,6 +585,7 @@ async fn run_review_on_session(
|
||||
review_session
|
||||
.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: prompt_items.items,
|
||||
cwd: params.parent_turn.cwd.to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
|
||||
@@ -125,7 +125,7 @@ pub(super) async fn user_input_or_turn_inner(
|
||||
op: Op,
|
||||
mirror_user_text_to_realtime: Option<()>,
|
||||
) {
|
||||
let (items, updates, responsesapi_client_metadata) = match op {
|
||||
let (items, updates, responsesapi_client_metadata, environments) = match op {
|
||||
Op::UserTurn {
|
||||
cwd,
|
||||
approval_policy,
|
||||
@@ -139,6 +139,7 @@ pub(super) async fn user_input_or_turn_inner(
|
||||
items,
|
||||
collaboration_mode,
|
||||
personality,
|
||||
environments,
|
||||
} => {
|
||||
let collaboration_mode = collaboration_mode.or_else(|| {
|
||||
Some(CollaborationMode {
|
||||
@@ -167,10 +168,12 @@ pub(super) async fn user_input_or_turn_inner(
|
||||
app_server_client_version: None,
|
||||
},
|
||||
None,
|
||||
environments,
|
||||
)
|
||||
}
|
||||
Op::UserInput {
|
||||
items,
|
||||
environments,
|
||||
final_output_json_schema,
|
||||
responsesapi_client_metadata,
|
||||
} => (
|
||||
@@ -180,11 +183,15 @@ pub(super) async fn user_input_or_turn_inner(
|
||||
..Default::default()
|
||||
},
|
||||
responsesapi_client_metadata,
|
||||
environments,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let Ok(current_context) = sess.new_turn_with_sub_id(sub_id.clone(), updates).await else {
|
||||
let Ok(current_context) = sess
|
||||
.new_turn_with_sub_id(sub_id.clone(), updates, environments)
|
||||
.await
|
||||
else {
|
||||
// new_turn_with_sub_id already emits the error event.
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -1027,6 +1027,7 @@ impl Session {
|
||||
self,
|
||||
self.next_internal_sub_id(),
|
||||
Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text,
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -110,6 +110,7 @@ pub(super) async fn spawn_review_thread(
|
||||
reasoning_summary,
|
||||
session_source,
|
||||
environment: parent_turn_context.environment.clone(),
|
||||
environments: parent_turn_context.environments.clone(),
|
||||
tools_config,
|
||||
features: parent_turn_context.features.clone(),
|
||||
ghost_snapshot: parent_turn_context.ghost_snapshot.clone(),
|
||||
|
||||
@@ -779,6 +779,7 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow
|
||||
sandbox_policy: Some(SandboxPolicy::DangerFullAccess),
|
||||
..Default::default()
|
||||
},
|
||||
/*environment_selections*/ None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1495,6 +1496,7 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result<
|
||||
initial
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "fork seed".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1555,6 +1557,7 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result<
|
||||
forked
|
||||
.thread
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "after fork".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -3021,7 +3024,8 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
inherited_shell_snapshot: None,
|
||||
user_shell_override: None,
|
||||
};
|
||||
let per_turn_config = Session::build_per_turn_config(&session_configuration);
|
||||
let per_turn_config =
|
||||
Session::build_per_turn_config(&session_configuration, session_configuration.cwd.clone());
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(
|
||||
session_configuration.collaboration_mode.model(),
|
||||
&per_turn_config.to_models_manager_config(),
|
||||
@@ -3137,6 +3141,8 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
&models_manager,
|
||||
/*network*/ None,
|
||||
Some(environment),
|
||||
/*environments*/ None,
|
||||
session_configuration.cwd.clone(),
|
||||
"turn_id".to_string(),
|
||||
Arc::clone(&js_repl),
|
||||
skills_outcome,
|
||||
@@ -3707,6 +3713,7 @@ fn op_kind_distinguishes_turn_ops() {
|
||||
);
|
||||
assert_eq!(
|
||||
Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![],
|
||||
final_output_json_schema: None,
|
||||
responsesapi_client_metadata: None,
|
||||
@@ -3725,6 +3732,7 @@ async fn user_turn_updates_approvals_reviewer() {
|
||||
&session,
|
||||
"sub-1".to_string(),
|
||||
Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -3751,6 +3759,133 @@ async fn user_turn_updates_approvals_reviewer() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn turn_environment_selection_sets_primary_environment() {
|
||||
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||
let selected_cwd =
|
||||
AbsolutePathBuf::try_from(session.get_config().await.cwd.as_path().join("selected"))
|
||||
.expect("absolute path");
|
||||
|
||||
let turn_context = session
|
||||
.new_turn_with_sub_id(
|
||||
"sub-1".to_string(),
|
||||
SessionSettingsUpdate::default(),
|
||||
Some(vec![codex_protocol::protocol::TurnEnvironmentSelection {
|
||||
environment_id: "local".to_string(),
|
||||
cwd: selected_cwd.clone(),
|
||||
}]),
|
||||
)
|
||||
.await
|
||||
.expect("turn should start");
|
||||
|
||||
let turn_environments = turn_context
|
||||
.environments
|
||||
.as_ref()
|
||||
.expect("turn environments should be recorded");
|
||||
assert_eq!(turn_environments.len(), 1);
|
||||
assert_eq!(turn_environments[0].environment_id, "local");
|
||||
assert!(std::sync::Arc::ptr_eq(
|
||||
turn_context
|
||||
.environment
|
||||
.as_ref()
|
||||
.expect("primary environment should be set"),
|
||||
&turn_environments[0].environment
|
||||
));
|
||||
assert_eq!(turn_context.cwd.as_path(), selected_cwd.as_path());
|
||||
assert_eq!(turn_context.config.cwd.as_path(), selected_cwd.as_path());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn multiple_turn_environment_selections_use_first_as_primary_environment() {
|
||||
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||
let session_cwd = session.get_config().await.cwd.clone();
|
||||
let first_cwd =
|
||||
AbsolutePathBuf::try_from(session_cwd.as_path().join("first")).expect("absolute path");
|
||||
let second_cwd =
|
||||
AbsolutePathBuf::try_from(session_cwd.as_path().join("second")).expect("absolute path");
|
||||
|
||||
let turn_context = session
|
||||
.new_turn_with_sub_id(
|
||||
"sub-1".to_string(),
|
||||
SessionSettingsUpdate::default(),
|
||||
Some(vec![
|
||||
codex_protocol::protocol::TurnEnvironmentSelection {
|
||||
environment_id: "local".to_string(),
|
||||
cwd: first_cwd.clone(),
|
||||
},
|
||||
codex_protocol::protocol::TurnEnvironmentSelection {
|
||||
environment_id: "local".to_string(),
|
||||
cwd: second_cwd.clone(),
|
||||
},
|
||||
]),
|
||||
)
|
||||
.await
|
||||
.expect("turn should start");
|
||||
|
||||
let turn_environments = turn_context
|
||||
.environments
|
||||
.as_ref()
|
||||
.expect("turn environments should be recorded");
|
||||
assert_eq!(turn_environments.len(), 2);
|
||||
assert_eq!(turn_environments[0].cwd, first_cwd);
|
||||
assert_eq!(turn_environments[1].cwd, second_cwd);
|
||||
assert!(std::sync::Arc::ptr_eq(
|
||||
turn_context
|
||||
.environment
|
||||
.as_ref()
|
||||
.expect("primary environment should be set"),
|
||||
&turn_environments[0].environment
|
||||
));
|
||||
assert_eq!(turn_context.cwd, first_cwd);
|
||||
assert_eq!(turn_context.config.cwd, first_cwd);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn empty_turn_environment_selection_clears_primary_environment() {
|
||||
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||
|
||||
let turn_context = session
|
||||
.new_turn_with_sub_id(
|
||||
"sub-1".to_string(),
|
||||
SessionSettingsUpdate::default(),
|
||||
Some(vec![]),
|
||||
)
|
||||
.await
|
||||
.expect("turn should start");
|
||||
|
||||
assert!(turn_context.environment.is_none());
|
||||
assert_eq!(turn_context.cwd, session.get_config().await.cwd);
|
||||
assert_eq!(turn_context.config.cwd, session.get_config().await.cwd);
|
||||
assert_eq!(
|
||||
turn_context
|
||||
.environments
|
||||
.as_ref()
|
||||
.expect("turn environments should be recorded")
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unknown_turn_environment_selection_returns_error() {
|
||||
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||
|
||||
let err = session
|
||||
.new_turn_with_sub_id(
|
||||
"sub-1".to_string(),
|
||||
SessionSettingsUpdate::default(),
|
||||
Some(vec![codex_protocol::protocol::TurnEnvironmentSelection {
|
||||
environment_id: "missing".to_string(),
|
||||
cwd: session.get_config().await.cwd.clone(),
|
||||
}]),
|
||||
)
|
||||
.await
|
||||
.expect_err("unknown environment should fail");
|
||||
|
||||
assert!(matches!(err, CodexErr::InvalidRequest(_)));
|
||||
assert!(err.to_string().contains("missing"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn spawn_task_turn_span_inherits_dispatch_trace_context() {
|
||||
struct TraceCaptureTask {
|
||||
@@ -4107,7 +4242,8 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
|
||||
inherited_shell_snapshot: None,
|
||||
user_shell_override: None,
|
||||
};
|
||||
let per_turn_config = Session::build_per_turn_config(&session_configuration);
|
||||
let per_turn_config =
|
||||
Session::build_per_turn_config(&session_configuration, session_configuration.cwd.clone());
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(
|
||||
session_configuration.collaboration_mode.model(),
|
||||
&per_turn_config.to_models_manager_config(),
|
||||
@@ -4223,6 +4359,8 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
|
||||
&models_manager,
|
||||
/*network*/ None,
|
||||
Some(environment),
|
||||
/*environments*/ None,
|
||||
session_configuration.cwd.clone(),
|
||||
"turn_id".to_string(),
|
||||
Arc::clone(&js_repl),
|
||||
skills_outcome,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::*;
|
||||
use codex_model_provider::SharedModelProvider;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
|
||||
pub(super) fn image_generation_tool_auth_allowed(auth_manager: Option<&AuthManager>) -> bool {
|
||||
matches!(
|
||||
@@ -24,6 +25,14 @@ impl TurnSkillsContext {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct TurnEnvironment {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) environment_id: String,
|
||||
pub(crate) environment: Arc<Environment>,
|
||||
pub(crate) cwd: AbsolutePathBuf,
|
||||
}
|
||||
|
||||
/// The context needed for a single turn of the thread.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TurnContext {
|
||||
@@ -39,6 +48,7 @@ pub(crate) struct TurnContext {
|
||||
pub(crate) reasoning_summary: ReasoningSummaryConfig,
|
||||
pub(crate) session_source: SessionSource,
|
||||
pub(crate) environment: Option<Arc<Environment>>,
|
||||
pub(crate) environments: Option<Vec<TurnEnvironment>>,
|
||||
/// The session's absolute working directory. All relative paths provided
|
||||
/// by the model as well as sandbox policies are resolved against this path
|
||||
/// instead of `std::env::current_dir()`.
|
||||
@@ -168,6 +178,7 @@ impl TurnContext {
|
||||
reasoning_summary: self.reasoning_summary,
|
||||
session_source: self.session_source.clone(),
|
||||
environment: self.environment.clone(),
|
||||
environments: self.environments.clone(),
|
||||
cwd: self.cwd.clone(),
|
||||
current_date: self.current_date.clone(),
|
||||
timezone: self.timezone.clone(),
|
||||
@@ -300,11 +311,14 @@ fn local_time_context() -> (String, String) {
|
||||
|
||||
impl Session {
|
||||
/// Don't expand the number of mutated arguments on config. We are in the process of getting rid of it.
|
||||
pub(crate) fn build_per_turn_config(session_configuration: &SessionConfiguration) -> Config {
|
||||
pub(crate) fn build_per_turn_config(
|
||||
session_configuration: &SessionConfiguration,
|
||||
cwd: AbsolutePathBuf,
|
||||
) -> Config {
|
||||
// todo(aibrahim): store this state somewhere else so we don't need to mut config
|
||||
let config = session_configuration.original_config_do_not_use.clone();
|
||||
let mut per_turn_config = (*config).clone();
|
||||
per_turn_config.cwd = session_configuration.cwd.clone();
|
||||
per_turn_config.cwd = cwd;
|
||||
per_turn_config.model_reasoning_effort =
|
||||
session_configuration.collaboration_mode.reasoning_effort();
|
||||
per_turn_config.model_reasoning_summary = session_configuration.model_reasoning_summary;
|
||||
@@ -346,6 +360,8 @@ impl Session {
|
||||
models_manager: &ModelsManager,
|
||||
network: Option<NetworkProxy>,
|
||||
environment: Option<Arc<Environment>>,
|
||||
environments: Option<Vec<TurnEnvironment>>,
|
||||
cwd: AbsolutePathBuf,
|
||||
sub_id: String,
|
||||
js_repl: Arc<JsReplHandle>,
|
||||
skills_outcome: Arc<SkillLoadOutcome>,
|
||||
@@ -389,8 +405,6 @@ impl Session {
|
||||
&per_turn_config.agent_roles,
|
||||
));
|
||||
|
||||
let cwd = session_configuration.cwd.clone();
|
||||
|
||||
let per_turn_config = Arc::new(per_turn_config);
|
||||
let turn_metadata_state = Arc::new(TurnMetadataState::new(
|
||||
conversation_id.to_string(),
|
||||
@@ -414,6 +428,7 @@ impl Session {
|
||||
reasoning_summary,
|
||||
session_source,
|
||||
environment,
|
||||
environments,
|
||||
cwd,
|
||||
current_date: Some(current_date),
|
||||
timezone: Some(timezone),
|
||||
@@ -450,7 +465,22 @@ impl Session {
|
||||
&self,
|
||||
sub_id: String,
|
||||
updates: SessionSettingsUpdate,
|
||||
) -> ConstraintResult<Arc<TurnContext>> {
|
||||
environment_selections: Option<Vec<TurnEnvironmentSelection>>,
|
||||
) -> CodexResult<Arc<TurnContext>> {
|
||||
let turn_environments = match self.resolve_turn_environments(environment_selections) {
|
||||
Ok(turn_environments) => turn_environments,
|
||||
Err(err) => {
|
||||
self.send_event_raw(Event {
|
||||
id: sub_id.clone(),
|
||||
msg: EventMsg::Error(ErrorEvent {
|
||||
message: err.to_string(),
|
||||
codex_error_info: Some(CodexErrorInfo::BadRequest),
|
||||
}),
|
||||
})
|
||||
.await;
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
let update_result = {
|
||||
let mut state = self.state.lock().await;
|
||||
match state.session_configuration.clone().apply(&updates) {
|
||||
@@ -482,15 +512,16 @@ impl Session {
|
||||
) = match update_result {
|
||||
Ok(update) => update,
|
||||
Err(err) => {
|
||||
let message = err.to_string();
|
||||
self.send_event_raw(Event {
|
||||
id: sub_id.clone(),
|
||||
msg: EventMsg::Error(ErrorEvent {
|
||||
message: err.to_string(),
|
||||
message: message.clone(),
|
||||
codex_error_info: Some(CodexErrorInfo::BadRequest),
|
||||
}),
|
||||
})
|
||||
.await;
|
||||
return Err(err);
|
||||
return Err(CodexErr::InvalidRequest(message));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -511,17 +542,63 @@ impl Session {
|
||||
sub_id,
|
||||
session_configuration,
|
||||
updates.final_output_json_schema,
|
||||
turn_environments,
|
||||
)
|
||||
.await)
|
||||
}
|
||||
|
||||
fn resolve_turn_environments(
|
||||
&self,
|
||||
environment_selections: Option<Vec<TurnEnvironmentSelection>>,
|
||||
) -> CodexResult<Option<Vec<TurnEnvironment>>> {
|
||||
let Some(environment_selections) = environment_selections else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let mut turn_environments = Vec::with_capacity(environment_selections.len());
|
||||
for environment_selection in environment_selections {
|
||||
let environment = self
|
||||
.services
|
||||
.environment_manager
|
||||
.get_environment(&environment_selection.environment_id)
|
||||
.ok_or_else(|| {
|
||||
CodexErr::InvalidRequest(format!(
|
||||
"unknown turn environment id `{}`",
|
||||
environment_selection.environment_id
|
||||
))
|
||||
})?;
|
||||
let cwd = environment_selection.cwd;
|
||||
turn_environments.push(TurnEnvironment {
|
||||
environment_id: environment_selection.environment_id,
|
||||
environment,
|
||||
cwd,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Some(turn_environments))
|
||||
}
|
||||
|
||||
async fn new_turn_from_configuration(
|
||||
&self,
|
||||
sub_id: String,
|
||||
session_configuration: SessionConfiguration,
|
||||
final_output_json_schema: Option<Option<Value>>,
|
||||
turn_environments: Option<Vec<TurnEnvironment>>,
|
||||
) -> Arc<TurnContext> {
|
||||
let per_turn_config = Self::build_per_turn_config(&session_configuration);
|
||||
// `None` means use the thread's default environment. `Some([])` is an
|
||||
// explicit no-environment turn, so do not fall back in that case.
|
||||
let primary_turn_environment = turn_environments
|
||||
.as_ref()
|
||||
.and_then(|turn_environments| turn_environments.first());
|
||||
let environment = match primary_turn_environment {
|
||||
Some(turn_environment) => Some(Arc::clone(&turn_environment.environment)),
|
||||
None if turn_environments.is_some() => None,
|
||||
None => self.services.environment_manager.default_environment(),
|
||||
};
|
||||
let cwd = primary_turn_environment
|
||||
.map(|turn_environment| turn_environment.cwd.clone())
|
||||
.unwrap_or_else(|| session_configuration.cwd.clone());
|
||||
let per_turn_config = Self::build_per_turn_config(&session_configuration, cwd.clone());
|
||||
{
|
||||
let mcp_connection_manager = self.services.mcp_connection_manager.read().await;
|
||||
mcp_connection_manager.set_approval_policy(&session_configuration.approval_policy);
|
||||
@@ -544,7 +621,6 @@ impl Session {
|
||||
.await;
|
||||
let effective_skill_roots = plugin_outcome.effective_skill_roots();
|
||||
let skills_input = skills_load_input_from_config(&per_turn_config, effective_skill_roots);
|
||||
let environment = self.services.environment_manager.default_environment();
|
||||
let fs = environment
|
||||
.as_ref()
|
||||
.map(|environment| environment.get_filesystem());
|
||||
@@ -576,6 +652,8 @@ impl Session {
|
||||
.then(|| started_proxy.proxy())
|
||||
}),
|
||||
environment,
|
||||
turn_environments,
|
||||
cwd,
|
||||
sub_id,
|
||||
Arc::clone(&self.js_repl),
|
||||
skills_outcome,
|
||||
@@ -619,6 +697,7 @@ impl Session {
|
||||
sub_id,
|
||||
session_configuration,
|
||||
/*final_output_json_schema*/ None,
|
||||
/*turn_environments*/ None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -2239,6 +2239,7 @@ async fn send_input_accepts_structured_items() {
|
||||
.expect("send_input should succeed");
|
||||
|
||||
let expected = Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![
|
||||
UserInput::Mention {
|
||||
name: "drive".to_string(),
|
||||
|
||||
@@ -35,6 +35,7 @@ use codex_protocol::protocol::RealtimeConversationVersion as RealtimeWsVersion;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::SessionConfiguredEvent;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use futures::future::BoxFuture;
|
||||
@@ -605,6 +606,7 @@ impl TestCodex {
|
||||
AskForApproval::Never,
|
||||
SandboxPolicy::DangerFullAccess,
|
||||
Some(service_tier),
|
||||
/*environments*/ None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -620,6 +622,22 @@ impl TestCodex {
|
||||
approval_policy,
|
||||
sandbox_policy,
|
||||
/*service_tier*/ None,
|
||||
/*environments*/ None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn submit_turn_with_environments(
|
||||
&self,
|
||||
prompt: &str,
|
||||
environments: Option<Vec<TurnEnvironmentSelection>>,
|
||||
) -> Result<()> {
|
||||
self.submit_turn_with_context(
|
||||
prompt,
|
||||
AskForApproval::Never,
|
||||
SandboxPolicy::DangerFullAccess,
|
||||
/*service_tier*/ None,
|
||||
environments,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -630,10 +648,12 @@ impl TestCodex {
|
||||
approval_policy: AskForApproval,
|
||||
sandbox_policy: SandboxPolicy,
|
||||
service_tier: Option<Option<ServiceTier>>,
|
||||
environments: Option<Vec<TurnEnvironmentSelection>>,
|
||||
) -> Result<()> {
|
||||
let session_model = self.session_configured.model.clone();
|
||||
self.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments,
|
||||
items: vec![UserInput::Text {
|
||||
text: prompt.into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -46,6 +46,7 @@ async fn interrupt_long_running_tool_emits_turn_aborted() {
|
||||
// Kick off a turn that triggers the function call.
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "start sleep".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -101,6 +102,7 @@ async fn interrupt_tool_records_history_entries() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "start history recording".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -120,6 +122,7 @@ async fn interrupt_tool_records_history_entries() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "follow up".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -201,6 +204,7 @@ async fn interrupt_persists_turn_aborted_marker_in_next_request() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "start interrupt marker".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -220,6 +224,7 @@ async fn interrupt_persists_turn_aborted_marker_in_next_request() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "follow up".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -357,6 +357,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 {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "rename without content change".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -994,6 +995,7 @@ async fn apply_patch_custom_tool_streaming_emits_updated_changes() -> Result<()>
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "create streamed file".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1091,6 +1093,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 {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "apply via shell heredoc with cd".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1175,6 +1178,7 @@ async fn apply_patch_shell_command_failure_propagates_error_and_skips_diff() ->
|
||||
let model = test.session_configured.model.clone();
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "apply patch via shell".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1330,6 +1334,7 @@ async fn apply_patch_emits_turn_diff_event_with_unified_diff(
|
||||
let model = test.session_configured.model.clone();
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "emit diff".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1397,6 +1402,7 @@ async fn apply_patch_turn_diff_for_rename_with_content_change(
|
||||
let model = test.session_configured.model.clone();
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "rename with change".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1473,6 +1479,7 @@ async fn apply_patch_aggregates_diff_across_multiple_tool_calls() -> Result<()>
|
||||
let model = test.session_configured.model.clone();
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "aggregate diffs".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1549,6 +1556,7 @@ async fn apply_patch_aggregates_diff_preserves_success_after_failure() -> Result
|
||||
let model = test.session_configured.model.clone();
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "apply patch twice with failure".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -584,6 +584,7 @@ async fn submit_turn(
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: prompt.into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -383,6 +383,7 @@ 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 {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -747,6 +748,7 @@ async fn includes_conversation_id_and_model_headers_in_request() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -947,6 +949,7 @@ async fn includes_base_instructions_override_in_request() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1001,6 +1004,7 @@ async fn chatgpt_auth_sends_correct_request() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1113,6 +1117,7 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1150,6 +1155,7 @@ async fn includes_user_instructions_message_in_request() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1236,6 +1242,7 @@ async fn includes_apps_guidance_as_developer_message_for_chatgpt_auth() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1297,6 +1304,7 @@ async fn omits_apps_guidance_for_api_key_auth_even_when_feature_enabled() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1354,6 +1362,7 @@ async fn omits_apps_guidance_when_configured_off() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1394,6 +1403,7 @@ async fn omits_environment_context_when_configured_off() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1449,6 +1459,7 @@ async fn skills_append_to_developer_message() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1501,6 +1512,7 @@ async fn includes_configured_effort_in_request() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1541,6 +1553,7 @@ async fn includes_no_effort_in_request() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1582,6 +1595,7 @@ async fn includes_default_reasoning_effort_in_request_when_defined_by_model_info
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1636,6 +1650,7 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1692,6 +1707,7 @@ async fn configured_reasoning_summary_is_sent() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1755,6 +1771,7 @@ async fn user_turn_explicit_reasoning_summary_overrides_model_catalog_default()
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1808,6 +1825,7 @@ async fn reasoning_summary_is_omitted_when_disabled() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1865,6 +1883,7 @@ async fn reasoning_summary_none_overrides_model_catalog_default() -> anyhow::Res
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1902,6 +1921,7 @@ async fn includes_default_verbosity_in_request() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1948,6 +1968,7 @@ async fn configured_verbosity_not_sent_for_models_without_support() -> anyhow::R
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1993,6 +2014,7 @@ async fn configured_verbosity_is_sent() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2043,6 +2065,7 @@ async fn includes_developer_instructions_message_in_request() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2335,6 +2358,7 @@ async fn token_count_includes_rate_limits_snapshot() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2504,6 +2528,7 @@ async fn usage_limit_error_emits_rate_limit_event() -> anyhow::Result<()> {
|
||||
|
||||
let submission_id = codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2579,6 +2604,7 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "seed turn".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2592,6 +2618,7 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "trigger context window".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2675,6 +2702,7 @@ async fn incomplete_response_emits_content_filter_error_message() -> anyhow::Res
|
||||
.await?;
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "trigger incomplete".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2784,6 +2812,7 @@ async fn azure_overrides_assign_properties_used_for_responses_url() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2871,6 +2900,7 @@ async fn env_var_overrides_loaded_auth() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2933,6 +2963,7 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() {
|
||||
// Turn 1: user sends U1; wait for completion.
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "U1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2947,6 +2978,7 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() {
|
||||
// Turn 2: user sends U2; wait for completion.
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "U2".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2961,6 +2993,7 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() {
|
||||
// Turn 3: user sends U3; wait for completion.
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "U3".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -998,6 +998,7 @@ async fn responses_websocket_usage_limit_error_emits_rate_limit_event() {
|
||||
let submission_id = test
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1085,6 +1086,7 @@ async fn responses_websocket_invalid_request_error_with_status_is_forwarded() {
|
||||
let submission_id = test
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -2609,6 +2609,7 @@ text(
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "use exec to inspect and call hidden tools".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -79,6 +79,7 @@ async fn no_collaboration_instructions_by_default() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -139,6 +140,7 @@ async fn user_input_includes_collaboration_instructions_after_override() -> Resu
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -174,6 +176,7 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -238,6 +241,7 @@ async fn override_then_next_turn_uses_updated_collaboration_instructions() -> Re
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -291,6 +295,7 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -362,6 +367,7 @@ async fn collaboration_mode_update_emits_new_instruction_message() -> Result<()>
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -390,6 +396,7 @@ async fn collaboration_mode_update_emits_new_instruction_message() -> Result<()>
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -447,6 +454,7 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -475,6 +483,7 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -534,6 +543,7 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -565,6 +575,7 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -625,6 +636,7 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged()
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -656,6 +668,7 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged()
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -720,6 +733,7 @@ async fn resume_replays_collaboration_instructions() -> Result<()> {
|
||||
initial
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -734,6 +748,7 @@ async fn resume_replays_collaboration_instructions() -> Result<()> {
|
||||
resumed
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "after resume".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -791,6 +806,7 @@ async fn empty_collaboration_instructions_are_ignored() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -240,6 +240,7 @@ async fn summarize_context_three_requests_and_instructions() {
|
||||
// 1) Normal user input – should hit server once.
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello world".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -263,6 +264,7 @@ async fn summarize_context_three_requests_and_instructions() {
|
||||
// 3) Next user input – third hit; history should include only the summary.
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: THIRD_USER_MSG.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -440,6 +442,7 @@ async fn manual_compact_uses_custom_prompt() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_ONE".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -585,6 +588,7 @@ async fn manual_compact_emits_context_compaction_items() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "manual compact".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -749,6 +753,7 @@ async fn multiple_auto_compact_per_task_runs_after_token_limit_hit() {
|
||||
// Start the conversation with the user message
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: user_message.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1249,6 +1254,7 @@ async fn auto_compact_runs_after_token_limit_hit() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: FIRST_AUTO_MSG.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1263,6 +1269,7 @@ async fn auto_compact_runs_after_token_limit_hit() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: SECOND_AUTO_MSG.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1277,6 +1284,7 @@ async fn auto_compact_runs_after_token_limit_hit() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: POST_AUTO_USER_MSG.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1446,6 +1454,7 @@ async fn auto_compact_emits_context_compaction_items() {
|
||||
for user in [FIRST_AUTO_MSG, SECOND_AUTO_MSG, POST_AUTO_USER_MSG] {
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: user.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1525,6 +1534,7 @@ async fn auto_compact_starts_after_turn_started() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: FIRST_AUTO_MSG.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1538,6 +1548,7 @@ async fn auto_compact_starts_after_turn_started() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: SECOND_AUTO_MSG.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1551,6 +1562,7 @@ async fn auto_compact_starts_after_turn_started() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: POST_AUTO_USER_MSG.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1665,6 +1677,7 @@ async fn auto_compact_runs_after_resume_when_token_usage_is_over_limit() {
|
||||
resumed
|
||||
.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: follow_up_user.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1756,6 +1769,7 @@ async fn pre_sampling_compact_runs_on_switch_to_smaller_context_model() {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "before switch".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1781,6 +1795,7 @@ async fn pre_sampling_compact_runs_on_switch_to_smaller_context_model() {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "after switch".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1892,6 +1907,7 @@ async fn pre_sampling_compact_runs_after_resume_and_switch_to_smaller_model() {
|
||||
initial
|
||||
.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "before resume".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1941,6 +1957,7 @@ async fn pre_sampling_compact_runs_after_resume_and_switch_to_smaller_model() {
|
||||
resumed
|
||||
.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "after resume".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2044,6 +2061,7 @@ async fn auto_compact_persists_rollout_entries() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: FIRST_AUTO_MSG.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2057,6 +2075,7 @@ async fn auto_compact_persists_rollout_entries() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: SECOND_AUTO_MSG.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2070,6 +2089,7 @@ async fn auto_compact_persists_rollout_entries() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: POST_AUTO_USER_MSG.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2157,6 +2177,7 @@ async fn manual_compact_retries_after_context_window_error() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "first turn".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2269,6 +2290,7 @@ async fn manual_compact_non_context_failure_retries_then_emits_task_error() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "first turn".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2362,6 +2384,7 @@ async fn manual_compact_twice_preserves_latest_user_messages() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: first_user_message.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2378,6 +2401,7 @@ async fn manual_compact_twice_preserves_latest_user_messages() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: second_user_message.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2394,6 +2418,7 @@ async fn manual_compact_twice_preserves_latest_user_messages() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: final_user_message.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2556,6 +2581,7 @@ async fn auto_compact_allows_multiple_attempts_when_interleaved_with_other_turn_
|
||||
for user in [MULTI_AUTO_MSG, follow_up_user, final_user] {
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: user.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2659,6 +2685,7 @@ async fn snapshot_request_shape_mid_turn_continuation_compaction() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: FUNCTION_CALL_LIMIT_MSG.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2858,6 +2885,7 @@ async fn auto_compact_counts_encrypted_reasoning_before_last_user() {
|
||||
{
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: user.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2976,6 +3004,7 @@ async fn auto_compact_runs_when_reasoning_header_clears_between_turns() {
|
||||
for user in [first_user, second_user, third_user] {
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: user.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -3036,6 +3065,7 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess
|
||||
for user in ["USER_ONE", "USER_TWO"] {
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: user.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -3067,6 +3097,7 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess
|
||||
.to_string();
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![
|
||||
UserInput::Image {
|
||||
image_url: image_url.clone(),
|
||||
@@ -3162,6 +3193,7 @@ async fn snapshot_request_shape_pre_turn_compaction_strips_incoming_model_switch
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "BEFORE_SWITCH_USER".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -3187,6 +3219,7 @@ async fn snapshot_request_shape_pre_turn_compaction_strips_incoming_model_switch
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "AFTER_SWITCH_USER".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -3283,6 +3316,7 @@ async fn snapshot_request_shape_pre_turn_compaction_context_window_exceeded() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_ONE".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -3296,6 +3330,7 @@ async fn snapshot_request_shape_pre_turn_compaction_context_window_exceeded() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_TWO".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -3367,6 +3402,7 @@ async fn snapshot_request_shape_manual_compact_without_previous_user_messages()
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "AFTER_MANUAL_EMPTY_COMPACT".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -246,6 +246,7 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello remote compact".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -261,6 +262,7 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "after compact".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -392,6 +394,7 @@ async fn remote_compact_runs_automatically() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello remote compact".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -467,6 +470,7 @@ async fn remote_compact_trims_function_call_history_to_fit_context_window() -> R
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: first_user_message.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -479,6 +483,7 @@ async fn remote_compact_trims_function_call_history_to_fit_context_window() -> R
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: second_user_message.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -595,6 +600,7 @@ async fn auto_remote_compact_trims_function_call_history_to_fit_context_window()
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: first_user_message.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -607,6 +613,7 @@ async fn auto_remote_compact_trims_function_call_history_to_fit_context_window()
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: second_user_message.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -625,6 +632,7 @@ async fn auto_remote_compact_trims_function_call_history_to_fit_context_window()
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "turn that triggers auto compact".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -723,6 +731,7 @@ async fn auto_remote_compact_failure_stops_agent_loop() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "turn that exceeds token threshold".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -735,6 +744,7 @@ async fn auto_remote_compact_failure_stops_agent_loop() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "turn that triggers auto compact".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -827,6 +837,7 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result
|
||||
|
||||
baseline_codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: first_user_message.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -842,6 +853,7 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result
|
||||
|
||||
baseline_codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: second_user_message.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -931,6 +943,7 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result
|
||||
|
||||
override_codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: first_user_message.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -946,6 +959,7 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result
|
||||
|
||||
override_codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: second_user_message.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1015,6 +1029,7 @@ async fn remote_manual_compact_emits_context_compaction_items() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "manual remote compact".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1094,6 +1109,7 @@ async fn remote_manual_compact_failure_emits_task_error_event() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "manual remote compact".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1177,6 +1193,7 @@ async fn remote_compact_persists_replacement_history_in_rollout() -> Result<()>
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "needs compaction".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1319,6 +1336,7 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res
|
||||
initial
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "start remote compact flow".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1335,6 +1353,7 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res
|
||||
initial
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "after compact in same session".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1358,6 +1377,7 @@ async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Res
|
||||
resumed
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "after resume".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1453,6 +1473,7 @@ async fn remote_compact_refreshes_stale_developer_instructions_without_resume()
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "start remote compact flow".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1468,6 +1489,7 @@ async fn remote_compact_refreshes_stale_developer_instructions_without_resume()
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "after compact in same session".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1538,6 +1560,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_sta
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_ONE".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1550,6 +1573,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_sta
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_TWO".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1615,6 +1639,7 @@ async fn remote_request_uses_custom_experimental_realtime_start_instructions() -
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_ONE".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1674,6 +1699,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_end
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_ONE".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1688,6 +1714,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_end
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_TWO".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1761,6 +1788,7 @@ async fn snapshot_request_shape_remote_manual_compact_restates_realtime_start()
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_ONE".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1776,6 +1804,7 @@ async fn snapshot_request_shape_remote_manual_compact_restates_realtime_start()
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_TWO".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1857,6 +1886,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_does_not_restate_real
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "SETUP_USER".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1871,6 +1901,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_does_not_restate_real
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_TWO".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1960,6 +1991,7 @@ async fn snapshot_request_shape_remote_compact_resume_restates_realtime_end() ->
|
||||
initial
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_ONE".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1988,6 +2020,7 @@ async fn snapshot_request_shape_remote_compact_resume_restates_realtime_end() ->
|
||||
resumed
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_TWO".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2082,6 +2115,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_including_incoming_us
|
||||
}
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: user.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2167,6 +2201,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_strips_incoming_model
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "BEFORE_SWITCH_USER".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2194,6 +2229,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_strips_incoming_model
|
||||
.await?;
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "AFTER_SWITCH_USER".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2311,6 +2347,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_context_window_exceed
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_ONE".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2323,6 +2360,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_context_window_exceed
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_TWO".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2406,6 +2444,7 @@ async fn snapshot_request_shape_remote_mid_turn_continuation_compaction() -> Res
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_ONE".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2482,6 +2521,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_summary_only_reinject
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_ONE".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2566,6 +2606,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_multi_summary_reinjec
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_ONE".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2581,6 +2622,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_multi_summary_reinjec
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_TWO".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2661,6 +2703,7 @@ async fn snapshot_request_shape_remote_manual_compact_without_previous_user_mess
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "USER_ONE".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -803,6 +803,7 @@ async fn start_test_conversation(
|
||||
async fn user_turn(conversation: &Arc<CodexThread>, text: &str) {
|
||||
conversation
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: text.into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -44,6 +44,7 @@ async fn submit_user_turn(
|
||||
let session_model = test.session_configured.model.clone();
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: prompt.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -125,6 +126,7 @@ async fn execpolicy_blocks_shell_invocation() -> Result<()> {
|
||||
let session_model = test.session_configured.model.clone();
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "run shell command".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -48,6 +48,7 @@ async fn fork_thread_twice_drops_to_first_message() {
|
||||
for text in ["first", "second", "third"] {
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: text.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -1036,6 +1036,7 @@ async fn blocked_queued_prompt_does_not_strand_earlier_accepted_prompt() -> Resu
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "initial prompt".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1053,6 +1054,7 @@ async fn blocked_queued_prompt_does_not_strand_earlier_accepted_prompt() -> Resu
|
||||
for text in ["accepted queued prompt", "blocked queued prompt"] {
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: text.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -111,6 +111,7 @@ async fn copy_paste_local_image_persists_rollout_request_shape() -> anyhow::Resu
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![
|
||||
UserInput::LocalImage {
|
||||
path: abs_path.clone(),
|
||||
@@ -198,6 +199,7 @@ async fn drag_drop_image_persists_rollout_request_shape() -> anyhow::Result<()>
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![
|
||||
UserInput::Image {
|
||||
image_url: image_url.clone(),
|
||||
|
||||
@@ -85,6 +85,7 @@ async fn user_message_item_is_emitted() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![expected_input.clone()],
|
||||
final_output_json_schema: None,
|
||||
responsesapi_client_metadata: None,
|
||||
@@ -139,6 +140,7 @@ async fn assistant_message_item_is_emitted() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please summarize results".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -198,6 +200,7 @@ async fn reasoning_item_is_emitted() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "explain your reasoning".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -258,6 +261,7 @@ async fn web_search_item_is_emitted() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "find the weather".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -323,6 +327,7 @@ async fn image_generation_call_event_is_emitted() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "generate a tiny blue square".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -386,6 +391,7 @@ async fn image_generation_call_event_is_emitted_when_image_save_fails() -> anyho
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "generate an image".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -440,6 +446,7 @@ async fn agent_message_content_delta_has_item_metadata() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please stream text".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -523,6 +530,7 @@ async fn plan_mode_emits_plan_item_from_proposed_plan_block() -> anyhow::Result<
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please plan".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -600,6 +608,7 @@ async fn plan_mode_strips_plan_from_agent_messages() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please plan".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -709,6 +718,7 @@ async fn plan_mode_streaming_citations_are_stripped_across_added_deltas_and_done
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please plan with citations".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -896,6 +906,7 @@ async fn plan_mode_streaming_proposed_plan_tag_split_across_added_and_delta_is_p
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please plan".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1010,6 +1021,7 @@ async fn plan_mode_handles_missing_plan_close_tag() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please plan".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1088,6 +1100,7 @@ async fn reasoning_content_delta_has_item_metadata() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "reason through it".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1148,6 +1161,7 @@ async fn reasoning_raw_content_delta_respects_flag() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "show raw reasoning".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -73,6 +73,7 @@ async fn codex_returns_json_result(model: String) -> anyhow::Result<()> {
|
||||
// 1) Normal user input – should hit server once.
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello world".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -48,6 +48,7 @@ async fn submit_skill_turn(test: &TestCodex, skill_path: PathBuf, prompt: &str)
|
||||
let session_model = test.session_configured.model.clone();
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![
|
||||
UserInput::Text {
|
||||
text: prompt.to_string(),
|
||||
|
||||
@@ -121,6 +121,7 @@ async fn model_change_appends_model_instructions_developer_message() -> Result<(
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -158,6 +159,7 @@ async fn model_change_appends_model_instructions_developer_message() -> Result<(
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "switch models".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -218,6 +220,7 @@ async fn model_and_personality_change_only_appends_model_instructions() -> Resul
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -255,6 +258,7 @@ async fn model_and_personality_change_only_appends_model_instructions() -> Resul
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "switch model and personality".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -392,6 +396,7 @@ async fn model_change_from_image_to_text_strips_prior_image_content() -> Result<
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![
|
||||
UserInput::Image {
|
||||
image_url: image_url.clone(),
|
||||
@@ -418,6 +423,7 @@ async fn model_change_from_image_to_text_strips_prior_image_content() -> Result<
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "second turn".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -526,6 +532,7 @@ async fn generated_image_is_replayed_for_image_capable_models() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "generate a lobster".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -547,6 +554,7 @@ async fn generated_image_is_replayed_for_image_capable_models() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "describe the generated image".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -658,6 +666,7 @@ async fn model_change_from_generated_image_to_text_preserves_prior_generated_ima
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "generate a lobster".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -679,6 +688,7 @@ async fn model_change_from_generated_image_to_text_preserves_prior_generated_ima
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "describe the generated image".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -792,6 +802,7 @@ async fn thread_rollback_after_generated_image_drops_entire_image_turn_history()
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "generate a lobster".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -821,6 +832,7 @@ async fn thread_rollback_after_generated_image_drops_entire_image_turn_history()
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "after rollback".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -978,6 +990,7 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result<
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "use larger model".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1037,6 +1050,7 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result<
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "switch to smaller model".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -114,6 +114,7 @@ async fn snapshot_model_visible_layout_turn_overrides() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "first turn".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -138,6 +139,7 @@ async fn snapshot_model_visible_layout_turn_overrides() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "second turn with context updates".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -217,6 +219,7 @@ async fn snapshot_model_visible_layout_cwd_change_does_not_refresh_agents() -> R
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "first turn in agents_one".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -241,6 +244,7 @@ async fn snapshot_model_visible_layout_cwd_change_does_not_refresh_agents() -> R
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "second turn in agents_two".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -317,6 +321,7 @@ async fn snapshot_model_visible_layout_resume_with_personality_change() -> Resul
|
||||
.await;
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "seed resume history".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -352,6 +357,7 @@ async fn snapshot_model_visible_layout_resume_with_personality_change() -> Resul
|
||||
resumed
|
||||
.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "resume and change personality".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -417,6 +423,7 @@ async fn snapshot_model_visible_layout_resume_override_matches_rollout_model() -
|
||||
.await;
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "seed resume history".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -463,6 +470,7 @@ async fn snapshot_model_visible_layout_resume_override_matches_rollout_model() -
|
||||
resumed
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "first resumed turn after model override".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -90,6 +90,7 @@ async fn renews_cache_ttl_on_matching_models_etag() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hi".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -101,6 +101,7 @@ async fn refresh_models_on_models_etag_mismatch_and_avoid_duplicate_models_fetch
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please run a tool".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -102,6 +102,7 @@ async fn responses_api_emits_api_request_event() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -146,6 +147,7 @@ async fn process_sse_emits_tracing_for_output_item() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -190,6 +192,7 @@ async fn process_sse_emits_failed_event_on_parse_error() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -235,6 +238,7 @@ async fn process_sse_records_failed_event_when_stream_closes_without_completed()
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -300,6 +304,7 @@ async fn process_sse_failed_event_records_response_error_message() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -363,6 +368,7 @@ async fn process_sse_failed_event_logs_parse_error() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -413,6 +419,7 @@ async fn process_sse_failed_event_logs_missing_error() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -472,6 +479,7 @@ async fn process_sse_failed_event_logs_response_completed_parse_error() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -525,6 +533,7 @@ async fn process_sse_emits_completed_telemetry() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -598,6 +607,7 @@ async fn handle_responses_span_records_response_kind_and_tool_name() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -683,6 +693,7 @@ async fn record_responses_sets_span_fields_for_response_events() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -768,6 +779,7 @@ async fn handle_response_item_records_tool_result_for_custom_tool_call() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -842,6 +854,7 @@ async fn handle_response_item_records_tool_result_for_function_call() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -926,6 +939,7 @@ async fn handle_response_item_records_tool_result_for_local_shell_missing_ids()
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -995,6 +1009,7 @@ async fn handle_response_item_records_tool_result_for_local_shell_call() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1104,6 +1119,7 @@ async fn handle_container_exec_autoapprove_from_config_records_tool_decision() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1156,6 +1172,7 @@ async fn handle_container_exec_user_approved_records_tool_decision() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "approved".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1223,6 +1240,7 @@ async fn handle_container_exec_user_approved_for_session_records_tool_decision()
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "persist".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1290,6 +1308,7 @@ async fn handle_sandbox_error_user_approves_retry_records_tool_decision() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "retry".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1357,6 +1376,7 @@ async fn handle_container_exec_user_denies_records_tool_decision() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "deny".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1424,6 +1444,7 @@ async fn handle_sandbox_error_user_approves_for_session_records_tool_decision()
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "persist".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1492,6 +1513,7 @@ async fn handle_sandbox_error_user_denies_records_tool_decision() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "deny".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -95,6 +95,7 @@ async fn build_codex(server: &StreamingSseServer) -> Arc<CodexThread> {
|
||||
async fn submit_user_input(codex: &CodexThread, text: &str) {
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: text.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -109,6 +110,7 @@ async fn submit_user_input(codex: &CodexThread, text: &str) {
|
||||
async fn submit_danger_full_access_user_turn(test: &TestCodex, text: &str) {
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: text.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -272,6 +274,7 @@ async fn injected_user_input_triggers_follow_up_request_with_deltas() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "first prompt".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -289,6 +292,7 @@ async fn injected_user_input_triggers_follow_up_request_with_deltas() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "second prompt".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -49,6 +49,7 @@ async fn permissions_message_sent_once_on_start() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -87,6 +88,7 @@ async fn permissions_message_added_on_override_change() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -115,6 +117,7 @@ async fn permissions_message_added_on_override_change() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -159,6 +162,7 @@ async fn permissions_message_not_added_when_no_change() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -171,6 +175,7 @@ async fn permissions_message_not_added_when_no_change() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -215,6 +220,7 @@ async fn permissions_message_omitted_when_disabled() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -243,6 +249,7 @@ async fn permissions_message_omitted_when_disabled() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -300,6 +307,7 @@ async fn resume_replays_permissions_messages() -> Result<()> {
|
||||
initial
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -330,6 +338,7 @@ async fn resume_replays_permissions_messages() -> Result<()> {
|
||||
initial
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -344,6 +353,7 @@ async fn resume_replays_permissions_messages() -> Result<()> {
|
||||
resumed
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "after resume".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -402,6 +412,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> {
|
||||
initial
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -432,6 +443,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> {
|
||||
initial
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -452,6 +464,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> {
|
||||
resumed
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "after resume".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -485,6 +498,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> {
|
||||
forked
|
||||
.thread
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "after fork".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -538,6 +552,7 @@ async fn permissions_message_includes_writable_roots() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -96,6 +96,7 @@ async fn user_turn_personality_none_does_not_add_update_message() -> anyhow::Res
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -147,6 +148,7 @@ async fn config_personality_some_sets_instructions_template() -> anyhow::Result<
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -205,6 +207,7 @@ async fn config_personality_none_sends_no_personality() -> anyhow::Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -269,6 +272,7 @@ async fn default_personality_is_pragmatic_without_config_toml() -> anyhow::Resul
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -321,6 +325,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()>
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -359,6 +364,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()>
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -426,6 +432,7 @@ async fn user_turn_personality_same_value_does_not_add_update_message() -> anyho
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -464,6 +471,7 @@ async fn user_turn_personality_same_value_does_not_add_update_message() -> anyho
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -544,6 +552,7 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()>
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -582,6 +591,7 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()>
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -701,6 +711,7 @@ async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -822,6 +833,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() -
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -860,6 +872,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() -
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -192,6 +192,7 @@ async fn capability_sections_render_in_developer_message_in_order() -> Result<()
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![codex_protocol::user_input::UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -268,6 +269,7 @@ async fn explicit_plugin_mentions_inject_plugin_guidance() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![codex_protocol::user_input::UserInput::Mention {
|
||||
name: "sample".into(),
|
||||
path: format!("plugin://{SAMPLE_PLUGIN_CONFIG_NAME}"),
|
||||
@@ -348,6 +350,7 @@ async fn explicit_plugin_mentions_track_plugin_used_analytics() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![codex_protocol::user_input::UserInput::Mention {
|
||||
name: "sample".into(),
|
||||
path: format!("plugin://{SAMPLE_PLUGIN_CONFIG_NAME}"),
|
||||
|
||||
@@ -146,6 +146,7 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -158,6 +159,7 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -244,6 +246,7 @@ async fn gpt_5_tools_without_apply_patch_append_apply_patch_instructions() -> an
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -256,6 +259,7 @@ async fn gpt_5_tools_without_apply_patch_append_apply_patch_instructions() -> an
|
||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -319,6 +323,7 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -331,6 +336,7 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -413,6 +419,7 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an
|
||||
// First turn
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -450,6 +457,7 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an
|
||||
// Second turn after overrides
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -533,6 +541,7 @@ async fn override_before_first_turn_emits_environment_context() -> anyhow::Resul
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "first message".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -685,6 +694,7 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res
|
||||
// First turn
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -707,6 +717,7 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res
|
||||
};
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -820,6 +831,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -841,6 +853,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -946,6 +959,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 1".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -967,6 +981,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello 2".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -41,6 +41,7 @@ async fn quota_exceeded_emits_single_error_event() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "quota?".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -1896,6 +1896,7 @@ async fn conversation_user_text_turn_is_sent_to_realtime_when_active() -> Result
|
||||
let prefixed_user_text = format!("[USER] {user_text}");
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: user_text.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2025,6 +2026,7 @@ async fn conversation_user_text_turn_is_capped_when_mirrored_to_realtime() -> Re
|
||||
);
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: user_text.clone(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -3183,6 +3185,7 @@ async fn inbound_handoff_request_steers_active_turn() -> Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "first prompt".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -172,6 +172,7 @@ async fn remote_models_config_context_window_override_clamps_to_max_context_wind
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -248,6 +249,7 @@ async fn remote_models_config_override_above_max_uses_max_context_window() -> Re
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -323,6 +325,7 @@ async fn remote_models_use_context_window_when_config_override_is_absent() -> Re
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -411,6 +414,7 @@ async fn remote_models_long_model_slug_is_sent_with_high_reasoning() -> Result<(
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -474,6 +478,7 @@ async fn namespaced_model_slug_uses_catalog_metadata_without_fallback_warning()
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -636,6 +641,7 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> {
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -861,6 +867,7 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> {
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ async fn request_body_is_zstd_compressed_for_codex_backend_when_enabled() -> any
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "compress me".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -88,6 +89,7 @@ async fn request_body_is_not_compressed_for_api_key_auth_even_when_enabled() ->
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "do not compress".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -187,6 +187,7 @@ async fn submit_turn(
|
||||
let session_model = test.session_configured.model.clone();
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: prompt.into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -138,6 +138,7 @@ async fn submit_turn(
|
||||
let session_model = test.session_configured.model.clone();
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: prompt.into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -131,6 +131,7 @@ async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Resul
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please confirm".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -249,6 +250,7 @@ where
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please confirm".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -127,6 +127,7 @@ async fn submit_turn_with_timeout(test: &TestCodex, prompt: &str) -> Result<()>
|
||||
let session_model = test.session_configured.model.clone();
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: prompt.into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -86,6 +86,7 @@ async fn resume_includes_initial_messages_from_rollout_events() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "Record some messages".into(),
|
||||
text_elements: text_elements.clone(),
|
||||
@@ -172,6 +173,7 @@ async fn resume_includes_initial_messages_from_reasoning_events() -> Result<()>
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "Record reasoning messages".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -262,6 +264,7 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "Record initial instructions".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -303,6 +306,7 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> {
|
||||
resumed
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "Resume with different model".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -319,6 +323,7 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> {
|
||||
resumed
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "Second turn after resume".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -390,6 +395,7 @@ async fn resume_model_switch_is_not_duplicated_after_pre_turn_override() -> Resu
|
||||
.await;
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "Record initial instructions".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -434,6 +440,7 @@ async fn resume_model_switch_is_not_duplicated_after_pre_turn_override() -> Resu
|
||||
resumed
|
||||
.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "first turn after override".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -721,6 +721,7 @@ async fn review_history_surfaces_in_parent_session() {
|
||||
let followup = "back to parent".to_string();
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: followup.clone(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -296,6 +296,7 @@ async fn call_cwd_tool(
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -429,6 +430,7 @@ async fn stdio_server_round_trip() -> anyhow::Result<()> {
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -797,6 +799,7 @@ async fn stdio_mcp_parallel_tool_calls_default_false_runs_serially() -> anyhow::
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -929,6 +932,7 @@ async fn stdio_mcp_parallel_tool_calls_opt_in_runs_concurrently() -> anyhow::Res
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -1028,6 +1032,7 @@ async fn stdio_image_responses_round_trip() -> anyhow::Result<()> {
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -1179,6 +1184,7 @@ async fn stdio_image_responses_preserve_original_detail_metadata() -> anyhow::Re
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -1416,6 +1422,7 @@ async fn stdio_image_responses_are_sanitized_for_text_only_model() -> anyhow::Re
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -1524,6 +1531,7 @@ async fn stdio_server_propagates_whitelisted_env_vars() -> anyhow::Result<()> {
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -1660,6 +1668,7 @@ async fn stdio_server_propagates_explicit_local_env_var_source() -> anyhow::Resu
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -1769,6 +1778,7 @@ async fn remote_stdio_env_var_source_does_not_copy_local_env() -> anyhow::Result
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -1893,6 +1903,7 @@ async fn streamable_http_tool_call_round_trip() -> anyhow::Result<()> {
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -2109,6 +2120,7 @@ async fn streamable_http_with_oauth_round_trip_impl() -> anyhow::Result<()> {
|
||||
service_tier: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
environments: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ async fn openai_model_header_mismatch_emits_warning_event_and_warning_item() ->
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "trigger safety check".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -137,6 +138,7 @@ async fn response_model_field_mismatch_emits_warning_when_header_matches_request
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "trigger response model check".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -223,6 +225,7 @@ async fn openai_model_header_mismatch_only_emits_one_warning_per_turn() -> Resul
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "trigger follow-up turn".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -273,6 +276,7 @@ async fn openai_model_header_casing_only_mismatch_does_not_warn() -> Result<()>
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "trigger casing check".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -502,6 +502,7 @@ async fn tool_search_returns_deferred_tools_without_follow_up_tool_injection() -
|
||||
let test = builder.build(&server).await?;
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "Find the calendar create tool".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -778,6 +779,7 @@ async fn tool_search_returns_deferred_dynamic_tool_and_routes_follow_up_call() -
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "Use the automation tool".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -157,6 +157,7 @@ async fn run_snapshot_command_with_options(
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "run unified exec with shell snapshot".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -248,6 +249,7 @@ async fn run_shell_command_snapshot_with_options(
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "run shell_command with shell snapshot".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -319,6 +321,7 @@ async fn run_tool_turn_on_harness(
|
||||
let cwd = test.cwd_path().to_path_buf();
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: prompt.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -554,6 +557,7 @@ async fn shell_command_snapshot_still_intercepts_apply_patch() -> Result<()> {
|
||||
let model = test.session_configured.model.clone();
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "apply patch via shell_command with snapshot".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -44,6 +44,7 @@ async fn submit_turn_with_policies(
|
||||
) -> Result<()> {
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: prompt.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -99,6 +99,7 @@ async fn user_turn_includes_skill_instructions() -> Result<()> {
|
||||
let session_model = test.session_configured.model.clone();
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![
|
||||
UserInput::Text {
|
||||
text: "please use $demo".to_string(),
|
||||
|
||||
@@ -399,6 +399,7 @@ async fn mcp_call_marks_thread_memory_mode_polluted_when_configured() -> Result<
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "call the rmcp echo tool".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -94,6 +94,7 @@ async fn continue_after_stream_error() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "first message".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -114,6 +115,7 @@ async fn continue_after_stream_error() {
|
||||
// error above, this submission would be rejected/queued indefinitely.
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "follow up".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -78,6 +78,7 @@ async fn retries_on_early_close() {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -78,6 +78,7 @@ async fn shell_tool_executes_command_and_streams_output() -> anyhow::Result<()>
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please run the shell command".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -149,6 +150,7 @@ async fn update_plan_tool_emits_plan_update_event() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please update the plan".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -230,6 +232,7 @@ async fn update_plan_tool_rejects_malformed_payload() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please update the plan".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -326,6 +329,7 @@ async fn apply_patch_tool_executes_and_emits_patch_events() -> anyhow::Result<()
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please apply a patch".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -430,6 +434,7 @@ async fn apply_patch_reports_parse_diagnostics() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please apply a patch".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -35,6 +35,7 @@ async fn run_turn(test: &TestCodex, prompt: &str) -> anyhow::Result<()> {
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: prompt.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -352,6 +353,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 {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "stream delayed completion".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -20,6 +20,7 @@ use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
use core_test_support::assert_regex_match;
|
||||
use core_test_support::responses::ev_assistant_message;
|
||||
use core_test_support::responses::ev_completed;
|
||||
@@ -75,6 +76,100 @@ fn ev_namespaced_function_call(
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn empty_turn_environments_omits_environment_backed_tools() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let response_mock = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_response_created("resp-1"),
|
||||
ev_assistant_message("msg-1", "done"),
|
||||
ev_completed("resp-1"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut builder = test_codex().with_config(|config| {
|
||||
config
|
||||
.features
|
||||
.enable(Feature::UnifiedExec)
|
||||
.expect("unified exec should enable for test");
|
||||
config
|
||||
.features
|
||||
.enable(Feature::JsRepl)
|
||||
.expect("js repl should enable for test");
|
||||
config.include_apply_patch_tool = true;
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
test.submit_turn_with_environments("which tools are available?", Some(vec![]))
|
||||
.await?;
|
||||
|
||||
let tools = tool_names(&response_mock.single_request().body_json());
|
||||
assert!(
|
||||
tools.contains(&"update_plan".to_string()),
|
||||
"non-environment tool should remain available; got {tools:?}"
|
||||
);
|
||||
for environment_tool in [
|
||||
"exec_command",
|
||||
"write_stdin",
|
||||
"js_repl",
|
||||
"js_repl_reset",
|
||||
"apply_patch",
|
||||
"view_image",
|
||||
] {
|
||||
assert!(
|
||||
!tools.contains(&environment_tool.to_string()),
|
||||
"{environment_tool} should be omitted for explicit empty turn environments; got {tools:?}"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn turn_environment_selection_keeps_environment_backed_tools() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let response_mock = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_response_created("resp-1"),
|
||||
ev_assistant_message("msg-1", "done"),
|
||||
ev_completed("resp-1"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut builder = test_codex().with_config(|config| {
|
||||
config
|
||||
.features
|
||||
.enable(Feature::UnifiedExec)
|
||||
.expect("unified exec should enable for test");
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
test.submit_turn_with_environments(
|
||||
"which tools are available?",
|
||||
Some(vec![TurnEnvironmentSelection {
|
||||
environment_id: "local".to_string(),
|
||||
cwd: test.config.cwd.clone(),
|
||||
}]),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let tools = tool_names(&response_mock.single_request().body_json());
|
||||
assert!(
|
||||
tools.contains(&"exec_command".to_string()),
|
||||
"environment tool should remain available with selected local environment; got {tools:?}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn custom_tool_unknown_returns_custom_output_error() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
@@ -500,6 +500,7 @@ async fn mcp_image_output_preserves_image_and_no_text_summary() -> Result<()> {
|
||||
fixture
|
||||
.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "call the rmcp image tool".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -166,6 +166,7 @@ async fn submit_unified_exec_turn(
|
||||
|
||||
test.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: prompt.into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -250,6 +251,7 @@ async fn unified_exec_intercepts_apply_patch_exec_command() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "apply patch via unified exec".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1740,6 +1742,7 @@ async fn unified_exec_keeps_long_running_session_after_turn_end() -> Result<()>
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "keep unified exec process after turn end".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1833,6 +1836,7 @@ async fn unified_exec_interrupt_preserves_long_running_session() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "interrupt long-running unified exec".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2305,6 +2309,7 @@ async fn unified_exec_runs_under_sandbox() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "summarize large output".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2414,6 +2419,7 @@ async fn unified_exec_enforces_glob_deny_read_policy() -> Result<()> {
|
||||
let session_model = session_configured.model.clone();
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "read the fixture files".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -2542,6 +2548,7 @@ async fn unified_exec_python_prompt_under_seatbelt() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "start python under seatbelt".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -57,6 +57,7 @@ mv "${tmp_path}" "${payload_path}""#,
|
||||
// 1) Normal user input – should hit server once.
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello world".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -170,6 +170,7 @@ async fn user_shell_command_does_not_replace_active_turn() -> anyhow::Result<()>
|
||||
fixture
|
||||
.codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "run model shell command".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -160,6 +160,7 @@ async fn assert_user_turn_local_image_resizes_to(
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::LocalImage {
|
||||
path: abs_path.clone(),
|
||||
}],
|
||||
@@ -279,6 +280,7 @@ async fn view_image_tool_attaches_local_image() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please add the screenshot".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -410,6 +412,7 @@ async fn view_image_tool_can_preserve_original_resolution_when_requested_on_gpt5
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please add the original screenshot".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -509,6 +512,7 @@ async fn view_image_tool_errors_clearly_for_unsupported_detail_values() -> anyho
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please attach the image at low detail".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -599,6 +603,7 @@ async fn view_image_tool_treats_null_detail_as_omitted() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please attach the image with a null detail".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -699,6 +704,7 @@ async fn view_image_tool_resizes_when_model_lacks_original_detail_support() -> a
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please add the screenshot".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -803,6 +809,7 @@ async fn view_image_tool_does_not_force_original_resolution_with_capability_only
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please add the screenshot".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -905,6 +912,7 @@ await codex.emitImage(out);
|
||||
let session_model = session_configured.model.clone();
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "use js_repl to write an image and attach it".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1025,6 +1033,7 @@ console.log(out.type);
|
||||
let session_model = session_configured.model.clone();
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "use js_repl to write an image but do not emit it".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1118,6 +1127,7 @@ async fn view_image_tool_errors_when_path_is_directory() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please attach the folder".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1199,6 +1209,7 @@ async fn view_image_tool_errors_for_non_image_files() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please use the view_image tool to read the json file".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1285,6 +1296,7 @@ async fn view_image_tool_errors_when_file_missing() -> anyhow::Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please attach the missing image".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1422,6 +1434,7 @@ async fn view_image_tool_returns_unsupported_message_for_text_only_model() -> an
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "please attach the image".into(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -1503,6 +1516,7 @@ async fn replaces_invalid_local_image_after_bad_request() -> anyhow::Result<()>
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::LocalImage {
|
||||
path: abs_path.clone(),
|
||||
}],
|
||||
|
||||
@@ -150,6 +150,7 @@ async fn websocket_fallback_hides_first_websocket_retry_stream_error() -> Result
|
||||
|
||||
codex
|
||||
.submit(Op::UserTurn {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: "hello".into(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -104,6 +104,7 @@ async fn window_id_advances_after_compact_persists_on_resume_and_resets_on_fork(
|
||||
async fn submit_user_turn(codex: &Arc<CodexThread>, text: &str) -> Result<()> {
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: text.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
|
||||
@@ -745,6 +745,7 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
|
||||
thread_id: primary_thread_id_for_span.clone(),
|
||||
input: items.into_iter().map(Into::into).collect(),
|
||||
responsesapi_client_metadata: None,
|
||||
environments: None,
|
||||
cwd: Some(default_cwd),
|
||||
approval_policy: Some(default_approval_policy.into()),
|
||||
approvals_reviewer: None,
|
||||
|
||||
@@ -108,6 +108,7 @@ pub async fn run_codex_tool_session(
|
||||
let submission = Submission {
|
||||
id: sub_id.clone(),
|
||||
op: Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: initial_prompt.clone(),
|
||||
// MCP tool prompts are plain text with no UI element ranges.
|
||||
@@ -156,6 +157,7 @@ pub async fn run_codex_tool_session_reply(
|
||||
.insert(request_id.clone(), thread_id);
|
||||
if let Err(e) = thread
|
||||
.submit(Op::UserInput {
|
||||
environments: None,
|
||||
items: vec![UserInput::Text {
|
||||
text: prompt,
|
||||
// MCP tool prompts are plain text with no UI element ranges.
|
||||
|
||||
@@ -103,6 +103,12 @@ pub const REALTIME_CONVERSATION_OPEN_TAG: &str = "<realtime_conversation>";
|
||||
pub const REALTIME_CONVERSATION_CLOSE_TAG: &str = "</realtime_conversation>";
|
||||
pub const USER_MESSAGE_BEGIN: &str = "## My request for Codex:";
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema)]
|
||||
pub struct TurnEnvironmentSelection {
|
||||
pub environment_id: String,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, TS)]
|
||||
#[serde(transparent)]
|
||||
#[ts(type = "string")]
|
||||
@@ -425,6 +431,9 @@ pub enum Op {
|
||||
UserInput {
|
||||
/// User input items, see `InputItem`
|
||||
items: Vec<UserInput>,
|
||||
/// Optional turn-scoped environment selections.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
environments: Option<Vec<TurnEnvironmentSelection>>,
|
||||
/// 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>,
|
||||
@@ -488,6 +497,10 @@ pub enum Op {
|
||||
/// Optional personality override for this turn.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
personality: Option<Personality>,
|
||||
|
||||
/// Optional turn-scoped environment selections.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
environments: Option<Vec<TurnEnvironmentSelection>>,
|
||||
},
|
||||
|
||||
/// Inter-agent communication that should be recorded as assistant history
|
||||
@@ -715,6 +728,7 @@ pub enum ThreadMemoryMode {
|
||||
impl From<Vec<UserInput>> for Op {
|
||||
fn from(value: Vec<UserInput>) -> Self {
|
||||
Op::UserInput {
|
||||
environments: None,
|
||||
items: value,
|
||||
final_output_json_schema: None,
|
||||
responsesapi_client_metadata: None,
|
||||
@@ -4883,6 +4897,7 @@ mod tests {
|
||||
#[test]
|
||||
fn user_input_serialization_omits_final_output_json_schema_when_none() -> Result<()> {
|
||||
let op = Op::UserInput {
|
||||
environments: None,
|
||||
items: Vec::new(),
|
||||
final_output_json_schema: None,
|
||||
responsesapi_client_metadata: None,
|
||||
@@ -4901,6 +4916,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
op,
|
||||
Op::UserInput {
|
||||
environments: None,
|
||||
items: Vec::new(),
|
||||
final_output_json_schema: None,
|
||||
responsesapi_client_metadata: None,
|
||||
@@ -4921,6 +4937,7 @@ mod tests {
|
||||
"additionalProperties": false
|
||||
});
|
||||
let op = Op::UserInput {
|
||||
environments: None,
|
||||
items: Vec::new(),
|
||||
final_output_json_schema: Some(schema.clone()),
|
||||
responsesapi_client_metadata: None,
|
||||
@@ -4942,6 +4959,7 @@ mod tests {
|
||||
#[test]
|
||||
fn user_input_with_responsesapi_client_metadata_round_trips() -> Result<()> {
|
||||
let op = Op::UserInput {
|
||||
environments: None,
|
||||
items: Vec::new(),
|
||||
final_output_json_schema: None,
|
||||
responsesapi_client_metadata: Some(HashMap::from([(
|
||||
|
||||
@@ -70,7 +70,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_unix_executes_script_without_extension() -> Result<()> {
|
||||
let env = TestExecutableEnv::new()?;
|
||||
let mut cmd = Command::new(&env.program_name);
|
||||
let mut cmd = Command::new(&env.executable_path);
|
||||
cmd.envs(&env.mcp_env);
|
||||
|
||||
let output = cmd.output().await;
|
||||
@@ -138,6 +138,8 @@ mod tests {
|
||||
// Held to prevent the temporary directory from being deleted.
|
||||
_temp_dir: TempDir,
|
||||
program_name: String,
|
||||
#[cfg(unix)]
|
||||
executable_path: std::path::PathBuf,
|
||||
mcp_env: HashMap<OsString, OsString>,
|
||||
}
|
||||
|
||||
@@ -160,6 +162,8 @@ mod tests {
|
||||
let mcp_env = create_env_for_mcp_server(Some(extra_env), &[])?;
|
||||
|
||||
Ok(Self {
|
||||
#[cfg(unix)]
|
||||
executable_path: Self::executable_path(dir_path),
|
||||
_temp_dir: temp_dir,
|
||||
program_name: Self::TEST_PROGRAM.to_string(),
|
||||
mcp_env,
|
||||
@@ -184,6 +188,11 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn executable_path(dir: &Path) -> std::path::PathBuf {
|
||||
dir.join(Self::TEST_PROGRAM)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn set_executable(path: &Path) -> Result<()> {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
@@ -150,6 +150,7 @@ impl AppCommand {
|
||||
) -> Self {
|
||||
Self(Op::UserTurn {
|
||||
items,
|
||||
environments: None,
|
||||
cwd,
|
||||
approval_policy,
|
||||
approvals_reviewer: None,
|
||||
@@ -296,6 +297,7 @@ impl AppCommand {
|
||||
final_output_json_schema,
|
||||
collaboration_mode,
|
||||
personality,
|
||||
environments: _,
|
||||
} => AppCommandView::UserTurn {
|
||||
items,
|
||||
cwd,
|
||||
|
||||
@@ -532,6 +532,7 @@ impl AppServerSession {
|
||||
thread_id: thread_id.to_string(),
|
||||
input: items.into_iter().map(Into::into).collect(),
|
||||
responsesapi_client_metadata: None,
|
||||
environments: None,
|
||||
cwd: Some(cwd),
|
||||
approval_policy: Some(approval_policy.into()),
|
||||
approvals_reviewer: Some(approvals_reviewer.into()),
|
||||
|
||||
Reference in New Issue
Block a user